import sys
import struct
import re


__all__ = ['load_bin_file', 'get_data', 'search_data']

type_map = {
        'float32': ('f', 4),
        'float64': ('d', 8),
        'uint16':  ('H', 2),
        'int16':   ('h', 2),
        'uint32':  ('I', 4),
        'int32':   ('i', 4),
        'uint64':  ('Q', 8),
        'int64':   ('q', 8),
    }


def append_data(metadata, varname, vartype, value, eps):
    metadata.append({
        'varname': varname,
        'vartype': vartype,
        'value'  : value,
        'eps'    : eps
        })
    return metadata

def guess_vartype(str):
    try:
        int(str)
        return 'int'
    except:
        pass
        
    try:
        float(str)
        return 'float'
    except:
        pass

    return 'str'

def find_ascii_strings(in_data, istart, ilast, min_length=4):
    pattern = rb'[\x20-\x7E]{%d,}' % min_length  # ASCII printable characters
    results = []

    for match in re.finditer(pattern, in_data[istart:ilast+1]):
        offset = match.start()
        value = match.group().decode('ascii', errors='ignore')
        results.append((offset, value))

    return results

def load_bin_file(file_path):
    with open(file_path, 'rb') as f:
        return f.read()

def extract_ascii_prefix(bin_data, offset, size):
    chunk = bin_data[offset:offset+size]
    result = bytearray()
    for b in chunk:
        if 0x20 <= b <= 0x7E:
            result.append(b)
        else:
            break  # 非ASCII文字が出たら終了
    return result.decode('ascii', errors='ignore')

def get_data(bin_data, offset, size, vartype, endian='little', varname = None):
    if endian == 'little':
        endian_prefix = '<'
    elif endian == 'big':
        endian_prefix = '>'
    else:
        raise ValueError(f"未対応のエンディアン指定: {endian}")

    if vartype == 'str':
        return extract_ascii_prefix(bin_data, offset, size)

    if vartype in type_map:
        fmt, expected_size = type_map[vartype]
        if size != expected_size:
            raise ValueError(f"サイズ {size} は型 {vartype} に対して不正です（期待値: {expected_size}）")
        try:
            ret = struct.unpack_from(endian_prefix + fmt, bin_data, offset)[0]
            return ret
        except:
            print(f"Error in binparser.get_data(): Failed to read [{varname}] (type {vartype}) from offset={offset} with size={size}")
            return None

    raise ValueError(f"未対応の型: {vartype}")

def search_data(bin_data, itop, ilast, size, vartype, endian, value, tolerance=1e-6, varname = 'not given'):
#    print("**search:", varname, value, vartype, size, tolerance)
    if endian == 'little':
        endian_prefix = '<'
    elif endian == 'big':
        endian_prefix = '>'
    else:
        raise ValueError(f"未対応のエンディアン指定: {endian}")

    matches = []

    if vartype == 'str':
        for offset in range(itop, ilast - size + 1):
            chunk = bin_data[offset:offset+size]
            try:
                decoded = chunk.decode('utf-8', errors='ignore')
                if value in decoded:
                    matches.append(offset)
            except UnicodeDecodeError:
                continue
        return matches

    if vartype not in type_map:
        raise ValueError(f"未対応の型: {vartype}")

    fmt, expected_size = type_map[vartype]
    if size != expected_size:
        raise ValueError(f"サイズ {size} は型 {vartype} に対して不正です（期待値: {expected_size}）")

    for offset in range(itop, ilast - size + 1):
        try:
            val = struct.unpack_from(endian_prefix + fmt, bin_data, offset)[0]
            if isinstance(val, float):
                if abs(val - value) <= tolerance:
                    matches.append(offset)
            else:
                if val == value:
                    matches.append(offset)
        except struct.error:
            continue

    return matches

def main():
    bin_file = 'test.raw'
    endian = 'little'

    if len(sys.argv) > 1: bin_file = sys.argv[1]
    if len(sys.argv) > 2: endian = sys.argv[2]

    bin_data = load_bin_file(bin_file)
    ilast = len(bin_data)
    eps = 0.002

    for val in [8.0, 120.0, 0.02, 514.902, 552.835]:
        offsets = search_data(bin_data, 0, ilast, 4, 'float32', endian, val, tolerance=eps)
        if not offsets:
            print(f"No data found for {val}")
        else:
            for offset in offsets:
                print(f"offset {offset:08x} for {val}")

    for val in [5601]:
        offsets = search_data(bin_data, 0, ilast, 2, 'int16', endian, val)
        if not offsets:
            print(f"No data found for {val}")
        else:
            for offset in offsets:
                print(f"offset {offset:08x} for {val}")

if __name__ == '__main__':
    main()
