#!/usr/bin/env python3
import os
import yaml
import re

from pprint import pprint
from argparse import ArgumentParser, ArgumentError
from home.pio import get_products, platformio_ini
from home.pio.exceptions import ProductConfigNotFoundError


def get_config(product: str) -> dict:
    config_path = os.path.join(
        os.getenv('HOME'), '.config',
        'homekit_pio', f'{product}.yaml'
    )
    if not os.path.exists(config_path):
        raise ProductConfigNotFoundError(f'{config_path}: product config not found')

    with open(config_path, 'r') as f:
        return yaml.safe_load(f)


def bsd_walk(product_config: dict,
             f: callable):
    try:
        for define_name, define_extra_params in product_config['build_specific_defines'].items():
            define_name = re.sub(r'^CONFIG_', '', define_name)
            kwargs = {}
            if isinstance(define_extra_params, dict):
                kwargs = define_extra_params
            f(define_name, **kwargs)
    except KeyError:
        pass


# 'bsd' means 'build_specific_defines'
def bsd_parser(product_config: dict,
               parser: ArgumentParser):
    def f(define_name, **kwargs):
        arg_kwargs = {}
        define_name = define_name.lower().replace('_', '-')

        if 'type' in kwargs:
            if kwargs['type'] in ('str', 'enum'):
                arg_kwargs['type'] = str
                if kwargs['type'] == 'enum' and 'list_config_key' in kwargs:
                    if not isinstance(product_config[kwargs['list_config_key']], list):
                        raise TypeError(f'product_config[{kwargs["list_config_key"]}] enum is not list')
                    if not product_config[kwargs['list_config_key']]:
                        raise ValueError(f'product_config[{kwargs["list_config_key"]}] enum cannot be empty')
                    arg_kwargs['choices'] = product_config[kwargs['list_config_key']]
                    if isinstance(product_config[kwargs['list_config_key']][0], int):
                        arg_kwargs['type'] = int
            elif kwargs['type'] == 'int':
                arg_kwargs['type'] = int
            else:
                raise TypeError(f'unsupported type {kwargs["type"]} for define {define_name}')
        else:
            arg_kwargs['action'] = 'store_true'

        parser.add_argument(f'--{define_name}', required=True, **arg_kwargs)

    bsd_walk(product_config, f)


def bsd_get(product_config: dict,
            arg: object):
    defines = {}
    enums = []
    def f(define_name, **kwargs):
        attr_name = define_name.lower()
        attr_value = getattr(arg, attr_name)
        if 'type' in kwargs:
            if kwargs['type'] == 'enum':
                enums.append(f'CONFIG_{define_name}')
                defines[f'CONFIG_{define_name}'] = f'HOMEKIT_{attr_value.upper()}'
                return
        defines[f'CONFIG_{define_name}'] = str(attr_value)
    bsd_walk(product_config, f)
    return defines, enums


if __name__ == '__main__':
    products = get_products()

    # first, get the product
    product_parser = ArgumentParser(add_help=False)
    product_parser.add_argument('--product', type=str, choices=products, required=True,
                                help='PIO product name')
    arg, _ = product_parser.parse_known_args()
    if not arg.product:
        product = os.path.basename(os.path.realpath(os.getcwd()))
        if product not in products:
            raise ArgumentError(None, 'invalid product')
    else:
        product = arg.product

    product_config = get_config(product)

    # then everythingm else
    parser = ArgumentParser(parents=[product_parser])
    parser.add_argument('--target', type=str, required=True, choices=product_config['targets'],
                        help='PIO build target')
    parser.add_argument('--platform', default='espressif8266', type=str)
    parser.add_argument('--framework', default='arduino', type=str)
    parser.add_argument('--upload-port', default='/dev/ttyUSB0', type=str)
    parser.add_argument('--monitor-speed', default=115200)
    parser.add_argument('--debug', action='store_true')
    parser.add_argument('--debug-network', action='store_true')
    bsd_parser(product_config, parser)
    arg = parser.parse_args()

    if arg.target not in product_config['targets']:
        raise ArgumentError(None, f'target {arg.target} not found for product {product}')

    bsd, bsd_enums = bsd_get(product_config, arg)
    ini = platformio_ini(product_config=product_config,
                         target=arg.target,
                         build_specific_defines=bsd,
                         build_specific_defines_enums=bsd_enums,
                         platform=arg.platform,
                         framework=arg.framework,
                         upload_port=arg.upload_port,
                         monitor_speed=arg.monitor_speed,
                         debug=arg.debug,
                         debug_network=arg.debug_network)
    print(ini)
