import zlib
import hashlib
from pathlib import Path
import click

def print_carray(f, payload):
    while len(payload) > 0:
        f.write('\n    ')
        f.write(', '.join('0x{:02x}'.format(x) for x in payload[0:16]))
        f.write(',')
        payload = payload[16:]
    f.write('\n')


@click.command()
@click.argument('dpath', type=click.Path(exists=True), default='.')
@click.option('--sd', is_flag=True, default=False, help='Generate header for using with SDCard')
def main(dpath, sd):
    """
    This script takes a directory path (default is current directory) and
    create compressed .bin.gz file along 'esp_binaries.h' containing metadata in esp32_zipfile_t struct.
    Which can be used to program ESP32 with TestBed or Brain.
    If '--sd' option is specified, it will skip generating C array data for gz file.
    """
    output_header = 'esp_binaries.h'
    with open(output_header, 'w') as fc:
        # print typedef struct
        fc.write('// Generated by tools/esp_compress.py\n')
        file_list = sorted(Path(dpath).glob('*.bin'))
        fc.write(f'#define ESP_BINARIES_COUNT ({len(file_list)})\n\n')

        # print list of struct first
        for fname in file_list:
            var = fname.stem
            var = var.replace('.', '_')
            var = var.replace('-', '_')
            fc.write(f'// const esp32_zipfile_t {var}\n')
        fc.write('\n')

        for fname in file_list:
            with open(fname, 'rb') as fi:
                image = fi.read()
                zimage = zlib.compress(image, 9)
                fzname = fname.with_suffix('.bin.gz')
                md5 = hashlib.md5(image)

                # write .gz file
                with open(fzname, 'wb') as fz:
                    fz.write(zimage)

                # write to c header file
                var = fname.stem
                var = var.replace('.', '_')
                var = var.replace('-', '_')

                # bin gz contents
                if not sd:
                    fc.write(f'const uint8_t _{var}_gz[{len(zimage)}] = {{')
                    print_carray(fc, zimage)
                    fc.write('};\n\n')

                fc.write(f'const esp32_zipfile_t {var} = {{\n')
                fc.write(f'  .name = "{fzname}",\n')
                if sd:
                    fc.write(f'  .data = NULL,\n')
                else:
                    fc.write(f'  .data = _{var}_gz,\n')
                fc.write(f'  .compressed_len = {len(zimage)},\n')
                fc.write(f'  .uncompressed_len = {len(image)},\n')
                fc.write('  .md5 = {')
                print_carray(fc, md5.digest())
                fc.write('  },\n')
                fc.write('};\n')
                fc.write('\n')

                deflation = 100*(1-len(zimage)/len(image))
                print(f"Compressed {var}: {len(image)} to {len(zimage)} bytes, deflation: {deflation:.2f}%")


if __name__ == '__main__':
    main()
