import sys
import chardet
import re

from binparser import type_map, load_bin_file, search_data, append_data, find_ascii_strings, guess_vartype


"""
Binaryファイルを解析する。
1. Binaryファイルと、それに対応するtextファイルを準備
2. Binaryエディタや生成AIで endian、整数型、浮動小数点型を推測
   endianについては、ほとんどの場合 little
3. parse_text_file() のように、テキストファイルを読み込み、
   binaryファイルに書かれている可能性のあるデータに対して
   辞書型変数のリストを返す。
     append_data(辞書型リスト変数, varname, vartype, value, eps)
     　vartypeはbinparser.type_map参照。
     　epsはfloat型の許容誤差
   注: テキストファイルの内容から int 型と推測した場合は、float型の可能性もあるので、両方登録する
   注: テキストファイルに明記されていない値でもbinaryファイルに書かれている可能性があるので、
       それらも登録する。
       例: ndata, Vtube, Itube
4. 3.で作成した辞書型リスト変数を binaryファイルから検索し、可能性のあるoffsetを取得
5. 実行最後に、可能性のある offset のリストをいれた変数 var_inf の定義が表示される
var_inf = [
    { 's': 'offset(decimal):offset(hex)', 'offset': offset, 'varname': varname, 'value': value },
...
    ]
＊＊このプログラムはここまで

この後：read_RIGAKU_raw.py を参考にbinaryデータ読み込み、グラフ描画プログラムを作る
A. 5.のvar_infをコピペ
B. var_infの中身はvalueだけで判断して追加しているので、varnameについてもっともらしいものだけを残す
C. read_RIGAKU_raw.pyを実行。
   text fileのメタデータの値との整合性を表示しているので確認 (testプログラムを作ってもいい)
   グラフ表示されればOK。

"""

text_file = 'sk250606-2-Eagle-Bi5w-S150c50w-Sub200c-25min-oradw.txt'
bin_file = 'sk250606-2-Eagle-Bi5w-S150c50w-Sub200c-25min-oradw.raw'
eps = 0.001
nmaxdata = 5
endian = 'little'
data_offset = 0xc56 + 16 #None

argv = sys.argv
nargs = len(argv)
if nargs > 1: text_file = argv[1]
if nargs > 2: bin_file = argv[2]
if nargs > 3: eps = float(argv[3])
if nargs > 4: nmaxdata = int(argv[4])
if nargs > 5: data_offset = int(argv[5], 0)
if nargs > 6: endian = argv[6]

print()
print(f"{text_file=}")
print(f"{bin_file=}")
print(f"{eps=}")
print(f"{nmaxdata=}")
print(f"{data_offset=}")
print(f"{endian=}")


def usage():
    print()
    print(f"Usage: python {argv[0]} text_file bin_file [eps nmaxdata endian]")
    print("   endian: little | big")
    print()

def parse_text_file(infile, nmaxdata = None):
    # 文字コードを判定
    with open(infile, 'rb') as f:
        raw = f.read(4096)
        encoding = chardet.detect(raw)['encoding']

    metadata = []
    x_list = []
    y_list = []
    with open(infile, 'r', encoding=encoding) as f:
        idata = 0
        for line in f:
            line = line.strip()
            if not line: continue
            
            parts = line.split('\t')
            key = parts[0].strip()
            if len(parts) == 1:
                # 1列しかない場合は空欄として扱う
                varname = parts[0].strip()
                append_data(metadata, varname, 'str', '', eps)
            elif not re.fullmatch(r'^-?\d+(\.\d+)?([eE][-+]?\d+)?$', key):
                varname = parts[0].strip()
                value = parts[1].strip()
                guess_type = guess_vartype(value)
                if guess_type == 'int':
# 文字列がintとして解釈できるときは、floatによる検索も行う
                    value = int(value)
                    append_data(metadata, varname, 'int16', value, eps)
                    value = float(value)
                    append_data(metadata, varname, 'float32', value, eps)
                elif guess_type == 'float':
                    value = float(value)
                    append_data(metadata, varname, 'float32', value, eps)
                else:
                    append_data(metadata, varname, 'str', value, eps)

                if varname == 'Start':
                    start = value
                elif varname == 'Stop':
                    stop = value
                elif varname == 'Step':
                    step = value
            else:
                if nmaxdata is not None and idata >= nmaxdata: break

                x = float(parts[0])
                y = float(parts[1])
                x_list.append(x)
                y_list.append(y)
                append_data(metadata, f'x[{idata}]', 'float32', x, eps)
                append_data(metadata, f'y[{idata}]', 'float32', y, eps)
                idata += 1

#add ndata
    ndata = int((stop - start) / step + 1.0001)
    append_data(metadata, 'ndata', 'int16', ndata, eps)

#X-Ray	40kV/15mA
#IncidentSlitBox	0.625deg
    append_data(metadata, 'Vtube', 'float32', 40.0, eps)
    append_data(metadata, 'Itube', 'float32', 15.0, eps)
    append_data(metadata, 'IncidentSlitBox', 'float32', 0.625, eps)

    return metadata, x_list, y_list


def main():
    global data_offset
    
    print()
    print(f"Read text file [{text_file}]")
    inf, x_list, y_list = parse_text_file(text_file, nmaxdata = nmaxdata)
    print("inf=", inf)

    print(f"Read raw file [{bin_file}]")
    bin_data = load_bin_file(bin_file)
    ilast = len(bin_data)
    
    size = 4
    print()
# data_offset is None の場合、y[0]からoffset+αを取得
    if data_offset is None:
        print("Guess data offset...")
        for d in inf:
            varname = d['varname']
            if varname != 'y[0]': continue

            vartype = d['vartype']
            size    = type_map[vartype][1]
            value   = d['value']
            eps     = d['eps']

            offset_y0s = search_data(bin_data, 0, ilast, size = size, 
                        vartype = vartype, endian = endian, value = value, tolerance = eps,
                        varname = varname)

            if len(offset_y0s) > 0: data_offset = offset_y0s[-1]
            break

    print("Analyzing...")
# y[0]をnmaxdata個読みだすので、ilast を nmaxdata*2個分余裕を取る
    ilast = data_offset + size * nmaxdata * 2

# データ読み出し
    data_list = []
    for d in inf:
        varname = d['varname']
        vartype = d['vartype']
        value   = d['value']
        eps     = d['eps']

        if vartype == 'str':
            size = len(value)
        else:
            size = type_map[vartype][1]

        if size == 0:
            print(f"Null data for [{varname}]. Skip.")
            continue
        
        offsets = search_data(bin_data, 0, ilast, size = size, 
                        vartype = vartype, endian = endian, value = value, tolerance = eps,
                        varname = varname)
        if not offsets:
            print(f"No data found for [{varname}] with value=[{value}]")
        else:
            for offset in offsets:
                print(f"offset 0x{offset:08x} for [{varname}] with [{value}]")
                data_list.append({"offset": offset, "vartype": vartype, "varname": varname, "value": value, "eps": eps})

    print()
    print(f"{ilast=}")
    str_list = find_ascii_strings(bin_data, 0, ilast, 4)
    print("Strings in bin_data header")
    print(str_list)
    for d in str_list:
        data_list.append({"offset": d[0], "vartype": "str", "varname": d[1], "value": d[1], "eps": 0.0})

    data_list = sorted(data_list, key=lambda x: x['offset'])
    print()
    print("var_inf = [")
    for d in data_list:
        offset  = d["offset"]
        vartype = d["vartype"]
        varname = d["varname"]
        value   = d["value"]
        eps     = d["eps"]
#        print(f"{offset:08d}:{offset:08x}:{varname}:{value}")
        if vartype == 'str':
            value = f"'{value}'"
        print(f"    {{ 's': '{offset:08d}:{offset:08x}', 'offset': {offset}, 'vartype': '{vartype}', 'varname': '{varname}', 'value': {value}, 'eps': {eps} }},")

    print("    ]")
    print()


if __name__ == '__main__':
    main()
    usage()
