#!/usr/bin/env python
# -*- coding: Shift_JIS -*-

# ##############################

# ファイル名: analyze.py
# 機能: 構文解析，条件処理表作成を開始する．
#	programs/analyze_programs内のpythonスクリプトを呼び出す．
# 関数: ReportException, CheckSyntax, MovePath, Preprocess, RemoveBlankLines,
#	CReform, PreIf, PreSw, Marking, Mujoken, Indent, GlobalDelete, ReplaceFilename
#	Postprocess
# 著者名: 山本椋太，若林丈紘

# ##############################

# convert python3 from kaiseki.sh

import os
import sys
import re
import shutil
import argparse
import codecs
import subprocess
import datetime

# 外部 python ソース群
import c_reform
import preif
import presw
import marking
import mujoken
import indent
import globaldelete
import pragma
import jokenshori
import table
import jotaivar
import rule_jotaivar

## for self configuration gcc(added 2018/01/23)
fi = open('lists/compile.txt', 'r')
comlines = fi.read()
fi.close()
comlines = comlines.replace('\r\n', ' ')
comlines = comlines.replace('\r', ' ')
comlines = comlines.replace('\n', ' ')
while '  ' in comlines: comlines = comlines.replace('  ', ' ')
comlines_str = comlines
comlines = comlines.split(' ')
GCC_PATH = comlines[0]
comlines = comlines[1:]
## end


# GCC_PATH = "gcc"

# ##############################

# 関数名: ReportException
# 機能: エラーメッセージをtemp/error_messageに書き込む．
# 引数
#   error_message: エラーメッセージ

# ##############################

def ReportException(error_message):
    error = open("temp/error_message","w")
    error.write(error_message)
    error.close()

# ##############################

# 関数名: CheckSyntax
# 機能: 解析対象のcファイルがコンパイル可能であるか確認する．
# 引数
#   filename: 解析対象のcファイル

# ##############################

def CheckSyntax(filename):
    ferror = open('./log/compile_error.txt', 'a')
    # before begin
    gccSyntaxCheckCmd = ([GCC_PATH, "-fsyntax-only", "-w", "-P", filename])
    #before end
    #after begin
    gccSyntaxCheckCmd = ([GCC_PATH]+comlines+[filename])
    #after end
    p = subprocess.Popen(gccSyntaxCheckCmd, stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    _,err = p.communicate()
    gccErr = err.decode("utf8","ignore")
    gccErr = gccErr.split("\n")
    newgccErr = []
    for ge in gccErr:
        if "error: unknown type name" in ge or "不明な型名" in ge or \
           "undeclared (first use in this function)" in ge or "宣言されていません" in ge or \
           "error: stray" in ge or "プログラム内に逸脱した" in ge or \
           "error: invalid suffix" in ge or "整数定数に無効な接尾辞" in ge or \
           "expected declaration specifiers" in ge or \
           "invalid digit" in ge or \
           " before numeric constant" in ge or \
           " undeclared here (not in a function)" in ge or \
           " in something not a structure or union" in ge or "構造体または共用体ではない何かのメンバ" in ge:
             pass
        else:
            newgccErr.append(ge)
    newgccErr = "".join(newgccErr)
    for ge in gccErr:
        ge = ge.replace("___.c", ".c")
        print(ge, file=ferror)
    ferror.close()
    return newgccErr

# ##############################

# 関数名: MovePath
# 機能: RExSTMのルートディレクトリに移動する．
# 引数
#   なし

# ##############################

def MovePath():
    current_path = os.path.abspath(sys.argv[0])
    basename = os.path.basename(current_path)
    current_path = current_path.replace(basename,"")
    os.chdir(current_path)
    os.chdir('../..')

# ##############################

# 関数名: Preprocess
# 機能: 解析対象のcファイルの整形を行う．
# 引数
#   filename: 解析対象のcファイル

# ##############################

def Preprocess(filename):
    whndl = open("temp/temp_before.c","w")

    for line in open(filename,"r") :
        convline = line
        # インデントの削除
        convline = re.sub(r'^[ \t]*', '', convline)
        # {}を新しい行におく
        convline = re.sub(r'\{','\n{\n', convline)
        convline = re.sub(r'\}','\n}\n', convline)
        ## 1行に1命令のみを許す
        # convline = convline.replace(';',';\n')
        # '('および')'のそれぞれの前後の空白文字(スペースとタブ)を削除
        ## python なら strip() のほうがいいかも
        convline = re.sub(r'[ \t]*\([ \t]*', '(', convline)
        convline = re.sub(r'[ \t]*\)[ \t]*', ')', convline)
        # 余分な改行を削除
        convline = re.sub(r'\{[ \n\t]*\}', '{\n}\n', convline)
        convline = re.sub(r'}[ \t]*;', '\n}\n;\n', convline)
        convline = re.sub(r'[\n][\n]+', '\n', convline)
        # 書き込み
        if( len(convline) > 0 and convline != "\n" ) :
            whndl.write(convline)
    whndl.close()

# ##############################

# 関数名: RemoveBlankLines
# 機能: 解析対象のcファイルから空行を除去する．
# 引数
#   なし

# ##############################

def RemoveBlankLines():
    shutil.copyfile("temp/temp_before.c", "temp/temp1")
    whndl = open("temp/temp_before.c","w")
    for line in open("temp/temp1","r") :
        if( len(line) > 0 and line != "\n" ) :
            # 書き込み
            whndl.write(line)
    whndl.close()
    shutil.copyfile("temp/temp_before.c", "temp/temp1")
    shutil.copyfile("temp/temp_before.c", "temp/temp2")

# ##############################

# 関数名: CReform
# 機能: c_reformを呼び出す．
# 引数
#   なし

# ##############################

def CReform():
    c_reform.c_reform("temp/temp1", "temp/temp2")
    shutil.copyfile("temp/temp2", "temp/temp1")
    shutil.copyfile("temp/temp2", "temp/c_reform")

# ##############################

# 関数名: PreIf
# 機能: preifを呼び出す．
# 引数
#   なし

# ##############################

def PreIf():
    preif.preif("temp/temp1", "temp/temp2")
    shutil.copyfile("temp/temp2", "temp/temp1")
    shutil.copyfile("temp/temp2", "temp/preif")

# ##############################

# 関数名: PreSw
# 機能: preswを呼び出す．
# 引数
#   なし

# ##############################

def PreSw():
    caseCheck = presw.presw("temp/temp1", "temp/temp2")
    if caseCheck == False:
        print("ERROR: The target has switch statements without internal case statements.\nTarget: [" + targetFiles[i] + "]")
        ReportException("ERROR: case句のないswitch文が存在します。\n対象ファイル: [" + targetFiles[i] + "]")
        shutil.move("temp/error_message", temp_dirname)
        return False

    shutil.copyfile("temp/temp2", "temp/temp1")
    shutil.copyfile("temp/temp2", "temp/presw")
    return True

# ##############################

# 関数名: Marking
# 機能: markingを呼び出す．
# 引数
#   なし

# ##############################

def Marking():
    marking.marking("temp/temp1", "temp/temp2")
    shutil.copyfile("temp/temp2", "temp/temp1")
    shutil.copyfile("temp/temp2", "temp/marking")

# ##############################

# 関数名: Mujoken
# 機能: mujokenを呼び出す．
# 引数
#   なし

# ##############################

def Mujoken():
    nestMaxDepth = mujoken.mujoken("temp/temp1", "temp/temp2")
    if nestMaxDepth > 6:
        print( "WARNING: Nesting depth greater than 5.\nTarget: [" + targetFiles[i] +"]")
        ReportException( "WARNING: 深さが5より大きいネスト構造が存在します。\n対象ファイル: [" + targetFiles[i] + "]")
    shutil.copyfile("temp/temp2", "temp/temp1")
    shutil.copyfile("temp/temp2", "temp/mujoken")

# ##############################

# 関数名: Indent
# 機能: indentを呼び出す．
# 引数
#   なし

# ##############################

def Indent():
    indent.indent("temp/temp1", "temp/temp2")
    shutil.copyfile("temp/temp2", "temp/temp1")
    shutil.copyfile("temp/temp2", "temp/indent")

# ##############################

# 関数名: GlobalDelete
# 機能: globaldeleteを呼び出す．
# 引数
#   なし

# ##############################

def GlobalDelete():
    globaldelete.globaldelete("temp/temp1", "temp/temp2")
    shutil.copyfile("temp/temp2", "temp/temp1")
    shutil.copyfile("temp/temp2", "temp/globaldelete")

# ##############################

# 関数名: ReplaceFilename
# 機能: ファイル名を書き換える
# 引数
#   filename: 解析対象のcファイル

# ##############################

def ReplaceFilename(filename):
    tsvFileName = filename.replace(".c", ".tsv")
    tsvFileName = tsvFileName.replace(temp_dirname, "")
    return tsvFileName

# ##############################

# 関数名: Postprocess
# 機能: 条件処理表を表すtsvファイルに対して改行文字等の後処理を行う．
# 引数
#   tsvFileName: 条件処理表を表すtsvファイル
#   temp_dirname: tempファイルをまとめたディレクトリ

# ##############################

def Postprocess(tsvFileName,temp_dirname):
    whndl = open("".join(["TSV_Result/", tsvFileName]), "wb")
    for line in open("temp/tableinfo/tableinfo3.tsv", "rb") :
        if( line[-2:] != b"\r\n" ) :
            line = line.replace(b"\n", b"\r\n")
        whndl.write(line)
    whndl.close()

    whndl = open("".join(["TSV_info/", tsvFileName.replace(".tsv",""), "_tableline.tsv"]), "wb")
    for line in open("temp/tableinfo/tableline.tsv","rb") :
        if( line[-2:] != b"\r\n" ) :
            line = line.replace(b"\n", b"\r\n")
        whndl.write(line)
    whndl.close()

    whndl = open("".join(["TSV_info/v_", tsvFileName]), "wb")
    for line in open("temp/jotaivarlist.tsv","rb") :
        if( line[-2:] != b"\r\n" ) :
            line = line.replace(b"\n", b"\r\n")
        whndl.write(line)
    whndl.close()

    shutil.move("temp/tableinfo", temp_dirname+"tableinfo")
    for tempfile in os.listdir("temp") :
        if os.path.isfile("temp/" + tempfile):
            shutil.move("temp/" + tempfile, temp_dirname + tempfile)

if __name__ == '__main__':

    # Select input C files
    print ("Read TargetPrograms")

    parser = argparse.ArgumentParser()
    parser.add_argument('-i','--no_include_flag',default = "y",type=str,help='Y/n ... You choice <Not_include> OR <Include> header files')
    parser.add_argument('-d','--debug_mode',default = "n",type=str,help='Y/n ... You make Log file /debug/analyze_YYYYMMDD.txt for stdout')
    parser.add_argument('-s','--stop_mode',\
                        default = "0",type=int,\
                        help='You can STOP after process \
                            [1:CheckSyntax][2:Preprocess][3:RemoveBlankLines][4:CReform]\
                            [5:PreIf][6:PreSw][7:Marking][8:Mujoken][9:Indent]\
                            [10:GlobalDelete][11:Pragma][12:ReplaceFilename][13:Jokenshori]\
                            [14:Table][15:Jotaivar][16:Rule_Jotaivar]')
    arg = parser.parse_args()

    if (arg.debug_mode.lower()=="y"):
        d = datetime.datetime.today()
        log_file = "debug/analyze_" + d.strftime('%Y%m%d%H%M%S') + ".txt"
        sys.stdout = open(log_file,"w")

    MovePath()

    if os.path.isdir("temp") == True:
        shutil.rmtree("temp")
    os.mkdir("temp")

    if os.path.isfile('./log/compile_error.txt'):
    	os.remove('./log/compile_error.txt')

    # Read all files
    targetFiles=[]
    temp_dirnames=[]

    for filename in os.listdir("TargetPrograms") :
        if filename[-2:] == ".c" :
            targetFiles.append("/".join(["TargetPrograms",filename]))

            temp_dirname = "temp/" + filename.replace(".c", "___o") + "/"
            temp_dirnames.append(temp_dirname)

            os.mkdir(temp_dirname)

    if len(targetFiles)==0:
        print("ERROR: C_source_file does NOT exist.")
        ReportException("ERROR: CファイルがTargetProgramsフォルダに存在しません。")
        sys.exit(-1)

    ####################################
    #  (3) START
    ####################################


    #print ("Do you NOT include ALL header file?")
    #sys.stdout.write ("(Y/n) -> ")  # 改行なし出力
    #print ("(Y/n)", end=" -> ")  # 改行なし出力
    #all_header = input()

    all_header = arg.no_include_flag

    # Read all files
    targetNewFiles=[]
    if (all_header.lower() == "y") :
        print ("Delete all INCLUDE")
        for filename in targetFiles :

            if ( filename[-5:] == "___.c" ) :
                targetFiles.remove(filename)
                continue    # 置換ファイルならパス
            new_filename = filename.replace('.c', '___.c')
            if ( os.path.exists(new_filename) ) :
                os.remove(new_filename) # すでに置換ファイルがあるなら消す
            whndl = open(new_filename,"wb")
            for line in open(filename,"rb") :
                if( re.match(b"[ \t]*#include", line) == None ) :
                    whndl.write(line)
            whndl.close()
            targetNewFiles.append(new_filename)
        print (targetNewFiles)


    # Read from no_included_files.txt
    elif (all_header.lower() == "n") :

        no_inc_files=[]

        #no_included_files.txt existent check
        print ("Read from no_included_files.txt")
        if ( os.path.exists("lists/no_included_files.txt") ) :
            print ("f1")
            for line in open("lists/no_included_files.txt","r") :
                no_inc_files.append(line.replace("\n",""))
        else :
            print("ERROR: no_included_files.txt does NOT exist.")
            ReportException("ERROR: no_included_files.txtがlistsフォルダに存在しません。")
            sys.exit(-1)

        #std.txt existent check
        print ("Read from std.txt")
        if ( os.path.exists("lists/std.txt") ) :
            print ("f2")
            for line in open("lists/std.txt","r") :
                no_inc_files.append(line.replace("\n",""))
        else :
            print("ERROR: std.txt does NOT exist.")
            ReportException("ERROR: std.txtがlistsフォルダに存在しません。")
            sys.exit(-1)

        print (" NOT valid header files\n", no_inc_files)

        for filename in targetFiles :
            if ( filename[-5:] == "___.c" ) :
                targetFiles.remove(filename)
                continue    # 置換ファイルならパス
            new_filename = filename.replace('.c', '___.c')
            if ( os.path.exists(new_filename) ) :
                os.remove(new_filename) # すでに置換ファイルがあるなら消す

            targetNewFiles.append(new_filename)

            # ユーザがあらかじめ選択したヘッダをソースから除外する #
            # その他のヘッダファイルがTargetPrograms内に置いていなければエラー #
            f = codecs.open(filename,"r", "shift_jis")
            source_lines = f.read()
            f.close()

            removed_headers=[]
            header_list = re.findall(r"[ \t]*#include[ \t][<\"].+[>\"]", source_lines)
            for header in header_list:
                for nif in no_inc_files:
                    if nif in header:
                        source_lines.replace(header,"")
                        removed_headers.append(header)

            header_list = [h for h in header_list if h not in removed_headers]
            header_list = [h.replace("#include ","") for h in header_list]
            header_list = [h.replace("\"","") for h in header_list]

            print ("valid header files\n",header_list)
            for header in header_list:
                if os.path.exists("TargetPrograms/" + header)==False:
                    filename = filename.replace("___.c", ".c")
                    print("ERROR: header_file does NOT exist "+header+"\n["+filename+"]")
                    ReportException("ERROR: ヘッダファイル"+header+"がTargetProgramsに存在しません。\n対象ファイル: ["+filename+"]")
                    sys.exit(-1)
            # 対象Cファイルがコンパイルできるか判定する処理
            # あらかじめCファイルをまとめる処理に置き換える予定です
            # 170310若林

            # gccErr = CheckSyntax(filename)
            # if ("error:" in gccErr) or ("エラー:" in gccErr):
            #     filename = filename.replace("___.c", ".c")
            #     print ("\n-----syntactic analysis-----\n" + gccErr + "\n-------------------------\n")
            #     print("ERROR: The target can not be analyzed by gcc.\n[" + filename + "]")
            #     ReportException("ERROR: gccで構文解析できません。 \n対象ファイル: [" + filename + "]")
            #     sys.exit(-1)

            # gccExec = " ".join([GCC_PATH, "-E", filename, ">", new_filename])
            gccExec = " ".join([comlines_str, filename, ">", new_filename])
            print(gccExec)
            os.system(gccExec)
            newf = open(new_filename,"r")
            source_lines = newf.read()
            newf.close()

            pat = re.compile(r"#.+\"%s\"" %filename)
            source_lines = re.split(pat,source_lines)[-1]

            newf = open(new_filename,"w")
            newf.write(source_lines)
            newf.close()
        print (targetNewFiles)

    else :
        print("ERROR: Please input Y/n")
        ReportException("ERROR: Y もしくは n を入力してください。")
        sys.exit(-1)

    ####################################
    #  (4) START
    ####################################
    print ("Clear comments and expand MACROs")

    tempFiles=[]
    for i,filename in enumerate(targetNewFiles) :
        temp_dirname = temp_dirnames[i]
        new_filename = filename.replace("TargetPrograms/", temp_dirname)
        new_filename = new_filename.replace(".c", "o.c")

        gccErr = CheckSyntax(filename)
        if ("error:" in gccErr) or ("エラー:" in gccErr):
            print ("\n-----syntactic analysis-----\n" + gccErr + "\n-------------------------\n")
            os.remove(filename)
            filename = filename.replace("___.c", ".c")
            print("ERROR: The target can not be analyzed by gcc.\nTarget: [" + filename + "]")
            ReportException("ERROR: GCCで構文解析できません。\n対象ファイル: [" + filename + "]")
            shutil.move("temp/error_message", temp_dirname)
            continue

        tempFiles.append(new_filename)
        # gccExec = " ".join([GCC_PATH, "-E", "-P", filename, ">", new_filename])
        gccExec = " ".join([comlines_str, filename, ">", new_filename])
        print(gccExec)
        os.system(gccExec)
        os.remove(filename)

    if (len(tempFiles) == 0):
        print ("All Target Files cannot be analyzed.")
        sys.exit(-1)


    ####################################
    #  (6) START
    ####################################

    for i,filename in enumerate(tempFiles) :
        temp_dirname = temp_dirnames[i]

        if arg.stop_mode == 1:
            continue

        Preprocess(filename)

        if arg.stop_mode == 2:
            continue

        # 空行除去 ... 直前処理は行単位のため、行またがりの空行になった場合除去できない
        RemoveBlankLines()

        if arg.stop_mode == 3:
            continue

        CReform()

        if arg.stop_mode == 4:
            continue

        PreIf()

        if arg.stop_mode == 5:
            continue

        if PreSw()==False: continue

        if arg.stop_mode == 6:
            continue

        Marking()

        if arg.stop_mode == 7:
            continue

        Mujoken()

        if arg.stop_mode == 8:
            continue

        Indent()

        if arg.stop_mode == 9:
            continue

        GlobalDelete()

        if arg.stop_mode == 10:
            continue

        pragma.pragma("temp/temp1", "temp/temp2")

        if arg.stop_mode == 11:
            continue

        tsvFilename = ReplaceFilename(filename)

        if arg.stop_mode == 12:
            continue

        jokenshori.jokenshori("temp/temp2")

        if arg.stop_mode == 13:
            continue

        table.table()

        if arg.stop_mode == 14:
            continue

        jotaivar.jotaivar("temp/temp2")

        if arg.stop_mode == 15:
            continue

        rule_jotaivar.rule_jotaivar("temp/temp2")

        if arg.stop_mode == 16:
            continue

        Postprocess(tsvFilename,temp_dirname)
