plt = None
try:
    import matplotlib.pyplot as plt
except:
    print('cannot load matplotlib')
import numpy as np

def get_sd_uid(data):

    return [i.uid for i in data.network.simple_dispatchable_device]

def get_num_t(data):

    return len(data.time_series_input.general.interval_duration)

def get_sd_cs(data):

    sd_cs = {
        i.uid:(i.device_type == 'consumer')
        for i in data.network.simple_dispatchable_device}
    return sd_cs

def get_sd_t_blocks(data):

    num_t = get_num_t(data)
    sd_cs = get_sd_cs(data)
    sd_t_p_max = get_sd_t_p_max(data)
    sd_t_blocks = {
        i.uid: [
            [{'sd': i.uid,
              'sd_cs': sd_cs[i.uid],
              't': t,
              'sd_t_p_max': sd_t_p_max[i.uid][t],
              'p_max': b[1],
              'c': b[0]}
             for b in i.cost[t]]
            for t in range(num_t)]
        for i in data.time_series_input.simple_dispatchable_device}
    return sd_t_blocks

def get_sd_t_p_max(data):

    num_t = get_num_t(data)
    sd_t_pmax = {
        i.uid: i.p_ub
        for i in data.time_series_input.simple_dispatchable_device}
    return sd_t_pmax

# def convert_sd_t_blocks_pr_to_cs(data, sd_t_blocks):

#     # just repeat cs-to-pr conversion
#     convert_sd_t_blocks_cs_to_pr(data, sd_t_blocks)

# def convert_sd_t_blocks_cs_to_pr(data, sd_t_blocks):

#     sd_uid = get_sd_uid(data)
#     num_t = get_num_t(data)
#     for j in sd_uid:
#         for t in range(num_t):
#             for b in sd_t_blocks[j][t]:
#                 if b['sd_cs']:
#                     b['c'] *= -1.0

def check_sd_t_blocks(sd_t_blocks):

    pass # todo check nonnegative p_max - ok as we do this elsewhere

def sort_sd_t_blocks_by_decreasing_margin(data, sd_t_blocks):
    '''
    i.e. pr blocks by increasing c, cs blocks by decreasing cu
    '''

    sd_uid = get_sd_uid(data)
    num_t = get_num_t(data)
    sd_cs = get_sd_cs(data)
    for j in sd_uid:
        if sd_cs[j]:
            for t in range(num_t):
                sd_t_blocks[j][t] = sorted(sd_t_blocks[j][t], key=(lambda x: -x['c']))
        else:
            for t in range(num_t):
                sd_t_blocks[j][t] = sorted(sd_t_blocks[j][t], key=(lambda x: x['c']))

def sort_t_blocks_by_increasing_c(data, t_blocks):

    num_t = get_num_t(data)
    for t in range(num_t):
        t_blocks[t] = sorted(t_blocks[t], key=(lambda x: x['c']))

def sort_t_blocks_by_decreasing_c(data, t_blocks):

    num_t = get_num_t(data)
    for t in range(num_t):
        t_blocks[t] = sorted(t_blocks[t], key=(lambda x: -x['c']))

def add_p_max_cumul_to_sd_t_blocks(data, sd_t_blocks):

    sd_uid = get_sd_uid(data)
    num_t = get_num_t(data)
    for j in sd_uid:
        for t in range(num_t):
            p_max = 0.0
            for b in sd_t_blocks[j][t]:
                p_max += b['p_max']
                b['p_max_cumul'] = p_max

def filter_sd_t_blocks_by_sd_t_pmax(data, sd_t_blocks):

    sd_uid = get_sd_uid(data)
    num_t = get_num_t(data)
    for j in sd_uid:
        for t in range(num_t):
            blocks = []
            for b in sd_t_blocks[j][t]:
                blocks.append(b)
                if b['p_max_cumul'] > b['sd_t_p_max']:
                    b['p_max'] -= (b['p_max_cumul'] - b['sd_t_p_max'])
                    b['p_max'] = max(0.0, b['p_max'])
                    b['p_max_cumul'] = b['sd_t_p_max']
                    break
            sd_t_blocks[j][t] = blocks

def flatten_sd_t_blocks_to_t_blocks(data, sd_t_blocks):

    sd_uid = get_sd_uid(data)
    num_t = get_num_t(data)
    t_blocks = [
        [b for j in sd_uid for b in sd_t_blocks[j][t]]
        for t in range(num_t)]
    return t_blocks

def get_t_blocks_pr(data, t_blocks):

    num_t = get_num_t(data)
    t_blocks_pr = [
        [b for b in t_blocks[t] if not b['sd_cs']]
        for t in range(num_t)]
    return t_blocks_pr

def get_t_blocks_cs(data, t_blocks):

    num_t = get_num_t(data)
    t_blocks_cs = [
        [b for b in t_blocks[t] if b['sd_cs']]
        for t in range(num_t)]
    return t_blocks_cs

def add_p_max_cumul_to_t_blocks(data, t_blocks):

    num_t = get_num_t(data)
    for t in range(num_t):
        p_max_cumul = np.cumsum([b['p_max'] for b in t_blocks[t]])
        for i in range(len(t_blocks[t])):
            t_blocks[t][i]['p_max_cumul'] = p_max_cumul[i]
        #p_max = 0.0
        #for b in t_blocks[t]:
        #    p_max += b['p_max']
        #    b['p_max_cumul'] = p_max

def plot_blocks_pr_cs_one_t(blocks_pr, blocks_cs, equilibrium, problem_file_name, t, file_name):

    x_pr = [b['p_max_cumul'] for b in blocks_pr for i in range(2)]
    x_pr = x_pr[0:-1]
    x_pr = [0.0] + x_pr
    y_pr = [b['c'] for b in blocks_pr for i in range(2)]
    x_cs = [b['p_max_cumul'] for b in blocks_cs for i in range(2)]
    x_cs = x_cs[0:-1]
    x_cs = [0.0] + x_cs
    y_cs = [b['c'] for b in blocks_cs for i in range(2)]

    info_str_format = '\n'.join(
        [
            #'problem: {}', 't: {}',
            'p (pu): {:.2e}', 'lambda ($/pu-h): {:.2e}',
            'surplus ($/h): {:.2e}', 'exchange ($/h): {:.2e}',
            'cost_pr ($/h): {:.2e}', 'value_cs ($/h): {:.2e}',
            'surplus_pr ($/h): {:.2e}', 'surplus_cs ($/h): {:.2e}'])
    info_str = info_str_format.format(
        #problem_file_name, t,
        equilibrium['p_med'], equilibrium['lambda_med'],
        equilibrium['surplus_total'], equilibrium['value_exchanged'],
        equilibrium['cost_pr'], equilibrium['value_cs'],
        equilibrium['surplus_pr'], equilibrium['value_cs'])

    fig,ax = plt.subplots()
    ax.plot(x_pr, y_pr, x_cs, y_cs)
    ax.set_yscale('log')
    ax.set_title('t = {}, {}'.format(t, problem_file_name))
    ax.text(0.5, 0.5, info_str, transform=plt.gcf().transFigure)
    ax.legend(['supply', 'demand'])
    ax.set_xlabel('p (pu)')
    ax.set_ylabel('lambda ($/pu-h)')
    plt.savefig(fname=file_name)

def compute_equilibrium_fixed_demand(block_p_max, block_c, p_demand):

    num_block = len(block_p_max)
    assert len(block_c) == num_block

    min_block_p_max = min([0.0] + block_p_max)
    assert min_block_p_max >= 0.0

    p_min_pr = 0.0
    p_max_pr = sum(block_p_max)
    if num_block == 0:
        lambda_min_pr = None
        lambda_max_pr = None
    else:
        lambda_min_pr = min(block_c)
        lambda_max_pr = max(block_c)

    if p_demand < p_min_pr:
        p_min = None
        p_max = None
        lambda_min = None
        lambda_max = None
    if p_demand > p_max_pr:
        p_min = None
        p_max = None
        lambda_min = None
        lambda_max = None
    if p_demand == p_min_pr and p_demand == p_max_pr:
        p_min = p_demand
        p_max = p_demand
        lambda_min = None
        lambda_max = None
    if p_demand == p_min_pr and p_demand < p_max_pr:
        p_min = p_demand
        p_max = p_demand
        lambda_min = None
        lambda_max = lambda_min_pr
    if p_demand > p_min_pr and p_demand == p_max_pr:
        p_min = p_demand
        p_max = p_demand
        lambda_min = lambda_max_pr
        lambda_max = None
    if p_min_pr < p_demand and p_demand < p_max_pr:
        p_min = p_demand
        p_max = p_demand
        block_indices_sorted = sorted(list(range(num_block)), key=(lambda x: block_c[x]))
        block_p_cumulative = np.cumsum([block_p_max[i] for i in block_indices_sorted])
        indices_below_demand = np.flatnonzero(np.less(block_p_cumulative, p_demand)).tolist()
        indices_at_demand = np.flatnonzero(np.equal(block_p_cumulative, p_demand)).tolist()
        indices_above_demand = np.flatnonzero(np.greater(block_p_cumulative, p_demand)).tolist()
        assert len(indices_above_demand) > 0
        if len(indices_at_demand) == 0:
            min_index_above_demand = min(indices_above_demand)
            lambda_index = min_index_above_demand
            lambda_block = block_indices_sorted[lambda_index]
            lambda_min = block_c[lambda_block]
            lambda_max = block_c[lambda_block]
        else:
            min_index_at_demand = min(indices_at_demand)
            max_index_at_demand = max(indices_at_demand)
            lambda_min_index = min_index_at_demand
            lambda_max_index = max_index_at_demand + 1
            lambda_min_block = block_indices_sorted[lambda_min_index]
            lambda_max_block = block_indices_sorted[lambda_max_index]
            lambda_min = block_c[lambda_min_block]
            lambda_max = block_c[lambda_max_block]
        #
        #marginal_index = min(np.flatnonzero(np.less_equal(p_demand, block_p_cumulative)).tolist())
        #marginal_block = block_indices_sorted[marginal_index]
        #lambda_min = block_c[marginal_block]
        #lambda_max = block_c[marginal_block]
        #

    if p_min is None:
        if p_max is None:
            p_med = None
        else:
            p_med = p_max
    else:
        if p_max is None:
            p_med = p_min
        else:
            p_med = 0.5 * (p_min + p_max)
    if lambda_min is None:
        if lambda_max is None:
            lambda_med = None
        else:
            lambda_med = lambda_max
    else:
        if lambda_max is None:
            lambda_med = lambda_min
        else:
            lambda_med = 0.5 * (lambda_min + lambda_max)

    equilibrium = {
        'fixed_demand': p_demand,
        'num_block': num_block,
        'min_block_p_max': min_block_p_max,
        'p_min_pr': p_min_pr,
        'p_max_pr': p_max_pr,
        'lambda_min_pr': lambda_min_pr,
        'lambda_max_pr': lambda_max_pr,
        'p_min': p_min,
        'p_max': p_max,
        'p_med': p_med,
        'lambda_min': lambda_min,
        'lambda_max': lambda_max,
        'lambda_med': lambda_med,
        }

    #print('fixed demand s-d equilibrium: {}'.format(equilibrium))

    return equilibrium

def compute_equilibrium_flexible_demand(pr_block_max_p, pr_block_c, cs_block_max_p, cs_block_c, fixed_demand):

    assert fixed_demand == 0.0

    num_pr_block = len(pr_block_max_p)
    num_cs_block = len(cs_block_max_p)
    assert len(pr_block_c) == num_pr_block
    assert len(cs_block_c) == num_cs_block

    pr_min_block_max_p = min([0.0] + pr_block_max_p)
    cs_min_block_max_p = min([0.0] + cs_block_max_p)
    # print('pr_block_max_p: {}'.format(pr_block_max_p))
    # print('pr_block_c: {}'.format(pr_block_c))
    # print('cs_block_max_p: {}'.format(cs_block_max_p))
    # print('cs_block_c: {}'.format(cs_block_c))
    assert pr_min_block_max_p >= 0.0
    assert cs_min_block_max_p >= 0.0

    #cs_block_c_minus = [-c for c in cs_block_c]
    fixed_demand_plus_flex_demand_max = fixed_demand + sum(cs_block_max_p)

    fixed_demand_equilibrium = compute_equilibrium_fixed_demand(
        block_p_max=(pr_block_max_p + cs_block_max_p),
        #block_c=(pr_block_c + cs_block_c_minus),
        block_c=(pr_block_c + cs_block_c),
        p_demand=fixed_demand_plus_flex_demand_max)

    lambda_min = fixed_demand_equilibrium['lambda_min']
    lambda_max = fixed_demand_equilibrium['lambda_max']

    p_min_pr = 0.0
    p_max_pr = sum(pr_block_max_p)
    p_min_cs = 0.0
    p_max_cs = sum(cs_block_max_p)

    if num_pr_block > 0:
        lambda_min_pr = min(pr_block_c)
        lambda_max_pr = max(pr_block_c)
    else:
        lambda_min_pr = None
        lambda_max_pr = None
    if num_cs_block > 0:
        lambda_min_cs = min(cs_block_c)
        lambda_max_cs = max(cs_block_c)
    else:
        lambda_min_cs = None
        lambda_max_cs = None

    p_min = 0.0
    p_max = 0.0
    # todo fill these in
    surplus_total = 0.0
    surplus_pr = 0.0
    surplus_cs = 0.0
    cost_pr = 0.0
    value_cs = 0.0
    if p_max_pr > 0.0:
        if p_max_cs > 0.0:
            #pr_block_indices_sorted = sorted(list(range(num_pr_block)), key=(lambda x: pr_block_c[x]))
            #pr_block_p_max_cumulative = np.cumsum([pr_block_p_max[i] for i in pr_block_indices_sorted])
            #cs_block_indices_sorted = sorted(list(range(num_cs_block)), key=(lambda x: -cs_block_c[x]))
            #cs_block_p_max_cumulative = np.cumsum([cs_block_p_max[i] for i in cs_block_indices_sorted])
            if lambda_min is None:
                if lambda_max is None:
                    pr_block_indices_in_money = []
                    pr_block_indices_out_of_money = []
                    pr_block_indices_marginal = list(range(num_pr_block))
                    cs_block_indices_in_money = []
                    cs_block_indices_out_of_money = []
                    cs_block_indices_marginal = list(range(num_cs_block))
                else:
                    pr_block_indices_in_money = []
                    pr_block_indices_out_of_money = np.flatnonzero(np.greater(pr_block_c, lambda_max)).tolist()
                    pr_block_indices_marginal = np.flatnonzero(np.less_equal(pr_block_c, lambda_max)).tolist()
                    cs_block_indices_in_money = np.flatnonzero(np.greater(cs_block_c, lambda_max)).tolist()
                    cs_block_indices_out_of_money = []
                    cs_block_indices_marginal = np.flatnonzero(np.less_equal(cs_block_c, lambda_max)).tolist()
            else:
                if lambda_max is None:
                    pr_block_indices_in_money = np.flatnonzero(np.less(pr_block_c, lambda_min)).tolist()
                    pr_block_indices_out_of_money = []
                    pr_block_indices_marginal = np.flatnonzero(np.greater_equal(pr_block_c, lambda_min)).tolist(),
                    cs_block_indices_in_money = []
                    cs_block_indices_out_of_money = np.flatnonzero(np.less(cs_block_c, lambda_min)).tolist()
                    cs_block_indices_marginal = np.flatnonzero(np.greater_equal(cs_block_c, lambda_min)).tolist()
                else:
                    pr_block_indices_in_money = np.flatnonzero(np.less(pr_block_c, lambda_min)).tolist()
                    pr_block_indices_out_of_money = np.flatnonzero(np.greater(pr_block_c, lambda_max)).tolist()
                    pr_block_indices_marginal = np.flatnonzero(
                        np.logical_and(
                            np.greater_equal(pr_block_c, lambda_min),
                            np.less_equal(pr_block_c, lambda_max))).tolist()
                    cs_block_indices_in_money = np.flatnonzero(np.greater(cs_block_c, lambda_max)).tolist()
                    cs_block_indices_out_of_money = np.flatnonzero(np.less(cs_block_c, lambda_min)).tolist()
                    cs_block_indices_marginal = np.flatnonzero(
                        np.logical_and(
                            np.greater_equal(cs_block_c, lambda_min),
                            np.less_equal(cs_block_c, lambda_max))).tolist()
            pr_p_max_in_money = sum(pr_block_max_p[i] for i in pr_block_indices_in_money)
            pr_p_max_out_of_money = sum(pr_block_max_p[i] for i in pr_block_indices_out_of_money)
            pr_p_max_marginal = sum(pr_block_max_p[i] for i in pr_block_indices_marginal)
            cs_p_max_in_money = sum(cs_block_max_p[i] for i in cs_block_indices_in_money)
            cs_p_max_out_of_money = sum(cs_block_max_p[i] for i in cs_block_indices_out_of_money)
            cs_p_max_marginal = sum(cs_block_max_p[i] for i in cs_block_indices_marginal)
            p_min = max(pr_p_max_in_money, cs_p_max_in_money)
            p_max = min(pr_p_max_in_money + pr_p_max_marginal, cs_p_max_in_money + cs_p_max_marginal)
            # todo
        else:
            pass # OK
    else:
        if p_max_cs > 0.0:
            pass # OK
        else:
            pass # OK
    # block_indices_sorted = sorted(list(range(num_block)), key=(lambda x: block_c[x]))
    # block_p_cumulative = np.cumsum([block_p_max[i] for i in block_indices_sorted])
    # marginal_index = min(np.flatnonzero(np.less_equal(p_demand, block_p_cumulative)).tolist())
    # marginal_block = block_indices_sorted[marginal_index]
    # lambda_min = block_c[marginal_block]
    # lambda_max = block_c[marginal_block]

    if p_min is None:
        if p_max is None:
            p_med = None
        else:
            p_med = p_max
    else:
        if p_max is None:
            p_med = p_min
        else:
            p_med = 0.5 * (p_min + p_max)
    if lambda_min is None:
        if lambda_max is None:
            lambda_med = None
        else:
            lambda_med = lambda_max
    else:
        if lambda_max is None:
            lambda_med = lambda_min
        else:
            lambda_med = 0.5 * (lambda_min + lambda_max)

    pr_block_indices_sorted = sorted(list(range(num_pr_block)), key=(lambda x: pr_block_c[x]))
    cs_block_indices_sorted = sorted(list(range(num_cs_block)), key=(lambda x: -cs_block_c[x]))
    pr_block_p_cumulative = np.cumsum([pr_block_max_p[i] for i in pr_block_indices_sorted])
    cs_block_p_cumulative = np.cumsum([cs_block_max_p[i] for i in cs_block_indices_sorted])
    pr_block_indices_in_money = np.flatnonzero(np.less_equal(pr_block_p_cumulative, p_med)).tolist()
    cs_block_indices_in_money = np.flatnonzero(np.less_equal(cs_block_p_cumulative, p_med)).tolist()
    num_pr_blocks_in_money = len(pr_block_indices_in_money)
    num_cs_blocks_in_money = len(cs_block_indices_in_money)
    cost_pr = sum(
        [pr_block_c[pr_block_indices_sorted[i]] * pr_block_max_p[pr_block_indices_sorted[i]]
         for i in range(num_pr_blocks_in_money)])
    value_cs = sum(
        [cs_block_c[cs_block_indices_sorted[i]] * cs_block_max_p[cs_block_indices_sorted[i]]
         for i in range(num_cs_blocks_in_money)])
    if num_pr_blocks_in_money < num_pr_block:
        p_start = 0.0
        if num_pr_blocks_in_money > 0:
            p_start = pr_block_p_cumulative[num_pr_blocks_in_money - 1]
        p_marginal = p_med - p_start
        c_marginal = pr_block_c[pr_block_indices_sorted[num_pr_blocks_in_money]]
        cost_pr += c_marginal * p_marginal
    if num_cs_blocks_in_money < num_cs_block:
        p_start = 0.0
        if num_cs_blocks_in_money > 0:
            p_start = cs_block_p_cumulative[num_cs_blocks_in_money - 1]
        p_marginal = p_med - p_start
        c_marginal = cs_block_c[cs_block_indices_sorted[num_cs_blocks_in_money]]
        value_cs += c_marginal * p_marginal
    value_exchanged = lambda_med * p_med
    surplus_total = value_cs - cost_pr
    surplus_pr = value_exchanged - cost_pr
    surplus_cs = value_cs - value_exchanged

    equilibrium = {
        'fixed_demand': fixed_demand,
        'num_pr_block': num_pr_block,
        'num_cs_block': num_cs_block,
        'p_min_pr': p_min_pr,
        'p_max_pr': p_max_pr,
        'p_min_cs': p_min_cs,
        'p_max_cs': p_max_cs,
        'lambda_min_pr': lambda_min_pr,
        'lambda_max_pr': lambda_max_pr,
        'lambda_min_cs': lambda_min_cs,
        'lambda_max_cs': lambda_max_cs,
        'p_min': p_min,
        'p_max': p_max,
        'p_med': p_med,
        'lambda_min': lambda_min,
        'lambda_max': lambda_max,
        'lambda_med': lambda_med,
        'surplus_total': surplus_total,
        'value_exchanged': value_exchanged,
        'surplus_pr': surplus_pr,
        'surplus_cs': surplus_cs,
        'cost_pr': cost_pr,
        'value_cs': value_cs,
        }

    #print('flexible demand s-d equilibrium: {}'.format(equilibrium))

    return equilibrium

def analyze_supply_demand(data, do_plots=False, problem_file_name=None):

    num_t = get_num_t(data)
    sd_t_blocks = get_sd_t_blocks(data)
    check_sd_t_blocks(sd_t_blocks)
    #print('sd_t_blocks: {}'.format(sd_t_blocks))
    #convert_sd_t_blocks_cs_to_pr(data, sd_t_blocks)
    #print('sd_t_blocks: {}'.format(sd_t_blocks))
    sort_sd_t_blocks_by_decreasing_margin(data, sd_t_blocks)
    #print('sd_t_blocks: {}'.format(sd_t_blocks))
    add_p_max_cumul_to_sd_t_blocks(data, sd_t_blocks)
    #print('sd_t_blocks: {}'.format(sd_t_blocks))
    filter_sd_t_blocks_by_sd_t_pmax(data, sd_t_blocks)
    #print('sd_t_blocks: {}'.format(sd_t_blocks))
    #convert_sd_t_blocks_pr_to_cs(data, sd_t_blocks)
    #print('sd_t_blocks: {}'.format(sd_t_blocks))
    t_blocks = flatten_sd_t_blocks_to_t_blocks(data, sd_t_blocks)
    t_blocks_pr = get_t_blocks_pr(data, t_blocks)
    t_blocks_cs = get_t_blocks_cs(data, t_blocks)
    t_equilibrium = [
        compute_equilibrium_flexible_demand(
            pr_block_max_p=[b['p_max'] for b in t_blocks_pr[t]],
            pr_block_c=[b['c'] for b in t_blocks_pr[t]],
            cs_block_max_p=[b['p_max'] for b in t_blocks_cs[t]],
            cs_block_c=[b['c'] for b in t_blocks_cs[t]],
            fixed_demand=0.0)
        for t in range(num_t)]
    for t in range(num_t):
        print('t: {}'.format(t))
        print('equilibrium: {}'.format(t_equilibrium[t]))
    sort_t_blocks_by_increasing_c(data, t_blocks_pr)
    sort_t_blocks_by_decreasing_c(data, t_blocks_cs)
    add_p_max_cumul_to_t_blocks(data, t_blocks_pr)
    add_p_max_cumul_to_t_blocks(data, t_blocks_cs)
    #do_plots = True
    if do_plots:
        if plt is None:
            print('cannot do plots due to failure to import matplotlib')
        else:
            for t in range(num_t):
                plot_blocks_pr_cs_one_t(
                    t_blocks_pr[t],
                    t_blocks_cs[t],
                    t_equilibrium[t],
                    problem_file_name=problem_file_name,
                    t=t,
                    file_name='supply_demand_t_{}.pdf'.format(t))
    t_duration = data.time_series_input.general.interval_duration
    surplus_total = sum([t_duration[t] * t_equilibrium[t]['surplus_total'] for t in range(num_t)])
    value_exchanged = sum([t_duration[t] * t_equilibrium[t]['value_exchanged'] for t in range(num_t)])
    surplus_pr = sum([t_duration[t] * t_equilibrium[t]['surplus_pr'] for t in range(num_t)])
    surplus_cs = sum([t_duration[t] * t_equilibrium[t]['surplus_cs'] for t in range(num_t)])
    cost_pr = sum([t_duration[t] * t_equilibrium[t]['cost_pr'] for t in range(num_t)])
    value_cs = sum([t_duration[t] * t_equilibrium[t]['value_cs'] for t in range(num_t)])
    output = {
        't_equilibrium': t_equilibrium,
        'value_exchanged': value_exchanged,
        'surplus_total': surplus_total,
        'surplus_pr': surplus_pr,
        'surplus_cs': surplus_cs,
        'cost_pr': cost_pr,
        'value_cs': value_cs,
        }
    return output


#!/usr/bin/env python3
# coding: utf-8

#-:-:-:-:-:-:-:-:-:-:-:-:#
#    TIDoS Framework     #
#-:-:-:-:-:-:-:-:-:-:-:-:#

#This module requires TIDoS Framework
#https://github.com/0xInfection/TIDoS-Framework

import nmap
import time
from core.Core.colors import *
import socket
from core.database.database_module import save_data
from core.variables import database
from core.methods.cache import targetname
import inspect

info = "This module discovers possible Telnet misconfiguration."
searchinfo = "Telnet Checker"
properties = {}

def netmisc0x00(web):

    web = web.replace('http://','')
    web = web.replace('https://','')
    ip = socket.gethostbyname(web)
    port = 23 # telnet port
    try:
        print(O+' [!] Configuring scanner for telnet ports...')
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(5)
        print(C+"\n [*] Connecting to '%s' via port %s" % (ip, port))
        r = sock.connect_ex((ip, port))
        time.sleep(0.1)
        if r == 0:
            print(G+' [+] Telnet port detected open!')
            time.sleep(0.5)
            print(O+' [*] Confirming...')
            nmScan = nmap.PortScanner()
            nmScan.scan(ip, '23')
            port = 23
            thisDict = nmScan[ip]['tcp'][port]
            print(G+' [+] Telnet network misconfiguration confirmed!')
            save_data(database, module, lvl1, lvl2, lvl3, name, "Telnet misconfiguration confirmed!")
            time.sleep(0.5)
            if thisDict['version']:
                print(G+' [+] Port ' + str(port) + ': ' +C+ thisDict['product'] +GR+ ', v' + thisDict['version'])
                data = "Port " + str(port) + ": " + thisDict["product"] + ", v" + thisDict["version"]
                save_data(database, module, lvl1, lvl2, lvl3, name, data)
            else:
                print(G+' [+] Port ' + str(port) + ': ' +C+ thisDict['product'])
                data = "Port " + str(port) + ": " + thisDict["product"]
                save_data(database, module, lvl1, lvl2, lvl3, name, data)
        else:
            print(R+' [-] Telnet Disabled!')
            save_data(database, module, lvl1, lvl2, lvl3, name, "Telnet disabled.")
        sock.close()

    except Exception as e:
        print('\n'+R+' [!] Exception detected at port %s !' % port)
        print(R+' [-] Error : '+str(e))

def netmisc(web):
    global name
    name = targetname(web)
    global lvl2
    lvl2 = inspect.stack()[0][3]
    global module
    module = "VulnAnalysis"
    global lvl1
    lvl1 = "Basic Bugs & Misconfigurations"
    global lvl3
    lvl3 = ""
    #print(R+'\n    ===================================')
    #print(R+'\n     TELNET ENABLED (Network Misconf.)')
    #print(R+'    ---<>----<>----<>----<>----<>----<>\n')

    from core.methods.print import pvln
    pvln("network misconf.")

    print(GR+' [*] Loading up port scanner...')
    netmisc0x00(web)
    time.sleep(0.5)

def attack(web):
    web = web.fullurl
    netmisc(web)


"""
Given an array nums of distinct integers, return all the possible permutations. You can return the answer in any order.

Input: nums = [1,2,3]
Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

"""
from typing import List
def permute(nums: List[int]) -> List[List[int]]:

    def permutations(arr):
        if len(arr) == 0:
            return []

        if len(arr) == 1:
            return [arr]

        possible_perms = []

        for a in range(len(arr)):
            #Ref first elem
            first = arr[a]

            #Extract the other elems
            splits = arr[:a] + arr[a+1:]

            for p in permutations(splits):
                possible_perms.append([first]+p)
        return possible_perms

    all_perms = permutations(nums)
    return all_perms

Case1 = [1,2,3]
print(permute(Case1))

"""
Time Complexity: O(N)
Space: O(N)

"""


import discord
from discord.ext import commands
from discord import app_commands
import os
from dotenv import load_dotenv
import asyncio

bot = commands.Bot(command_prefix="/", intents=discord.Intents.all())

print("Lancement du bot...")
bot.remove_command("help")

@bot.event
async def on_ready():
    print("Bot lancé!")
    await bot.change_presence(status=discord.Status.online)
    try:
        synced = await bot.tree.sync()
        print(f"Synced {len(synced)} command(s)")
    except Exception as e:
        print(e)

@bot.tree.command(name="help")
async def help(interaction: discord.Interaction):
    await interaction.response.send_message("Voici la commande help.", ephemeral=True)
    user = interaction.user
    embed = discord.Embed(title = "Help", description = f"{user.mention}, voici la liste des commands slash :\n> hello\n> ping\n> test_button\n> fruts_dropdown", color = 0xffffff)
    embed.set_footer(text = "Template Bot")
    await interaction.channel.send(embed=embed)

@bot.tree.command(name="hello")
async def hello(interaction: discord.Interaction):
    await interaction.response.send_message("Heyyy", ephemeral=True)

@bot.tree.command(name="ping")
async def ping(interaction: discord.Interaction):
    await interaction.response.send_message(f"Pong !\n> **{round(bot.latency *1000)}** ms", ephemeral=True)

class TestButton(discord.ui.View):
    def __init__(self):
        super().__init__()

    @discord.ui.button(label="📌 Test", style=discord.ButtonStyle.red)
    async def testBtn(self, interaction: discord.Interaction, button: discord.ui.Button):
        await interaction.response.send_message("Vous avez appuyez !", ephemeral=True)

@bot.tree.command(name="test_button")
async def fruts_dropdown(interaction: discord.Interaction):
    await interaction.response.send_message("Boutton créé !", ephemeral=True)
    await interaction.channel.send("Test boutton 📌", view=TestButton())

class frutsDropdown(discord.ui.Select):
    def __init__(self):
        options = [
            discord.SelectOption(label="🍎 Pomme", description='Tu es team pomme ?'),
            discord.SelectOption(label="🍓 Fraise", description='Tu es team fraise ?'),
            discord.SelectOption(label="🍌 Banane", description='Tu es team banane ?'),
        ]

        super().__init__(placeholder="🍍 Choisi ton fruit !", options=options, min_values=1, max_values=1)

    async def callback(self, interaction: discord.Interaction):
        await interaction.response.send_message(f"Vous avez choisi la team {self.values[0]} !", ephemeral=True)

class frutsView(discord.ui.View):
    def __init__(self):
        super().__init__()
        self.add_item(frutsDropdown())

@bot.tree.command(name="fruts_dropdown")
async def fruts_dropdown(interaction: discord.Interaction):
    await interaction.response.send_message("Dropdown créé !", ephemeral=True)
    await interaction.channel.send("Choisi ton fruit 🍍 :", view=frutsView())

load_dotenv()
TOKEN = os.getenv("DISCORD_TOKEN")
bot.run(TOKEN)


import subprocess
import os

def convert_epub_to_pdf(epub_path):
    pdf_path = os.path.splitext(epub_path)[0] + ".pdf"
    cmd = ['ebook-convert', epub_path, pdf_path]

    try:
        subprocess.run(cmd, check=True)
        print(f"Converted {epub_path} to {pdf_path}")
    except subprocess.CalledProcessError as e:
        print(f"Conversion failed with error:\n{e}")

# example usage:
epub_path = input("Please enter the path to your .epub file: ")
convert_epub_to_pdf(epub_path)


import math

import cv2
from absl import logging

from src.helper.image_helper import ImageHelper

class Preprocessor(object):
    def __init__(self, flags):
        self.__flags = flags
        self.filter_strength = 15
        self.canny_lower_threshold = 50

    def run(self, image):
        if image is None:
            logging.warning("Image for preprocessor is not defined")
            return

        image = self.__remove_noise(image, "02_noise_removal")
        image = self.__align_image(image, "03_aligning")
        image = self.__binarize_image(image, "04_binarization")
        return image

    def __remove_noise(self, image, title: str):
        logging.debug("Remove noise for image")
        cv2.fastNlMeansDenoising(image, image, self.filter_strength)
        return ImageHelper.write_image(image, title, self.__flags)

    def __align_image(self, image, title: str):
        logging.debug("Align image")
        angle = self.__compute_angle(image.copy())
        logging.debug("Rotating image by {} degrees".format(angle))

        if not math.isclose(angle, 0.0):
            image = self.__deskew(image.copy(), angle)
        return ImageHelper.write_image(image, title, self.__flags)

    def __compute_angle(self, image) -> float:
        height, width = image.shape

        edges = cv2.Canny(image, self.canny_lower_threshold, self.canny_lower_threshold * 3)
        lines = cv2.HoughLinesP(edges, 1, math.pi / 180, 200, minLineLength=width / 8, maxLineGap=width / 128)

        angle = 0.0
        if lines.any():
            for points in lines:
                x1, y1, x2, y2 = points[0]
                angle += math.atan2(y2 - y1, x2 - x1)
        return angle * 180 / math.pi

    def __deskew(self, image, angle: float):
        non_zero_pixels = cv2.findNonZero(image)
        center, wh, theta = cv2.minAreaRect(non_zero_pixels)

        root_mat = cv2.getRotationMatrix2D(center, angle, 1)
        rows, cols = image.shape
        rotated = cv2.warpAffine(image, root_mat, (cols, rows), cv2.INTER_CUBIC, borderValue=(255, 255, 255))
        return cv2.getRectSubPix(rotated, (cols, rows), center)

    def __binarize_image(self, image, title: str):
        logging.debug("Binarize image")
        _, image = cv2.threshold(image, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
        return ImageHelper.write_image(image, title, self.__flags)


import sys
sys.stdin = open("baek1926.txt")
from collections import deque

def bfs(start_row, start_col):

    cnt = 1
    visited[start_row][start_col] = True
    que = deque()
    que.append((start_row, start_col))

    while que:
        row, col = que.popleft()

        for d in range(0, 4):
            next_row = row + dr[d]
            next_col = col + dc[d]

            if next_row < 0 or next_col < 0 or next_row >= N or next_col >= M:
                continue
            if visited[next_row][next_col]:
                continue
            if MAP[next_row][next_col]:
                cnt += 1
                visited[next_row][next_col] = True
                que.append((next_row, next_col))
    return cnt

dr = [-1, 1, 0, 0]
dc = [0, 0, -1, 1]
N, M = map(int, input().split())
MAP = [0] * N
visited = [[False] * M for _ in range(0, N)]
area_cnt = 0
max_area = 0
for i in range(0, N):
    MAP[i] = list(map(int, input().split()))

for i in range(0, N):
    for j in range(0, M):
        if not visited[i][j] and MAP[i][j]:
            now = bfs(i, j)
            area_cnt += 1
            max_area = max(max_area, now)
print(area_cnt)
print(max_area)