#!/usr/bin/env python
from pprint import pprint, pformat
from time import perf_counter
import progressbar
import traceback
import click
import pandas as pd
from tabulate import tabulate

from negmas.apps.scml import *
from negmas.helpers import humanize_time


@click.command()
@click.option('--steps', default=60, help='Number of steps.')
@click.option('--levels', default=2, help='Number of intermediate production levels (processes). '
                                          '-1 means a single product and no factories.')
@click.option('--neg-speedup', default=21, help='Negotiation Speedup.')
@click.option('--negotiator', default='negmas.sao.AspirationNegotiator',
              help='Negotiator type to use for builtin agents.')
@click.option('--consumption', default=4, help='The number of units consumed by each consumer at every time-step.')
@click.option('--agents-per-level', default=4, help='Number of agents (miners/negmas.consumers) per production level')
@click.option('--horizon', default=10, help='Consumption horizon.')
@click.option('--transport', default=0, help='Transportation Delay.')
@click.option('--time', default=None, help='Total time limit.')
@click.option('--neg-time', default=None, help='Time limit per single negotiation')
@click.option('--neg-steps', default=20, help='Number of rounds per single negotiation')
@click.option('--sign', default=1, help='The default delay between contract conclusion and signing')
@click.option('--guaranteed', default=False
              , help='Whether to only sign contracts that are guaranteed not to cause breaches')
@click.option('--lines', default=10, help='The number of lines per factory')
@click.option('--retrials', default=1, help='The number of times an agent re-tries on failed negotiations')
@click.option('--use-consumer', default=True, help='Use internal consumer object in factory negmas')
@click.option('--insurance', default=-1, help='Use insurance against partner in factory negmas')
def cli(steps, levels, neg_speedup, negotiator, agents_per_level, horizon, consumption, transport, time, neg_time
        , neg_steps, sign, guaranteed, lines, retrials, use_consumer, insurance):
    neg_speedup = neg_speedup if neg_speedup is not None and neg_speedup > 0 else None
    world = SCMLWorld.single_path_world(log_file_name='', n_steps=steps
                                        , negotiation_speed=neg_speedup
                                        , n_intermediate_levels=levels
                                        , n_miners=agents_per_level
                                        , n_consumers=agents_per_level
                                        , n_factories_per_level=agents_per_level
                                        , consumption=consumption
                                        , consumer_kwargs={'negotiator_type': negotiator
                                                           , 'consumption_horizon': horizon}
                                        , miner_kwargs={'negotiator_type': negotiator, 'n_retrials': retrials}
                                        , factory_kwargs={'negotiator_type': negotiator, 'n_retrials': retrials
                                                          , 'sign_only_guaranteed_contracts': guaranteed
                                                          , 'use_consumer': use_consumer
                                                          , 'max_insurance_premium': None if insurance < 0 else insurance}
                                        , transportation_delay=transport, time_limit=time, neg_time_limit=neg_time
                                        , neg_n_steps=neg_steps, default_signing_delay=sign
                                        , n_lines_per_factory=lines)
    failed = False
    strt = perf_counter()
    try:
        for i in progressbar.progressbar(range(world.n_steps), max_value=world.n_steps):
            elapsed = perf_counter() - strt
            if world.time_limit is not None and elapsed >= world.time_limit:
                break
            if not world.step():
                break
    except Exception:
        exception = traceback.format_exc()
        failed = True
    elapsed = perf_counter() - strt

    def print_and_log(s):
        world.logdebug(s)
        print(s)

    pprint(world.stats, compact=True)
    world.logdebug(f'{pformat(world.stats, compact=True)}')
    world.logdebug(f'=================================================\n'
                   f'steps: {steps}, horizon: {horizon}, time: {time}, levels: {levels}, agents_per_level: '
                   f'{agents_per_level}, lines: {lines}, guaranteed: {guaranteed}, negotiator: {negotiator}\n'
                   f'consumption: {consumption}'
                   f', transport: {transport}, sign: {sign}, speedup: {neg_speedup}, neg_steps: {neg_steps}'
                   f', retrials: {retrials}'
                   f', neg_time: {neg_time}\n'
                   f'==================================================')

    if failed:
        print(exception)

    if len(world.saved_contracts) > 0:
        data = pd.DataFrame(world.saved_contracts)
        data = data.loc[:, ['seller_name', 'buyer_name', 'delivery_time', 'unit_price', 'quantity', 'product_name'
                             , 'n_neg_steps', 'concluded_at', 'cfp']]
        data = data.sort_values(['delivery_time'])
        print_and_log(tabulate(data, headers='keys', tablefmt='psql'))
        n_signed, n_breaches = sum(world.stats['n_contracts_signed']), sum(world.stats['n_breaches'])
        n_executed = sum(world.stats['n_contracts_executed'])
        n_negs = sum(world.stats["n_negotiations"])
        n_contracts = len(world.saved_contracts)
        print_and_log(f'{n_contracts} contracts :-) [N. Negotiations: {n_negs}'
                      f', Agreement Rate: {n_contracts / n_negs if n_negs != 0 else 0: 0.0%}]')
        print_and_log(f'Executed: {n_executed / n_signed:0.0%}'
                      f', Breached: {n_breaches / n_signed:0.0%}'
                      f', N. Executed: {n_executed}'
                      f', Business size: {sum(world.stats["activity_level"])}\n'
                      f'Winners: {[(_.name, _.factory.state.balance) for _ in world.winners]}'
                      f'Running Time {humanize_time(elapsed)}')
    else:
        print_and_log('No contracts! :-(')
        print_and_log(f'Running Time {humanize_time(elapsed)}')

    if failed:
        print(f'FAILED at step {world.current_step} of {world.n_steps}\n')


if __name__ == '__main__':
    cli()
