import argparse
import logging
import pathlib
import sys
from math import ceil

from tqdm import tqdm
import pandas as pd
import trio

from dstat_interface.core.tasks import ExperimentBaseTasks
from . import state
from .dstat.comm import SerialDevices, dstat_connect, read_settings
from .experiments.experiment_container import ExperimentContainer
from .experiments.lsv import LSVExperimentContainer, CVExperimentContainer
from .experiments.swv import SWVExperimentContainer, DPVExperimentContainer
from .dstat.state import DStatState

# Setup Logging
logger = logging.getLogger(__name__)

logging_choices = {'DEBUG': logging.DEBUG,
                   'INFO': logging.INFO,
                   'WARNING': logging.WARNING,
                   'ERROR': logging.ERROR}

experiments = [LSVExperimentContainer, CVExperimentContainer, SWVExperimentContainer, DPVExperimentContainer]

dstat_state = DStatState()


class CLIExperimentTasks(ExperimentBaseTasks):
    async def update_progress(self, cancel_scope):
        scan = 0
        while True:
            if scan == self.exp_con.progress_iters-1:  # Inner loop broken when already at last scan (iters 0-indexed)
                break
            scan = self.exp_con.progress_scan
            last_progress = 0
            with tqdm(total=100, unit='%', dynamic_ncols=True, desc=f'Scan {scan+1}',
                      bar_format='{l_bar}{bar}[{elapsed}]') as pbar:
                while True:
                    try:
                        progress = ceil(self.exp_con.get_progress())
                    except StopIteration:
                        pbar.update(100 - last_progress)
                        break
                    pbar.update(progress-last_progress)
                    last_progress = progress
                    if progress >= 100:
                        break
                    await trio.sleep(0.2)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--log-level', choices=logging_choices, default="INFO")
    parser.add_argument('--experiment-path', type=str, default=None)
    parser.add_argument('-s', '--simulator', action='store_true')
    experiment_subparsers = parser.add_subparsers(title="Experiments:", required=True)

    for exp in experiments:
        experiment = exp({})
        exp_parser = experiment_subparsers.add_parser(name=experiment.experiment_id)
        exp_parser.set_defaults(experiment=exp)
        arguments = experiment.param_input
        for i in arguments:
            kind = arguments[i]
            try:
                default = experiment.defaults[i]
            except KeyError:
                default = None
            try:
                doc = experiment.param_docs[i]
            except KeyError:
                doc = ''

            if kind is bool:
                exp_parser.add_argument(f'--{i}', action=f'store_{not experiment.defaults[i]}'.lower(), help=doc)
            elif isinstance(kind, list):
                if default is not None:
                    exp_parser.add_argument(f'--{i}', choices=kind, default=default, help=doc)
                else:
                    exp_parser.add_argument(i, metavar=i, choices=kind, help=doc)
            else:
                if default is not None:
                    exp_parser.add_argument(f'--{i}', type=kind, default=default, help=doc)
                else:
                    exp_parser.add_argument(i, type=kind, help=doc)

    args = parser.parse_args()

    if args.experiment_path is not None:
        state.experiment_folder_location = pathlib.Path(args.experiment_path).expanduser() / state.experiment_name

    # Make sure paths exist
    pathlib.Path(state.experiment_folder_location).mkdir(parents=True, exist_ok=True)
    pathlib.Path(state.app_dirs.user_config_dir).mkdir(parents=True, exist_ok=True)

    # Logging
    logfile = state.experiment_folder_location / f'{state.experiment_name}.log'
    root_logger = logging.getLogger()
    for handler in logging.root.handlers[:]:
        logging.root.removeHandler(handler)
    log_handlers = [logging.StreamHandler(), logging.FileHandler(logfile)]
    log_formatter = logging.Formatter(
        fmt='%(asctime)s %(levelname)s: [%(name)s] %(message)s',
        datefmt='%H:%M:%S',
    )

    for handler in log_handlers:
        handler.setFormatter(log_formatter)
        root_logger.addHandler(handler)
    root_logger.setLevel(logging_choices[args.log_level])

    if args.simulator:
        dstat_connect('simulator')
    else:
        ports = SerialDevices()
        dstat_connect(ports.ports[0])

    logger.info('DStat ver: %s Firmware ver: %s', dstat_state.dstat_version, dstat_state.firmware_version)
    read_settings()
    exp: ExperimentContainer = args.experiment(vars(args))  # Convert namespace to dict

    dstat_state.ser.start_exp(exp.get_proc())

    exp.start_handler(dstat_state.ser)
    tasks = CLIExperimentTasks(exp)

    trio.run(tasks.loop)
    data = pd.DataFrame(exp.handler_instance.data)
    data.iloc[1:].to_csv(state.experiment_folder_location / 'data.csv')
    dstat_state.ser.close()
    sys.exit()
