from Poloniex import *
import json
import datetime as dt
import pandas as pd
import os
from statsmodels.tsa.arima_model import ARIMA
import warnings

if __name__ == "__main__":
    # CONFIGURABLE SETTINGS
    arima_order = (5,1,0)
    training_data_days = 98

    # DEFAULT SETTINGS
    warnings.filterwarnings('ignore', 'statsmodels.tsa.arima_model.ARMA',
                        FutureWarning)
    warnings.filterwarnings('ignore', 'statsmodels.tsa.arima_model.ARIMA',
                        FutureWarning)
    with open('APISettings.json','r') as f:
        config = json.load(f)
    API_Secret = config["API_Secret"]
    API_Key = config["API_Key"]
    interval = config["Interval"]
    ticker = config["Ticker"]
    Polo = Poloniex(API_Key,API_Secret)
    start_day = dt.datetime(2019,8,1)
    last_day = start_day + dt.timedelta(days=training_data_days)
    training_dates = list()

    # GENERATE ALL INTERVALS BETWEEN THE PERIOD
    start_day_string = dt.datetime.strftime(start_day, "%Y-%m-%d %H:%M:%S")
    last_day_string = dt.datetime.strftime(last_day, "%Y-%m-%d %H:%M:%S")
    while True:
        if start_day >= last_day:
            print(f"Loaded all {len(training_dates)} intervals between {start_day_string} and {last_day_string}")
            break
        string_datetime = dt.datetime.strftime(start_day, "%Y-%m-%d %H:%M:%S")
        training_dates.append(string_datetime)
        start_day = start_day + dt.timedelta(seconds=interval)

    if not os.path.exists("Backtesting"):
        os.mkdir("Backtesting")
    if not os.path.exists(f"Backtesting\\{ticker}_{interval}_trade_log_arima_order_{str(arima_order)}_training_data_days_{str(training_data_days)}.json"):
        with open(f"Backtesting\\{ticker}_{interval}_trade_log_arima_order_{str(arima_order)}_training_data_days_{str(training_data_days)}.json","w") as f:
            json.dump({},f)
    if not os.path.exists(f"Backtesting\\{ticker}_{interval}_price_log.json"):
        print("Loading full DataFrame.")
        df = Polo.auto_create_df(ticker,interval,full_df=True)
        df.drop(["high","low","open","volume","quoteVolume","weightedAverage"],axis=1,inplace=True)
        json_string = df.to_json(orient="index")
        new_json_data = json.loads(json_string)
        with open(f"Backtesting\\{ticker}_{interval}_price_log.json","w")as f:
            json.dump(new_json_data,f,indent=2,sort_keys=True)
    with open(f"Backtesting\\{ticker}_{interval}_price_log.json","r") as f:
        all_data = json.load(f)
    # GET FINAL DATE FROM ALL DATA TO STOP
    final_date_string = list(all_data.keys())[-1]
    final_date = dt.datetime.strptime(final_date_string, "%Y-%m-%d %H:%M:%S")
    # LOAD BACKTESTING DATA
    backtesting_data = {}
    for key in sorted(training_dates):
        if key in all_data:
            backtesting_data[key] = all_data[key]
    print(f"Loaded initial backtesting dataframe of {len(backtesting_data)} values.")

    while True:
        # OPEN TRADE LOG
        with open(f"Backtesting\\{ticker}_{interval}_trade_log_arima_order_{str(arima_order)}_training_data_days_{str(training_data_days)}.json","r") as f:
            trade_log = json.load(f)

        # CREATE DF FROM BACKTESTING DATA DICT
        df = pd.DataFrame.from_dict(backtesting_data, orient="index")
        df.index.name = "period"
        df.index = df.index.astype(str)
        x = df["close"].values
        # GET PREVIOUS CLOSE FOR HIGHER/LOWER CHECKS
        previous_close = df.tail(2).head(1)['close'].item()
        current_interval = dt.datetime.strptime(df.tail(1).index.item(), "%Y-%m-%d %H:%M:%S")
        next_interval =  current_interval + dt.timedelta(seconds=interval)
        next_interval_string = dt.datetime.strftime(next_interval, "%Y-%m-%d %H:%M:%S")
        training_dates.append(next_interval_string)

        # TRAIN THE MODEL
        model = ARIMA(x, order=arima_order)
        model_fit = model.fit(disp=0)
        output = model_fit.forecast()
        result = output[0][0]
        # LOG PREDICTIONS BASED ON CURRENT PRICE
        if result > previous_close:
            direction = "Higher"
            difference = result - previous_close
        else:
            direction = "Lower"
            difference = previous_close - result

        # ALL THE TRADING LOGIC HERE BASED ON DIRECTION AND IF THERE ARE ANY OPEN TRADES OF THAT TICKER
        if difference >= 5:
            took_trade = True
        else:
            took_trade = False

         # UPDATE JSON DICT WITH NEW PREDICTION DATA AND DUMP IT
        trade_log[dt.datetime.strftime(current_interval,"%Y-%m-%d %H:%M:%S")] = {"close":None,"prediction":result,"predicted_direction_from_current":direction,"previous_close":previous_close,"correct_prediction":None,"took_trade":took_trade}

        # ADD NEXT INTERVAL DATA TO BACKTESTING DATA
        first_key = list(backtesting_data.keys())[0]
        current_interval_string = dt.datetime.strftime(current_interval, "%Y-%m-%d %H:%M:%S")
        print(f"Backtested data using {first_key} to {current_interval_string}.")
        if next_interval_string in all_data:
            backtesting_data[next_interval_string] = all_data[next_interval_string]
        # DELETE OLDEST VAL
        del backtesting_data[first_key]

        # UPDATE CLOSE PRICES
        update_trade_log = list()
        for date in trade_log:
            if date in all_data:
                if all_data[date]["close"] != trade_log[date]["close"]:
                    trade_log[date]["close"] = all_data[date]["close"]
                    update_trade_log.append(date)
        # UPDATE TRADE LOG DATA NOW WE HAVE CLOSE
        for date in update_trade_log:
            if trade_log[date]["correct_prediction"] is not None:
                continue
            if trade_log[date]["predicted_direction_from_current"] == "Lower":
                if type(trade_log[date]["close"]) is not float:
                    continue
                elif type(trade_log[date]["previous_close"]) is not float:
                    continue
                if trade_log[date]["previous_close"] > trade_log[date]["close"]:
                    trade_log[date]["correct_prediction"] = True
                else:
                    trade_log[date]["correct_prediction"] = False
            else:
                if type(trade_log[date]["close"]) is not float:
                    continue
                elif type(trade_log[date]["previous_close"]) is not float:
                    continue
                if trade_log[date]["previous_close"] < trade_log[date]["close"]:
                    trade_log[date]["correct_prediction"] = True
                else:
                    trade_log[date]["correct_prediction"] = False

        with open(f"Backtesting\\{ticker}_{interval}_trade_log_arima_order_{str(arima_order)}_training_data_days_{str(training_data_days)}.json","w")as f:
            json.dump(trade_log,f,indent=2,sort_keys=True)

        if next_interval > final_date:
            print("Reached the end of backtesting, stopping now.")
            break

    total_predictions = list()
    correct_predictions = list()
    trades_taken = list()
    correct_trades_taken = list()
    could_have_taken = list()
    could_have_taken_correct = list()
    with open(f"Backtesting\\{ticker}_{interval}_trade_log_arima_order_{str(arima_order)}_training_data_days_{str(training_data_days)}.json","r") as f:
        data = json.load(f)

    for period in data:
        if data[period]["correct_prediction"] is None:
            continue

        total_predictions.append(1)

        if data[period]["correct_prediction"]:
            correct_predictions.append(1)

        if data[period]["took_trade"]:
            trades_taken.append(1)
            if data[period]["correct_prediction"]:
                correct_trades_taken.append(1)

        if data[period]["predicted_direction_from_current"] == "Higher":
            if (data[period]["prediction"] - data[period]["previous_close"]) > 5:
                could_have_taken.append(1)
                if (data[period]["close"] - data[period]["previous_close"]) > 5:
                    could_have_taken_correct.append(1)
        else:
            if (data[period]["previous_close"] - data[period]["prediction"]) > 5:
                could_have_taken.append(1)
                if (data[period]["previous_close"] - data[period]["close"]) > 5:
                    could_have_taken_correct.append(1)

    if len(total_predictions) > 0:
        prediction_percentage = len(correct_predictions)/len(total_predictions)*100
    else:
        prediction_percentage = 0

    if len(trades_taken) > 0:
        taken_percentage = len(correct_trades_taken)/len(trades_taken)*100
    else:
        taken_percentage = 0
    profit_percentage = len(could_have_taken_correct)/len(could_have_taken)*100
    print(f"Total number of correct predictions {len(correct_predictions)}/{len(total_predictions)} This is an overall accuracy of {prediction_percentage}%\nOut of this amount {len(trades_taken)} were taken and {len(correct_trades_taken)} of those were correct, this is an actual accuracy of {taken_percentage}%.\nOut of {len(total_predictions)} predictions, {len(could_have_taken)} trades could have been taken.\nOut of that amount, {len(could_have_taken_correct)} would have been profitable.\nThat is a possible profitability percentage of {profit_percentage}%")


import time
import random
import asyncio
from typing import Any, Callable, List

from .worker import FunctionQueueWorker

class _WorkerTask():
    def __init__(self):
        self.result = None
        self.exception = None
        self.done = False

    def on_finish(self, result: Any):
        self.result = result
        self.done = True

    def on_exception(self, e: Exception):
        self.exception = e
        self.done = True

class Executor():
    def __init__(self, worker: FunctionQueueWorker, *args, **kwargs):
        self.__worker = worker

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        pass

    async def __aenter__(self):
        return self

    async def __aexit__(self, type, value, traceback):
        pass

    @property
    def worker(self):
        return self.__worker

    def run_method_in_queue(self,
                            func: Callable,
                            *args,
                            on_finish: Callable[[Any], None] = None,
                            on_exception: Callable[[Exception], None] = None,
                            **kwargs) -> None:
        """Run the given functions. It does not block the calling thread."""
        self.__worker.run_method(
            func, *args, on_finish=on_finish, on_exception=on_exception, **kwargs)

    def run_method(self,
                   func: Callable,
                   *args,
                   **kwargs) -> Any:
        """Run the given function. It blocks the calling thread."""
        task = _WorkerTask()
        self.__worker.run_method(
            func, *args, on_finish=task.on_finish, on_exception=task.on_exception, **kwargs)

        while not task.done:
            time.sleep(0)

        if task.exception:
            raise task.exception from task.exception
        return task.result

    async def run_method_async(self,
                               func: Callable,
                               *args,
                               **kwargs) -> Any:
        """Run the given function asynchronously."""
        task = _WorkerTask()
        self.__worker.run_method(
            func, *args, on_finish=task.on_finish, on_exception=task.on_exception, **kwargs)

        while not task.done:
            await asyncio.sleep(0)

        if task.exception:
            raise task.exception from task.exception
        return task.result

class ExecutorFactory():
    def __init__(self,
                 pool_name: str = None,
                 executor_type: Executor = Executor,
                 worker_type: FunctionQueueWorker = FunctionQueueWorker,
                 worker_limit: int = 1):
        self._pool_name = pool_name or type(self).__name__
        self._executor_type = executor_type
        self._worker_type = worker_type
        self._worker_limit = worker_limit
        self._pool = []
        self._disposed = False

    @property
    def worker_count(self) -> int:
        return len(self._pool)

    @property
    def workers(self) -> List[FunctionQueueWorker]:
        return self._pool

    def dispose(self):
        if not self._disposed:
            self._disposed = True
            for w in self._pool:
                w.dispose()

    def run_method_in_queue(self,
                            func: Callable,
                            *args,
                            on_finish: Callable[[Any], None] = None,
                            on_exception: Callable[[Exception], None] = None,
                            **kwargs) -> None:
        """Run the given func in queue. It does not block the calling thread"""
        with self.get_executor() as executor:
            executor.run_method_in_queue(
                func, *args, on_finish=on_finish, on_exception=on_exception, **kwargs)

    def run_method(self,
                   func: Callable,
                   *args,
                   **kwargs) -> Any:
        """
        Run the given func. It blocks the calling thread.
        """
        with self.get_executor() as executor:
            return executor.run_method(func, *args, **kwargs)

    async def run_method_async(self,
                               func: Callable,
                               *args,
                               **kwargs) -> Any:
        """Run the given func asynchronously."""
        async with self.get_executor() as executor:
            return await executor.run_method_async(func, *args, **kwargs)

    def get_executor(self, *args, **kwargs) -> Executor:
        """
        Create and return an executor instance.
        """
        if len(self._pool) < self._worker_limit:
            worker = self._worker_type(
                '{}_{}'.format(self._pool_name, len(self._pool)))
            self._pool.append(worker)
            return self._executor_type(worker, *args, **kwargs)

        return self._executor_type(self._pool[random.randint(0, len(self._pool)-1)], *args, **kwargs)


import argparse
from .utils import outputResult, debugOutputResult

def parseArguments():
    parser = argparse.ArgumentParser(
        description='Remove dark bezels from a video.')
    parser.add_argument('input', metavar='input_video', type=str,
                        help='the video that will have its bezels removed')
    parser.add_argument('output', metavar='output', type=str,
                        help='output destination')
    parser.add_argument('--debug', dest='outputResult', action='store_const',
                        const=debugOutputResult, default=outputResult,
                        help='if set the output will be the first frame of the modified video')

    return parser.parse_args()


import torch
from torch import einsum
import torch.nn as nn
import torch.nn.functional as F

import math

import roma
from invariant_point_attention import IPABlock
from loss.FAPE import computeFAPE

def rigidFrom3Points(x1, x2, x3):
    """
    Construct of frames from groud truth atom positions
    From alphaFold2 supplementary Algorithm {21} Rigid from 3 points using the Gram–Schmidt process
    Args:
        x1(torch.Tensor): N atom coordinates. dim -> (batch_size, num_res, 3)
        x2(torch.Tensor): C alpha atom coordinates. -> (batch_size, num_res, 3)
        x3(torch.Tensor): C atom coordinates. -> (batch_size, num_res, 3)

    Return:
        Rotations(tensor.Tensor): Rotation matrix. dim -> (batch_size, num_res, 3, 3)
        translations(tensor.Tensor): translation vector. dim -> (batch_size, num_res, 3)
    """
    v1 = x3 - x2
    v2 = x1 - x2
    e1 = v1 / torch.linalg.norm(v1, dim=2, keepdim=True)
    u2 = v2 - torch.einsum('bij,bij->bi', v2, e1).unsqueeze(-1) * e1 / torch.einsum('bij,bij->bi', e1, e1).unsqueeze(-1)
    e2 = u2 / torch.linalg.norm(u2, dim=2, keepdim=True)
    e3 = torch.cross(e1, e2)
    R = torch.stack([e1, e2, e3], dim=2) # column vector matrix
    t = x2

    return R, t

def linear_beta_schedule(timesteps):
    """
    linear schedule, proposed in original paper
    https://arxiv.org/abs/2006.11239
    """
    scale = 1000 / timesteps
    beta_start = scale * 0.0001
    beta_end = scale * 0.02
    return torch.linspace(beta_start, beta_end, timesteps, dtype = torch.float64)

def cosine_beta_schedule(timesteps, s = 0.008):
    """
    cosine schedule
    https://openreview.net/forum?id=-NEXDKk8gZ
    """
    t_T = torch.linspace(0, timesteps, timesteps + 1, dtype = torch.float64) / timesteps
    f_t = torch.cos((t_T + s) / (1 + s) * math.pi * 0.5) ** 2
    alphas_cumprod = f_t / f_t[0]
    betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1])
    return torch.clip(betas, 0, 0.999)

class ProteinDiffusion(nn.Module):
    def __init__(self, timesteps=1000, beta_schedule = 'cosine', device = None):
        super(ProteinDiffusion, self).__init__()
        self.device = device
        self.timesteps = timesteps

        if beta_schedule == "linear":
            beta_schedule_fn = linear_beta_schedule
        elif beta_schedule == "cosine":
            beta_schedule_fn = cosine_beta_schedule
        else:
            raise ValueError(f'unkown beta schedule {beta_schedule}')

        self.betas = beta_schedule_fn(self.timesteps)
        self.alphas = 1 - self.betas
        self.alpha_bars = torch.cumprod(self.alphas, dim = 0)

    def sample_timesteps(self, batch_size: int):
        return torch.randint(low=1, high=self.timesteps, size=(batch_size, ))

    def coord_q_sample(self, x: torch.Tensor, t: torch.Tensor):
        """
        Foward diffusion process for C alpha coordinates

        Args:
            x(torch.Tensor): dim -> (batch_size, num_res, 3)
            t(torch.Tensor): dim -> (batch_size, )

        Return:
            noisy(torch.Tensor): dim-> (batch_size, num_res, 3)
        """
        alpha_bars_t = self.alpha_bars[t].view(-1, 1, 1) # dim -> (batch_size, 1, 1)
        epsilon = torch.rand_like(x)

        noisy = torch.sqrt(alpha_bars_t) * x + torch.sqrt(1 - alpha_bars_t) * epsilon
        return noisy

    def quaternion_q_sample(self, q_0:torch.Tensor, t: torch.Tensor):
        """
        Foward diffusion process for unit quaternions

        Args:
            q_0(torch.Tensor): dim -> (batch_size, num_res, 4)
            t(torch.Tensor): dim -> (batch_size, )

        Return:
            q_t(torch.Tensor): dim -> (batch_size, num_res, 4)
        """
        batch_size, num_res = q_0.shape[0], q_0.shape[1]

        alpha_t = self.alphas[t]
        q_T = roma.random_unitquat(size=(batch_size, num_res)) # dim -> (batch_size, num_res, 4)
        q_interpolated = roma.utils.unitquat_slerp(q_0.double(), q_T.double(), alpha_t) #(t_size, batch_size, 20, 4)
        # create empty result tensor
        q_t = torch.empty_like(q_T)
        # loop over t_size and extract desired batch
        for t in range(len(alpha_t)):
            batch = q_interpolated[t, t % batch_size]
            q_t[t] = batch # dim -> (t_size, num_res, 4)

        return q_t

    def sample_p(self, model:nn.Module, single_repr: torch.Tensor, pair_repr: torch.Tensor):
        """
        sample from predicted x_0 and q_0
        Args:
            single_repr(torch.Tensor): dim -> (batch_size, num_res, 1280)
            pair_repr(torch.Tensor): dim -> (batch_size, num_res, num_res)
        Return:
            x_list(list): list of x_t, dim -> (timesteps, batch_size, num_res, 3)
            q_list(list): list of q_t, dim -> (timesteps, batch_size, num_res, 4)
        """
        batch_size, num_res = single_repr.shape[0], single_repr.shape[1]

        x_t = torch.randn(size=(batch_size, num_res, 3), device = self.device).to(torch.float32)
        q_t = roma.random_unitquat(size=(batch_size, num_res), device = self.device).to(torch.float32)

        sample_steps = list(range(1, self.timesteps + 1))

        x_hat, q_hat = model(single_repr, pair_repr, q_t, x_t)

        return x_hat, q_hat

class StructureModel(nn.Module):
    def __init__(self, input_single_repr_dim = 1280, input_pair_repr_dim = 1, dim = 128,
                 structure_module_depth = 12, structure_module_heads = 4, point_key_dim = 4, point_value_dim = 4):
        super().__init__()

        self.single_repr = nn.Linear(input_single_repr_dim, dim)

        self.pair_repr = nn.Linear(input_pair_repr_dim, dim)

        self.structure_module_depth = structure_module_depth
        self.ipa_block =IPABlock(dim=dim, heads = structure_module_heads,
                                 point_key_dim = point_key_dim, point_value_dim = point_value_dim)

        self.to_points = nn.Linear(dim, 6)
        self.to_quaternion_update = nn.Linear(dim, 6)

    def forward(self, single_repr:torch.Tensor, pair_repr:torch.Tensor, quaternions:torch.Tensor, translations:torch.Tensor):
        """
        Args:
            single_repr: dim -> (batch_size, num_res, embedding_dim)
            pair_repr: dim -> (batch_size, num_res, num_res)
            quaternions: dim -> (batch_size, num_res, 4)
            translation: dim -> (batch_size, num_res, 3)

        Returns:
            rotations: dim -> (batch_size, num_res, 3, 3)
            translations: dim -> (batch_size, num_res, 3)
            points_local: dim -> (batch_size, num_res, 3)
        """

        pair_repr = pair_repr.unsqueeze(-1) # dim -> (batch_size, num_res, num_res, 1)

        # Linear Foward
        single_repr = self.single_repr(single_repr)
        pair_repr = self.pair_repr(pair_repr)

        for i in range(self.structure_module_depth):
            is_last = i == (self.structure_module_depth - 1)

            rotations = roma.unitquat_to_rotmat(quaternions)

            if not is_last:
                rotations = rotations.detach()

            single_repr = self.ipa_block(single_repr, pairwise_repr = pair_repr,
                                         rotations = rotations, translations = translations)

            # update quaternion and translation
            quaternion_update, translation_update = self.to_quaternion_update(single_repr).chunk(2, dim = -1) # (batch_size, num_res, 6) -> (batch_size, num_res, 3), (batch_size, num_res, 3)
            quaternion_update = F.pad(quaternion_update, (0, 1), value = 1.) # dim -> (batch_size, num_res, 4), roma expects quatations to be in (x, y, z, w) format
            quaternion_update = quaternion_update / torch.linalg.norm(quaternion_update, dim = -1, keepdim = True) # normalize quaternion to unit quaternion

            quaternions = roma.quat_product(quaternions, quaternion_update)
            translations = translations + einsum('b n c, b n c r -> b n r', translation_update, rotations)

        # convert to points
        n_points_local, c_points_local = self.to_points(single_repr).chunk(2, dim = -1) # dim -> (batch_size, num_res, 3), (batch_size, num_res, 3)
        rotations = roma.unitquat_to_rotmat(quaternions) # dim -> (batch_size, num_res, 3, 3)

        return quaternions, translations, n_points_local, c_points_local


def plot_chart(stock_prices, result, stock, strategy):
    ######Plot
    ######
    growth, longs, shorts,  win_trade_count, loss_trade_count, win_percent, loss_percent, final_cash=result
    import plotly.graph_objs as go
    from datetime import datetime
    from ipywidgets import interact, interactive, fixed, interact_manual
    import ipywidgets as widgets
    from plotly.subplots import make_subplots
    # Make sure dates are in ascending order
    # We need this for slicing in the callback below
    data = stock_prices
    df = data.set_index('date')
    fig = go.FigureWidget(make_subplots(rows=5, cols=1))

    fig.add_trace(
        go.Scattergl(x=list(df.index), y=list(df['close']),
        name = "close"),
        row=1, col=1
    )
    if('stoploss' in df.columns):
        fig.add_trace(
            go.Scattergl(x=list(df.index), y=list(df['stoploss']), line = dict(color='firebrick', width=2, dash='dash'),
            name = "stoploss"),
            row=1, col=1
        )
    if('stoploss_fix' in df.columns):
        fig.add_trace(
            go.Scattergl(x=list(df.index), y=list(df['stoploss_fix']), line = dict(color='firebrick', width=2, dash='dash'),
            name = "stoploss_fix"),
            row=1, col=1
        )

    if('stoploss_danger' in df.columns):
        fig.add_trace(
            go.Scattergl(x=list(df.index), y=list(df['stoploss_danger']), line = dict(color='firebrick', width=2, dash='dash'),
            name = "stoploss_danger"),
            row=1, col=1
        )
    fig.add_trace(
        go.Scattergl(x=[i for i in shorts], y=[shorts[i] for i in shorts],
        name = "Short", mode="markers", marker=dict(color="red", size=5)),
        row=1, col=1
    )
    fig.add_trace(
        go.Scattergl(x=[i for i in longs], y=[longs[i] for i in longs],
        name = "Long", mode="markers", marker=dict(color="green", size=5)),
        row=1, col=1
    )
    plot1,plot2,plot3,plot4,plot5 = strategy.get_col_to_plot()

    for plott in plot1:
        p = plott[0]
        marker = plott[1]
        fig.add_trace(
            go.Scattergl(x=list(df.index), y=list(df[p]),
            name = p, mode=marker, marker=dict(size=4)),
            row=1, col=1
        )
    for plott in plot2:
        p = plott[0]
        marker = plott[1]
        fig.add_trace(
            go.Scattergl(x=list(df.index), y=list(df[p]),
            name = p, mode=marker, marker=dict(size=4)),
            row=2, col=1
        )
    for plott in plot3:
        p = plott[0]
        marker = plott[1]
        fig.add_trace(
            go.Scattergl(x=list(df.index), y=list(df[p]),
            name = p, mode=marker, marker=dict(size=4)),
            row=3, col=1
        )
    for plott in plot4:
        p = plott[0]
        marker = plott[1]
        fig.add_trace(
            go.Scattergl(x=list(df.index), y=list(df[p]),
            name = p, mode=marker, marker=dict(size=4)),
            row=4, col=1
        )
    for plott in plot5:
        p = plott[0]
        marker = plott[1]
        fig.add_trace(
            go.Scattergl(x=list(df.index), y=list(df[p]),
            name = p, mode=marker, marker=dict(size=4)),
            row=5, col=1
        )
    fig.layout.xaxis=dict(
            anchor='x',
            rangeselector=dict(
                buttons=list([
                    dict(count=1,
                         label='1m',
                         step='month',
                         stepmode='backward'),
                    dict(count=6,
                         label='6m',
                         step='month',
                         stepmode='backward'),
                    dict(count=1,
                        label='YTD',
                        step='year',
                        stepmode='todate'),
                    dict(count=1,
                        label='1y',
                        step='year',
                        stepmode='backward'),
                    dict(step='all')
                ])
            ),
            type='date'
        )
    fig.layout.yaxis=dict(
            domain=[0.6, 1]
        )
    fig.layout.yaxis2=dict(
            domain=[0.45, 0.59]
        )
    fig.layout.yaxis3=dict(
            domain=[0.30, 0.44]
        )
    fig.layout.yaxis4=dict(
            domain=[0.15, 0.29]
        )
    fig.layout.yaxis5=dict(
            domain=[0.0, 0.14]
        )
    #fig.layout.yaxis2.range=[0,100]
    #fig.layout.yaxis2.tickvals=[0,20,80,100]
    fig['layout'].update(height=700, width=1000)

    import time
    import datetime
    def zoom(layout, xrange):
        try:
            fig.layout.xaxis2.range = fig.layout.xaxis.range
            fig.layout.xaxis3.range = fig.layout.xaxis.range
            fig.layout.xaxis4.range = fig.layout.xaxis.range
            fig.layout.xaxis5.range = fig.layout.xaxis.range
            in_view = df.loc[fig.layout.xaxis.range[0]:fig.layout.xaxis.range[1]]
            padding = 0.1

            from_price = in_view.iloc[0].close
            to_price = in_view.iloc[-1].close
            growth = (to_price-from_price)/from_price*100.0

            max_val_y = in_view["close"].max()
            min_val_y =  in_view["close"].min()
            axis_diff = max_val_y-min_val_y
            fig.layout.yaxis.range = [min_val_y - (axis_diff*padding), max_val_y + (axis_diff*padding)]

            if(len(plot2)>0):
                max_val_y = in_view[plot2[0][0]].max()
                min_val_y =  in_view[plot2[0][0]].min()
                axis_diff = max_val_y-min_val_y
                fig.layout.yaxis2.range = [min_val_y - (axis_diff*padding), max_val_y + (axis_diff*padding)]

            if(len(plot3)>0):
                max_val_y = in_view[plot3[0][0]].max()
                min_val_y =  in_view[plot3[0][0]].min()
                axis_diff = max_val_y-min_val_y
                fig.layout.yaxis3.range = [min_val_y - (axis_diff*padding), max_val_y + (axis_diff*padding)]

            if(len(plot4)>0):
                max_val_y = in_view[plot4[0][0]].max()
                min_val_y =  in_view[plot4[0][0]].min()
                axis_diff = max_val_y-min_val_y
                fig.layout.yaxis4.range = [min_val_y - (axis_diff*padding), max_val_y + (axis_diff*padding)]

            if(len(plot5)>0):
                max_val_y = in_view[plot5[0][0]].max()
                min_val_y =  in_view[plot5[0][0]].min()
                axis_diff = max_val_y-min_val_y
                fig.layout.yaxis5.range = [min_val_y - (axis_diff*padding), max_val_y + (axis_diff*padding)]

            fig['layout'].update(title=symbol+" ("+str(round(growth,2))+"%)")
        except Exception as e:
            None
    fig.layout.on_change(zoom, 'xaxis')
    return fig


# https://contest.yandex.ru/yacup/contest/42202/problems/C/
# 119 case Time Limit (
import re
import string
from collections import Counter
from functools import cache

DEFAULT_CHARS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'

@cache
def base2int(value, base, chars=None, allow_negative=True):
    # this is the "fountain" we'll get the weird base's digits from
    chars = DEFAULT_CHARS if chars is None else chars
    charmap = {chars[i]: i for i in range(0, len(chars))}  # make dict of {char:position} pairs

    converted = 0  # converted integer
    sign = 1  # 1= positive, -1= negative; multiply int by this to correct sign

    num = list(value)  # working copy of str as list
    num.reverse()  # makes the integer position equal to exponent

    if num[-1] == '-':
        # remember, "leading" char is now at the end due to the above reverse()
        if not allow_negative and '-' not in charmap:
            # this error won't raise if '-' is being used as a digit!
            raise ValueError("Negative values not allowed")
        # NOTE: if you want a leading '-' to be treated as a char, you must
        #       set allow_negative = False; else we assume it means "negative"
        sign = -1 if allow_negative else 1
        num.pop()  # remove the sign, kind of like abs() on an int

    for exp in range(0, len(num)):
        # for each char index, use it as an exponent as well as an index, and...
        try:
            converted += charmap[num[exp]] * (base ** exp)  # value of digit times base to the power of position
        except KeyError as e:
            raise KeyError(f"digit {e} not found in '{chars}'")

    converted *= sign  # multiply by sign to correct negative if needed
    return converted  # return the converted int

def get_sum(args, from_base):
    out = 0

    for arg in args:
        if arg[0] == '+':
            out += base2int(arg[1], from_base)
        else:
            out -= base2int(arg[1], from_base)

    return out

with open("input.txt") as f_in:
    l_in, r_in = f_in.readline().upper().replace('\n', '').split('=')
    l_in = f"+ {l_in}"
    r_in = f"+ {r_in}"

    l_all = re.findall(r'([+-]+)\s*([\dA-Z]+)', l_in)
    r_all = re.findall(r'([+-]+)\s*([\dA-Z]+)', r_in)

    b_min = max(max(Counter(l_in)), max(Counter(r_in)))

    b_min = int(b_min) if b_min in string.digits else (ord(b_min) - ord('A') + 10)

    out = -1
    for b in range(b_min + 1, 1024):
        try:
            if get_sum(l_all, b) == get_sum(r_all, b):
                out = b
                break
        except:
            pass

    print(out)


import soundcloud

from media_entry import MediaEntry
from service_interface import ServiceInterface

import youtube_dl
from FileDownloader import FileDownloader

class SoundCloudService(ServiceInterface):
    def __init__(self):
        ServiceInterface.__init__(self)
        self._client = soundcloud.Client(client_id="a0f302b73e746e103ea4be14fac09677")
        self.downloader = None
        self._current_feed = None
        self._name = "soundcloud"

    def search(self, words):
        self._current_feed = self._client.get('/tracks', q=words)
        return self._createMediaEntries()

    def _createMediaEntries(self):
        _entries = []
        for entry in self._current_feed:
            artwork_url = entry.obj["artwork_url"]
            if artwork_url is None:
                artwork_url = "soundcloud_default.png"
            _mediaEntry = MediaEntry(entry.obj["permalink_url"],
                                     [artwork_url],
                                     entry.obj["title"],
                                     str(entry.obj["duration"] / 1000),  # Duration is in milliseconds
                                     entry.obj["description"],
                                     self,
                                     audio_only=True)
            _entries.append(_mediaEntry)
        return _entries

    def _get_url_from_infos(self, infos):
        return infos["formats"][0]


from .SBAS_sbas_gmtsar import SBAS_sbas_gmtsar
from .PRM import PRM
from .tqdm_dask import tqdm_dask

class SBAS_sbas(SBAS_sbas_gmtsar):

    @staticmethod
    def lstsq(x, w, matrix):
        """
        Compute the least squares solution (or weighted least squares if weights are provided) for a given matrix.

        This function handles single-pixel processing and allows for non-weighted least squares, weighted least squares,
        and ignoring pixels where the correlation is not defined.

        Parameters
        ----------
        x : numpy.ndarray
            Input data array.
        w : numpy.ndarray or None
            Weights array for weighted least squares. If None, non-weighted least squares is used.
        matrix : numpy.ndarray
            Input matrix for which the least squares solution is computed.

        Returns
        -------
        numpy.ndarray
            Least squares solution for the given matrix.

        Notes
        -----
        If w is None, non-weighted least squares is used. If w has NaN values, the corresponding pixels are ignored.
        In case of a LinAlgError (e.g., SVD did not converge), the function returns an array of NaN values with the
        same shape as the input matrix's second dimension.

        """
        import numpy as np

        if w is None:
            # allow not weighted Least Squares
            w = 0.5 * np.ones_like(x)
        elif np.any(np.isnan(w)):
            # ignore pixels where correlation is not defined
            return np.nan * np.zeros(matrix.shape[1])

        # fill nans as zeroes and set corresponding weight to 0
        nanmask = np.where(np.isnan(x))
        if nanmask[0].size > 0:
            # function arguments are read-only
            x = x.copy()
            w = w.copy()
            x[nanmask] = 0
            w[nanmask] = 0
            # check if x has enough valid values
            if x.size - nanmask[0].size < matrix.shape[1]:
                return np.nan * np.zeros(matrix.shape[1])
        try:
            if np.all(w == 1):
                # least squares solution
                model = np.linalg.lstsq(matrix, x, rcond=None)
            else:
                # weighted least squares solution
                W = (w/np.sqrt(1-w**2))
                model = np.linalg.lstsq(matrix * W[:,np.newaxis], x * W, rcond=None)
        except Exception as e:
            # typically, this error handled:
            # LinAlgError: SVD did not converge in Linear Least Squares
            print ('SBAS.lstsq notice:', str(e))
            return np.nan * np.zeros(matrix.shape[1])
        #print ('model', model)
        return model[0]

    def lstsq_matrix(self, pairs):
        """
        Create a matrix for use in the least squares computation based on interferogram date pairs.

        Parameters
        ----------
        pairs : pandas.DataFrame
            DataFrame containing interferogram date pairs.

        Returns
        -------
        numpy.ndarray
            Matrix with one row for every interferogram and one column for every date.
            Each element in the matrix is an integer, with 1 representing that the date
            is between the corresponding interferogram's reference and repeat dates, and
            0 otherwise.

        Notes
        -----
        This function also calculates image capture dates from the interferogram date pairs.

        """
        import numpy as np
        import pandas as pd

        # also define image capture dates from interferogram date pairs
        pairs, dates = self.pairs(pairs, dates=True)
        pairs = pairs[['ref', 'rep']].astype(str).values

        # here are one row for every interferogram and one column for every date
        matrix = []
        for pair in pairs:
            mrow = [date>pair[0] and date<=pair[1] for date in dates]
            matrix.append(mrow)
        matrix = np.stack(matrix).astype(int)
        return matrix

    def sbas_parallel(self, pairs=None, mask=None, data='detrend', corr='corr', weight='corr',
                      chunks=None, chunksize=None, n_jobs=-1, interactive=False):
        """
        An alias for the lstsq_parallel function, performing parallel least squares computation on input data.

        This function is an alias for the lstsq_parallel function, which computes the least squares (weighted
        or unweighted) on the input data, considering interferogram date pairs and processing it in parallel
        using Dask and Joblib.

        Parameters
        ----------
        pairs : array-like or pandas.DataFrame, optional
            Interferogram date pairs.
        mask : array-like, optional
            Deprecated. Use "data" and "weight" arguments to call with custom data arrays.
        data : str or xarray.DataArray, optional
            Input data to compute least squares on.
        corr : str, optional
            Deprecated. Use "weight" argument instead.
        weight : str, xarray.DataArray, pd.Series, or np.ndarray, optional
            Weights for the least squares computation.
        chunks : int, optional
            Deprecated. Use "chunksize" argument instead.
        chunksize : int, optional
            Size of the chunks for processing the data.
        n_jobs : int, optional
            Number of CPU cores to use for parallel processing (default is -1, which means using all available cores).
        interactive : bool, optional
            If True, returns the result as an xarray DataArray (default is False).

        Returns
        -------
        xarray.DataArray
            The computed least squares model as an xarray DataArray.

        Examples
        --------
        Process detrended unwrapped phase:
        sbas.sbas_parallel(pairs, data='detrended', weight='corr')

        Process unwrapped phase without detrending:
        sbas.sbas_parallel(pairs, data='unwrap', weight='corr')

        Process defined data stack only:
        unwraps = sbas.open_grids(pairs, 'unwrap')
        sbas.sbas_parallel(pairs, data=unwraps)

        Notes
        -----
        This function processes large stacks by splitting them into chunks, performing the computation,
        and then rebuilding and saving the results in a user-friendly format. It is an alias for the
        lstsq_parallel function.
        """
        print ('NOTE: sbas_parallel() is alias for [Weighted] Least Squares lstsq_parallel() function')

        if corr != 'corr':
            print ('NOTE: use "weight" argument instead of "corr"')

        if mask is not None:
            print ('NOTE: "mask" argument support is removed. Use "data" and "weight" arguments to call with custom data arrays')

        if chunks is not None:
            print ('NOTE: use "chunksize" argument instead of "chunks"')

        return self.lstsq_parallel(pairs=pairs, data=data, weight=weight,
                                   chunksize=chunksize, n_jobs=n_jobs, interactive=interactive)

    def lstsq_parallel(self, pairs=None, data='detrend', weight='corr',
                       chunksize=None, n_jobs=-1, interactive=False):
        """
        Perform least squares (weighted or unweighted) computation on the input data in parallel.

        This function applies the least squares computation on the input data, taking into account
        interferogram date pairs, and processes it in parallel using Dask and Joblib.

        Parameters
        ----------
        pairs : array-like or pandas.DataFrame, optional
            Interferogram date pairs.
        data : str or xarray.DataArray, optional
            Input data to compute least squares on.
        weight : str, xarray.DataArray, pd.Series, or np.ndarray, optional
            Weights for the least squares computation.
        chunksize : int, optional
            Size of the chunks for processing the data.
        n_jobs : int, optional
            Number of CPU cores to use for parallel processing (default is -1, which means using all available cores).
        interactive : bool, optional
            If True, returns the result as an xarray DataArray (default is False).

        Returns
        -------
        xarray.DataArray
            The computed least squares model as an xarray DataArray.

        Notes
        -----
        This function processes large stacks by splitting them into chunks, performing the computation,
        and then rebuilding and saving the results in a user-friendly format.

        """
        import xarray as xr
        import numpy as np
        import pandas as pd
        import dask
        from tqdm.auto import tqdm
        import joblib
        import os

        if chunksize is None:
            # smaller chunks are better for large 3D grids processing
            chunksize = self.chunksize
        #print ('chunksize', chunksize)

        # also define image capture dates from interferogram date pairs
        # convert pairs (list, array, dataframe) to 2D numpy array
        pairs, dates = self.pairs(pairs, dates=True)
        pairs = pairs[['ref', 'rep']].astype(str).values

        # split large stacks to chunks about chunksize ^ 3
        minichunksize = chunksize**2 / len(pairs)
        # round to 2**deg (32,  64, 128,..., 2048)
        chunkdegrees = np.array([2**deg for deg in range(5,12) if 2**deg <= chunksize])
        minichunksize = int(chunkdegrees[np.abs(chunkdegrees-minichunksize).argmin()])
        #print ('minichunksize', minichunksize)

        # source grids lazy loading
        if isinstance(data, str):
            data = self.open_grids(pairs, data, chunksize=minichunksize, interactive=True)

        if isinstance(weight, str):
            weight = self.open_grids(pairs, weight, chunksize=minichunksize, interactive=True)
        elif isinstance(weight, (pd.Series, np.ndarray)):
            # vector weight like to average correlation
            weight = xr.DataArray(weight, dims=['pair'], coords={'pair': data.pair})

        # xarray wrapper
        model = xr.apply_ufunc(
            self.lstsq,
            data.chunk(dict(pair=-1)),
            weight.chunk(dict(pair=-1)) if weight is not None else None,
            input_core_dims=[['pair'], ['pair'] if weight is not None else []],
            exclude_dims=set(('pair',)),
            dask='parallelized',
            vectorize=True,
            output_dtypes=[np.float32],
            output_core_dims=[['date']],
            dask_gufunc_kwargs={'output_sizes': {'date': len(dates)}},
            kwargs={'matrix': self.lstsq_matrix(pairs)}
        ).rename('displacement')
        # define dates axis
        model['date'] = dates
        # set the stack index to be first
        model = model.transpose('date',...)

        if interactive:
            return model

        # use this trick to save large model using limited RAM and all CPU cores
        ts, ys, xs = model.data.blocks.shape
        assert ts == 1, 'Date chunks count should be equal to 1'
        tchunks, ychunks, xchunks = model.chunks
        coordt = model.date
        coordy = np.array_split(model.y, np.cumsum(ychunks))
        coordx = np.array_split(model.x, np.cumsum(xchunks))

        def func(iy, ix):
            chunk_filename = self.get_filenames(None, None, f'disp_chunk_{iy}_{ix}')
            if os.path.exists(chunk_filename):
                os.remove(chunk_filename)
            # lazy dask array
            data = model.data.blocks[0,iy,ix]
            # wrap the array
            da = xr.DataArray(data,
                              dims=['date','y','x'],
                              coords={'date': coordt, 'y':coordy[iy], 'x':coordx[ix]})\
                 .rename('displacement')
            # compute and save to NetCDF using chunks of original coordinates
            da.to_netcdf(chunk_filename,
                         unlimited_dims=['y','x'],
                         encoding={'displacement': self.compression(chunksize=(1, minichunksize, minichunksize))},
                         engine=self.engine,
                         compute=True)
            return chunk_filename

        # process the chunks as separate tasks on all available CPU cores
        with self.tqdm_joblib(tqdm(desc='[Correlation-Weighted] Least Squares Computing', total=ys*xs)) as progress_bar:
            filenames = joblib.Parallel(n_jobs=n_jobs)(joblib.delayed(func)(iy, ix) \
                                                     for iy in range(ys) for ix in range(xs))

        # rebuild the datasets to user-friendly format
        das = [xr.open_dataarray(f, engine=self.engine, chunks=chunksize) for f in filenames]
        das = xr.combine_by_coords(das)

        def output(dt):
            filename = self.get_filenames(None, None, f'disp_{dt}'.replace('-',''))
            if os.path.exists(filename):
                os.remove(filename)
            da = das.sel(date=dt)
            da.to_netcdf(filename,
                        encoding={'displacement': self.compression(da.displacement.shape, chunksize=chunksize)},
                        engine=self.engine)

        # saving all the grids
        with self.tqdm_joblib(tqdm(desc='Saving', total=len(das.date))) as progress_bar:
            joblib.Parallel(n_jobs=n_jobs)(joblib.delayed(output)(dt) for dt in das.date.values)

        # cleanup
        for filename in filenames:
            if os.path.exists(filename):
                os.remove(filename)

    def baseline_table(self, n_jobs=-1, debug=False):
        """
        Generates a baseline table for Sentinel-1 data, containing dates, times, and baseline components.

        This function creates a baseline table for Sentinel-1 data by processing the PRM files, which
        contain metadata for each image. The table includes dates, times, and parallel and perpendicular
        baseline components for each image.

        Parameters
        ----------
        n_jobs : int, optional
            Number of CPU cores to use for parallel processing (default is -1, which means using all available cores).
        debug : bool, optional
            If True, print additional information during processing (default is False).

        Returns
        -------
        pandas.DataFrame
            A DataFrame containing the baseline table with date, times, and baseline components.

        Notes
        -----
        This function processes Sentinel-1 data by first generating PRM and LED files if they don't exist,
        then calculating doppler and orbital parameters for each image, and finally computing the baseline
        components.

        """
        import pandas as pd
        import numpy as np
        from tqdm.auto import tqdm
        import joblib
        import os

        # use any subswath (the 1st one here) to produce the table
        subswath = self.get_subswaths()[0]
        datetimes = self.df[self.df.subswath==subswath].datetime

        def get_filename(dt):
            _, stem = self.multistem_stem(subswath, dt)
            filename = os.path.join(self.basedir, f'{stem}.PRM')
            return filename

        def ondemand(date, dt):
            if not os.path.exists(get_filename(dt)):
                self.make_s1a_tops(subswath, date, debug=debug)

        # generate PRM, LED if needed
        #for (date, dt) in datetimes.iteritems():
        #    #print (dt, date)
        #    ondemand(dt)
        with self.tqdm_joblib(tqdm(desc='PRM generation', total=len(datetimes))) as progress_bar:
            joblib.Parallel(n_jobs=n_jobs)(joblib.delayed(ondemand)(date, dt) for (date, dt) in datetimes.items())

        # after merging use unmerged subswath PRM files
        # calc_dop_orb() required for SAT_baseline
        master_dt = datetimes[self.master]
        prm_ref = PRM().from_file(get_filename(master_dt)).calc_dop_orb(inplace=True)
        data = []
        for (date, dt) in datetimes.items():
            # after merging use unmerged subswath PRM files
            prm_rep = PRM().from_file(get_filename(dt))
            ST0 = prm_rep.get('SC_clock_start')
            DAY = int(ST0 % 1000)
            YR = int(ST0/1000) - 2014
            YDAY = YR * 365 + DAY
            #print (f'YR={YR}, DAY={DAY}')
            BPL, BPR = prm_ref.SAT_baseline(prm_rep).get('B_parallel', 'B_perpendicular')
            data.append({'date':date, 'ST0':ST0, 'YDAY':YDAY, 'BPL':BPL, 'BPR':BPR})
            #print (date, ST0, YDAY, BPL, BPR)
        return pd.DataFrame(data).set_index('date')

    def baseline_pairs(self, days=100, meters=150, limit=None, invert=False, n_jobs=-1, debug=False):
        """
        Generates a sorted list of baseline pairs based on specified temporal and spatial criteria.

        This function creates a list of baseline pairs for Sentinel-1 data, considering the given
        temporal and spatial constraints. The list includes pairs that meet the specified days and
        meters criteria.

        Parameters
        ----------
        days : int, optional
            Maximum temporal separation between image pairs in days (default is 100).
        meters : int, optional
            Maximum spatial separation between image pairs in meters (default is 150).
        limit : int, optional
            Maximum number of pairs to return per reference image (default is None, which means no limit).
        invert : bool, optional
            If True, invert the order of reference and repeat images (default is False).
        n_jobs : int, optional
            Number of CPU cores to use for parallel processing (default is -1, which means using all available cores).
        debug : bool, optional
            If True, print additional information during processing (default is False).

        Returns
        -------
        pandas.DataFrame
            A DataFrame containing the sorted list of baseline pairs with reference and repeat dates,
            timelines, and baselines.

        """
        import numpy as np
        import pandas as pd

        tbl = self.baseline_table(n_jobs=n_jobs, debug=debug)
        data = []
        for line1 in tbl.itertuples():
            counter = 0
            for line2 in tbl.itertuples():
                #print (line1, line2)
                if limit is not None and counter >= limit:
                    continue
                if not (line1.YDAY < line2.YDAY and line2.YDAY - line1.YDAY < days):
                    continue
                if not (abs(line1.BPR - line2.BPR)< meters):
                    continue

                counter += 1
                if not invert:
                    data.append({'ref_date':line1.Index, 'rep_date': line2.Index,
                                 'ref_timeline': np.round(line1.YDAY/365.25+2014, 2), 'ref_baseline': np.round(line1.BPR, 2),
                                 'rep_timeline': np.round(line2.YDAY/365.25+2014, 2), 'rep_baseline': np.round(line2.BPR, 2)})
                else:
                    data.append({'ref_date':line2.Index, 'rep_date': line1.Index,
                                 'ref_timeline': np.round(line2.YDAY/365.25+2014, 2), 'ref_baseline': np.round(line2.BPR, 2),
                                 'rep_timeline': np.round(line1.YDAY/365.25+2014, 2), 'rep_baseline': np.round(line1.BPR, 2)})

        return pd.DataFrame(data).sort_values(['ref_date', 'rep_date'])

    @staticmethod
    def triplets2pairs(triplets, pairs):
        """
        Generates a DataFrame of pairs based on the provided triplets and pairs DataFrames.

        This function takes a DataFrame of baseline triplets and a DataFrame of baseline pairs,
        and generates a DataFrame containing pairs that correspond to the provided triplets.

        Parameters
        ----------
        triplets : pandas.DataFrame
            A DataFrame containing the sorted list of baseline triplets with columns 'A', 'B', and 'C'.

        pairs : pandas.DataFrame
            A DataFrame containing the sorted list of baseline pairs with reference and repeat dates.

        Returns
        -------
        pandas.DataFrame
            A DataFrame containing the sorted baseline pairs corresponding to the provided triplets,
            with columns 'ref_date' and 'rep_date'.

        """
        import pandas as pd

        data = []
        for triplet in triplets.itertuples():
            data.append({'ref_date': triplet.A, 'rep_date': triplet.B})
            data.append({'ref_date': triplet.B, 'rep_date': triplet.C})
            data.append({'ref_date': triplet.A, 'rep_date': triplet.C})
        tripairs = pd.DataFrame(data).sort_values(['ref_date', 'rep_date']).drop_duplicates()
        idx = tripairs.set_index(['ref_date', 'rep_date']).index
        return pairs.set_index(['ref_date', 'rep_date']).loc[idx].reset_index()

    @staticmethod
    def pairs2triplets(pairs, invert=False):
        """
        Generates a DataFrame containing sorted baseline triplets based on the provided pairs.

        This function takes a DataFrame of baseline pairs and generates a DataFrame containing
        sorted baseline triplets. The resulting triplets have the following relationship:
        A -> B -> C, where A, B, and C are dates.

        Parameters
        ----------
        pairs : pandas.DataFrame
            A DataFrame containing the sorted list of baseline pairs with reference and repeat dates.

        invert : bool, optional
            If set to True, the resulting triplets will have the dates inverted.
            Default is False.

        Returns
        -------
        pandas.DataFrame
            A DataFrame containing the sorted baseline triplets with columns 'A', 'B', and 'C'.

        """
        import pandas as pd

        data = []
        pairs_a = pairs
        for line_a in pairs_a.itertuples():
            #print (line_a)
            date_a_ref = line_a.ref_date
            date_a_rep = line_a.rep_date
            pairs_b = pairs[pairs.ref_date==date_a_rep]
            for line_b in pairs_b.itertuples():
                #print (line_b)
                date_b_ref = line_b.ref_date
                date_b_rep = line_b.rep_date
                pairs_c = pairs[(pairs.rep_date==date_b_rep)&(pairs.ref_date==date_a_ref)]
                for line_c in pairs_c.itertuples():
                    #print (line_c)
                    date_c_ref = line_c.ref_date
                    date_c_rep = line_c.rep_date
                    #print (date_a_ref, date_a_rep, date_b_rep)
                    data.append({'A': date_a_ref, 'B': date_a_rep, 'C': date_b_rep})
        return pd.DataFrame(data).sort_values(['A', 'B', 'C'])


from PyQt5 import uic, QtWidgets
from PyQt5.QtWidgets import QApplication, QLabel, QVBoxLayout, QPushButton, QStackedWidget, QWidget, QMainWindow
import sys

from parity_check import Parity_check
from hamming import Haming_method

class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        uic.loadUi('ui/main.ui', self)
        self.window = None
        self.Button_parity.clicked.connect(self.open_parity_window)
        self.Button_haming.clicked.connect(self.open_hamming_window)

    def open_parity_window(self):
        if self.window is None or 'Parity_check' or 'Haming_method':
            self.window = Parity_check()
        self.window.show()

    def open_hamming_window(self):
        if self.window is None or 'Parity_check' or 'Haming_method':
            self.window = Haming_method()
        self.window.show()

if __name__ == '__main__':
    app = QApplication([])
    wimdow = MainWindow()
    wimdow.show()
    app.exec_()


import logging
from base.models import Makanan
from api.serializers.MakananSerializer import MakananSerializer
from django.db.models.aggregates import Count
from random import randint
from .seleksi import Seleksi
import random
import copy

class AlgoritmaGenetika(Seleksi):

    def __init__(self, kebutuhan_gizi: dict) -> None:
        pass

    def generateGenerasi(self, blok_makanan: list = []) -> list:
        """Membuat 10 set rekomendasi menu yang terdiri dari 3 makanan dan 2 camilan

        Returns:
            list: 10 set rekomendasi menu yang terdiri dari 3 makanan dan 2 camilan
        """
        try:
            hasil = []

            for row in range(0, 10):
                set_makanan = self.ambilRandomMakanan('ME', 3, blok_makanan)
                set_camilan = self.ambilRandomMakanan('CA', 2, blok_makanan)

                hasil.append(set_makanan + set_camilan)

            return hasil

        except BaseException as e:
            raise

    def hitungNilaiFitness(self, generasi, kebutuhan_gizi: dict) -> dict:
        """Hitung nilai fitness dari generasi

        Args:
            generasi (_type_): 10 st rekomendasi menu makanan
            kebutuhan_gizi (dict): Kebutuhan gizi pengguna

        Returns:
            dict: Nilai gizi setiap kromsom dan nilai fitness setiap kromosom
        """
        try:
            hasil = {
                'set_nilai_gizi': [],
                'set_nilai_fitness': [],
            }

            # Hitung nilai gizi
            for index, kromosom in enumerate(generasi):
                # Hitung nilai gizi
                nilai_gizi = self.sumNilaiGiziKromosom(kromosom)
                nilai_gizi['index'] = index

                # Simpan nilai gizi
                hasil['set_nilai_gizi'].append(nilai_gizi)

                # Hitung nilai fitness
                nilai_fitness_protein = self.hitungNilaiAnggota(
                    kebutuhan_gizi['kebutuhan_protein']['protein_15'],
                    kebutuhan_gizi['kebutuhan_protein']['protein_20'],
                    nilai_gizi['protein']
                )
                nilai_fitness_lemak = self.hitungNilaiAnggota(
                    kebutuhan_gizi['kebutuhan_lemak']['lemak_20'],
                    kebutuhan_gizi['kebutuhan_lemak']['lemak_25'],
                    nilai_gizi['lemak']
                )
                nilai_fitness_karbo = self.hitungNilaiAnggota(
                    kebutuhan_gizi['kebutuhan_karbo']['karbo_55'],
                    kebutuhan_gizi['kebutuhan_karbo']['karbo_65'],
                    nilai_gizi['karbo']
                )

                nilai_fitness = {
                    'nilai_fitness_protein': nilai_fitness_protein,
                    'nilai_fitness_lemak': nilai_fitness_lemak,
                    'nilai_fitness_karbo': nilai_fitness_karbo,
                }

                # Simpan nilai fitness
                hasil['set_nilai_fitness'].append(nilai_fitness)

            return hasil

        except BaseException as e:
            raise

    def selectCross(self, set_nilai_fitness: list, generasi: list) -> list:
        """Container untuk melakukan proses selection dan crossover secara bersamaan

        Args:
            set_nilai_fitness (list): Nilai fitness dari generasi
            generasi (list): Generasi yang akan mengalami crossover

        Returns:
            hasil['parents']: Set kromosom yang terpilih sebagai orang tua
            hasil['childs']: Set kromosom yang dihasilkan oleh parents (pada proses crossover)
        """
        try:

            hasil = {
                "parents": [],
                "childs": [],
            }

            probailitas = Seleksi().generateProbabilitas(set_nilai_fitness)

            # Forloop crossover
            for row in range(0, int(len(generasi)/2)):
                parents = Seleksi().selectParent(probailitas['probabilitas_kumulatif'], copy.deepcopy(generasi))
                hasil['parents'].extend(parents)

                childs = self.crossover(copy.deepcopy(parents))
                hasil['childs'].extend(childs)

            return hasil

        except BaseException as e:
            raise

    def mutasi(self, generasi: list, blok_makanan: list = []) -> list:
        """Mengubah gen pada kromosom pada tempat yang acak

        Args:
            generasi (list): Generasi yang akan mengalami mutasi

        Returns:
            list: Generasi yang telah mengalami mutasi
        """
        try:
            random_number = random.randrange(0, 2)

            for row in generasi:
                random_makanan = self.ambilRandomMakanan('ME', 1, blok_makanan)[0]
                row[random_number] = random_makanan

            return generasi
        except BaseException as e:
            raise

    def elitism(self, generasi: list, set_nilai_fitness: list) -> list:
        """Mengambil kromosom terbaik dari generasi

        Args:
            generasi (list): Generasi yang akan diambil nilai terbaiknya
            set_nilai_fitness (list): Nilai fitness dari generasi

        Returns:
            list: Dua generasi terbaik
        """
        try:
            hasil = []

            # Hitung probabilitas (lagi) untuk mengurutkan kromosom terjelek ke terbaik
            probabilitas = Seleksi().generateProbabilitas(set_nilai_fitness)

            # Simpan index, agar tidak terlalu panjang codingannya
            kromosom1_index = probabilitas['final_probabilitas'][-1]['index']
            kromosom2_index = probabilitas['final_probabilitas'][-2]['index']

            # Simpan generasi dengan hasil terbaik
            hasil.append(generasi[kromosom1_index])
            hasil.append(generasi[kromosom2_index])

            return hasil

        except BaseException as e:
            raise

    def bentukPopulasiBaru(self, generasi_mutasi: list, generasi_elit: list) -> list:
        """Menggabungkan generasi mutasi dan generasi elit

        Args:
            generasi_mutasi (list): Generasi yang telah mengalami mutasi
            generasi_elit (list): Generasi dengan nilai fitness atau probabilitas terpilih yang terbaik

        Returns:
            list: Gabungan antara generasi mutasi dan generasi elit (jumlahnya 10)
        """
        try:
            hasil = generasi_elit

            generasi_mutasi.pop(-1)
            generasi_mutasi.pop(-2)

            hasil.extend(generasi_mutasi)

            return hasil

        except BaseException as e:
            raise

    def crossover(self, parents:list) -> list:
        """Melakukan persilangan kromosom antar parent

        Args:
            parents (list): Kromosom yang telah dipilih sebagai parent

        Returns:
            list: Childs dari parent
        """
        try:
            childs = copy.deepcopy(parents)

            random_nomor_1 = random.randrange(0,2)
            random_nomor_2 = random.randrange(3,4)

            childs[0][random_nomor_1] = parents[1][random_nomor_1]
            childs[0][random_nomor_2] = parents[1][random_nomor_2]

            childs[1][random_nomor_1] = parents[0][random_nomor_1]
            childs[1][random_nomor_2] = parents[0][random_nomor_2]

            return childs

        except BaseException as e:
            raise

    # Util
    def hitungNilaiAnggota(self, a: float, c: float, x: float) -> float:
        """Hitung nilai anggota kurva segitiga
        * Utility hitungNilaiFitness

        Args:
            a (float): Nilai bawah
            c (float): Nilai atas
            x (float): Nilai yang akan diukur nilai anggotanya

        Raises:
            Exception: Value out of bound

        Returns:
            float: Nilai anggota antara 0 sampai 1
        """
        try:
            hasil = 0
            b = (a+c) / 2

            if x <= a or x >= c:
                return hasil
            elif x > a and x < b:
                hasil = (x-a)/(b-a)
            elif x > b and x < c:
                hasil = (c-x)/(c-b)
            elif x == b:
                hasil = 1
            else:
                raise Exception("Value out of bound")

            return hasil

        except BaseException as e:
            raise

    def sumNilaiGiziKromosom(self, kromosom: dict) -> dict:
        """Hitung sum nilai dari kromosom
        * Utility hitungNilaiFitness

        Args:
            kromosom (dict): Row dari generasi

        Returns:
            dict: Sum nilai gizi protein, lemak, karbo
        """
        try:

            hasil = {
                'protein': 0,
                'lemak': 0,
                'karbo': 0,
            }

            for row in kromosom:
                hasil['protein'] += row['protein']
                hasil['lemak'] += row['lemak']
                hasil['karbo'] += row['karbo']

            return hasil

        except BaseException as e:
            raise

    def avgNilaiFitnessGizi(self, kromosom: dict) -> dict:
        """Hitung sum nilai dari kromosom
        * Utility hitungNilaiFitness

        Args:
            kromosom (dict): Row dari generasi

        Returns:
            dict: Sum nilai gizi protein, lemak, karbo
        """
        try:

            hasil = {
                'protein': 0,
                'lemak': 0,
                'karbo': 0,
            }

            for row in kromosom:
                hasil['protein'] += row['nilai_fitness_protein']
                hasil['lemak'] += row['nilai_fitness_lemak']
                hasil['karbo'] += row['nilai_fitness_karbo']

            hasil['protein'] = hasil['protein'] / len(kromosom)
            hasil['lemak'] = hasil['lemak'] / len(kromosom)
            hasil['karbo'] = hasil['karbo'] / len(kromosom)

            return hasil

        except BaseException as e:
            raise

    def ambilRandomMakanan(self, jenis: str, jumlah: int, blok_makanan: list = []) -> list:
        """Mengambil makanan random dari dataset makanan

        ! Ubah fungsi ini sesuah dengan jenis dataset
        * Saat ini fungsi dibuat untuk mengambil data dari database menggunakan model Makanan

        Args:
            jenis (str): Jenis makanan ('CA', 'BA', 'ME')
            jumlah (int): Jumlah data yang didapatkan

        Raises:
            Exception: Tipe data jenis str
            Exception: Tipe data jumlah int
            Exception: Pemilihan jenis
            Exception: Jumlah harus lebih dari 0

        Returns:
            list: [
                {
                    "id": 793,
                    "nama": "Manan samin",
                    "lemak": 50.6,
                    "protein": 3.4,
                    "karbo": 18.1,
                    "energi": 379.0, ..
                },
                ...
            ]
        """
        try:

            # Data type validation
            if type(jenis) != str:
                raise Exception('Tipe data jenis harus str')
            if type(jumlah) != int:
                raise Exception('Tipe data jumlah harus int')

            # Value validation
            if jenis != 'ME' and jenis != 'CA' and jenis != 'BA':
                raise Exception(
                    'Silahkan pilih salah satu jenis makanan CA, ME, atau BA')
            if jumlah <= 0:
                raise Exception('Jumlah tidak boleh kurang atau sama dengan 0')

            hasil = []

            makanan = Makanan.objects.filter(jenis=jenis).exclude(id__in=blok_makanan)
            count = makanan.aggregate(count=Count('id'))['count']

            for x in range(0, jumlah):

                random_index = randint(0, count - 1)
                random_makanan = makanan.all()[random_index]
                random_makanan = MakananSerializer(random_makanan, many=False)

                hasil.append(random_makanan.data)

            return hasil

        except BaseException as e:
            raise


import uuid
from typing import cast
from bespoke.db import models
from bespoke.db.models import session_scope
from bespoke.bespoke_catalog import bespoke_catalog_util
from bespoke_test.db import db_unittest
from sqlalchemy.orm.session import Session

def setup_brand(
	session: Session,
	brand_name: str,
	parent_company_id: str,
) -> str:
	brand = models.BespokeCatalogBrand(# type: ignore
		id = uuid.uuid4(),
		brand_name = brand_name,
		parent_company_id = parent_company_id,
	)
	session.add(brand)
	return brand.id

class TestCreateUpdateBespokeCatalogBrandView(db_unittest.TestCase):
	def test_create_brand(self) -> None:
		with session_scope(self.session_maker) as session:
			brand_name = 'Test Brand'
			parent_company_id = None

			brand_id, err = bespoke_catalog_util.create_update_bespoke_catalog_brand(
				id=None,
				session=session,
				brand_name=brand_name,
				parent_company_id=None,
			)

			brand = cast(
				models.BespokeCatalogBrand,
				session.query(models.BespokeCatalogBrand).filter_by(
					id=brand_id
				).first()
			)

			self.assertEqual(err, None)
			self.assertEqual(brand.brand_name, brand_name)
			self.assertEqual(brand.parent_company_id, parent_company_id)

	def test_update_brand(self) -> None:
		with session_scope(self.session_maker) as session:
			company_id = str(uuid.uuid4())
			brand_id = setup_brand(
				session=session,
				brand_name='Test Brand',
				parent_company_id=None,
			)

			new_brand_name = 'Gucci'
			brand_id, err = bespoke_catalog_util.create_update_bespoke_catalog_brand(
				session=session,
				id=brand_id,
				brand_name=new_brand_name,
				parent_company_id=company_id,
			)

			brand = cast(
				models.BespokeCatalogBrand,
				session.query(models.BespokeCatalogBrand).filter_by(
					id=brand_id
				).first()
			)

			self.assertEqual(err, None)
			self.assertEqual(str(brand.id), brand_id)
			self.assertEqual(brand.brand_name, new_brand_name)
			self.assertEqual(str(brand.parent_company_id), company_id)


# 물통 문제 응용 백준 2251번
# 무한으로? ㄴㄴ 큐에 원소들이 모두 빠져나갈때까지 같게 안만들어지면 0 출력
# -> bfs
from collections import deque

def bfs():
    q = deque()
    q.append((A, B))
    check[A][B] = True
    while q:
        x, y = q.popleft()
        z = D-x-y
        if x == y == z:
            print(1)
            return
        for a, b in (x, y), (x, z), (y, z):
            if a < b:
                b -= a
                a += a
            elif a > b:
                a -= b
                b += b
            else:
                continue
            c = D-a-b
            X = min(a, b, c)
            Y = max(a, b, c)
            if not check[X][Y]:
                q.append((X, Y))
                check[X][Y] = True
    print(0)

def solve():
    if D % 3:
        print(0)
        return
    else:
        bfs()

A, B, C = map(int, input().split())
D = A+B+C
check = [[False]*D for _ in range(D)]
solve()


"""Module that let's us add/edit json"""
import json
import csv

# Edit vars below to edit location
DIR_OLD = "data-old/"
DIR_NEW = "data-new/"

# Edit vars below to define input (old) and output (new) file names
FILE_OLD = "all-cards-20230721092742.json"
# FILE_OLD = "unique-artwork-sample.json"
FILE_NEW = "database.csv"

# Pipe old (input) database into var called 'data_old'
# Then create temp array for new (output) json called 'data_new'
with open(DIR_OLD + FILE_OLD, "r", encoding="utf-8") as database:
    data_old = json.load(database)
    data_new = []

def shape_and_write_csv():
    """Transfer only what we need from data_old to data_new"""
    index_count = 0

    with open(DIR_NEW + FILE_NEW, "a", encoding="utf-8") as csvfile:
        fieldnames = ["index", "title", "content", "description"]
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()

    # then write the rows
    for obj in data_old:
        if (
            obj.get("image_uris")
            and obj.get("image_uris").get("border_crop")
            and obj.get("lang") == "en"
            and obj.get("highres_image")
        ):
            content = obj["image_uris"]["border_crop"]
            title = obj["name"] if obj.get("name") else ""
            description = obj["type_line"] if obj.get("type_line") else ""

            with open(DIR_NEW + FILE_NEW, "a", encoding="utf-8") as csvfile:
                writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
                writer.writerow(
                    {
                        "index": index_count,
                        "title": title.encode("unicode_escape").decode("utf-8"),
                        "content": content,
                        "description": description.encode("unicode_escape").decode(
                            "utf-8"
                        ),
                    }
                )

            print("âœ… " + title + " added!")
            index_count += 1
        else:
            print("ðŸ›‘ " + str(index_count) + " skipped!")

def init():
    """initialize file: shape data and write it so json file'"""
    print("shaping data!")
    shape_and_write_csv()

init()


import cv2
import numpy as np

def show_img(img):
    cv2.imshow('image',img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

def handle_light(img,th):
    #处理图片的反光,大于th的像素被填充为77
    ret,mask = cv2.threshold(img,th,255,cv2.THRESH_BINARY)
    inv_mask = cv2.bitwise_not(mask)
    img_h = cv2.bitwise_and(img,img,mask=inv_mask)
    img_temp1 = cv2.add(inv_mask,77)
    img_temp2 = cv2.bitwise_and(img_temp1,img_temp1,mask=mask)
    dst = cv2.add(img_temp2,img_h)
    cv2.imwrite('../data/4.jpg',dst)
    show_img(dst)

def handle(img):
    #gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转换为灰度图
    ret,mask = cv2.threshold(img,0,255,cv2.THRESH_BINARY | cv2.THRESH_OTSU)

def test(img):
    kernel=np.ones((8,8),np.uint8)#构造5x5卷积核
    erosion=cv2.erode(img,kernel,iterations=3) #进行5次腐蚀
    show_img(erosion)
    dilation=cv2.dilate(erosion,kernel,iterations=3)#进行5次膨胀
    show_img(dilation)
    water = img - dilation
    show_img(water)
    #二值化操作
    th1,ret1=cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
    show_img(ret1)
    ret1,contours,hierarchy=cv2.findContours(ret1,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)#轮廓分析
    img_m = cv2.imread('../data/w.jpg')
    for cnt in contours:
        tmp = cv2.contourArea(cnt)
        tempC=cv2.arcLength(cnt,True)
        cv2.drawContours(img_m,[cnt],-1,(255,0,0,),thickness=2)
        print(tmp)#输出液滴面积
    show_img(img_m)

if __name__=='__main__':
    img = cv2.imread('../data/w.jpg',0)
    test(img)
    # th1,ret1=cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
    # show_img(img)
    # show_img(ret1)


from tkinter import *
from functools import partial

def Bresenham(x1, y1, x2, y2):
    tab = []
    dx = abs(x2 - x1)
    dy = abs(y2 - y1)
    x, y = x1, y1
    sx = -1 if x1 > x2 else 1
    sy = -1 if y1 > y2 else 1

    if dx > dy:
        err = dx / 2.0
        while x != x2:
            tab.append((x, y))
            err -= dy
            if err < 0:
                y += sy
                err += dx
            x += sx
    else:
        err = dy / 2.0
        while y != y2:
            tab.append((x, y))
            err -= dx
            if err < 0:
                x += sx
                err += dy
            y += sy

    tab.append((x2, y2))

    return tab

def Prosta(number1, number2, number3, number4):
    w = number1.get()
    z = number2.get()
    q = number3.get()
    t = number4.get()

    x1 = int(w)
    y1 = int(z)
    x2 = int(q)
    y2 = int(t)

    nowytab = Bresenham(x1, y1, x2, y2)
    print(nowytab)

    canvas = Canvas(root,width=400,height=400)
    #canvas.create_line(x1, y1, x2, y2)
    canvas.create_line(2, 2, 2, 400, arrow=LAST)
    canvas.create_line(2, 2, 400, 2, arrow=LAST)

    for d in nowytab:
        canvas.create_line(d[0], d[1], d[0]+1, d[1], fill="#000000")
        canvas.pack(fill=BOTH, expand=1)
        canvas.place(x=20,y=60)

root = Tk()
root.title('Grafika Komputerowa APP')

number1 = StringVar()
number2 = StringVar()
number3 = StringVar()
number4 = StringVar()

content1 = Label(root,text='Wprowadź punkt początkowy    x=           y= ')
content1.place(x=10,y=10)
content2 = Label(root,text='Wprowadź punkt końcowy         x=           y= ')
content2.place(x=10,y=35)

iksy1 = Entry(root, width=3, textvariable=number1)
iksy1.place(x=195,y=10)

igrek1 = Entry(root, width=3, textvariable=number2)
igrek1.place(x=242,y=10)

iksy2 = Entry(root, width=3, textvariable=number3)
iksy2.place(x=195,y=35)

igrek2 = Entry(root, width=3, textvariable=number4)
igrek2.place(x=242,y=35)

prostaWithArguments = partial(Prosta, number1, number2, number3, number4)

action = Button(root,text='Draw',width=20, command=prostaWithArguments)
action.place(x=200,y=470)
clear = Button(root,text='Exit',width=20, command=root.destroy)
clear.place(x=50,y=470)

root.geometry("450x500")
mainloop()


from __future__ import print_function
import datetime
import os.path
from datetime import timedelta
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/calendar.events']

def get_creds():
    """
    Retrieve and return creds to build calendar api service
    """
    creds = None
    # The file token.json stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    if os.path.exists('token.json'):
        creds = Credentials.from_authorized_user_file('token.json', SCOPES)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:

        if creds and creds.expired and creds.refresh_token:
            print('refreshing creds')
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                'credentials.json', SCOPES)
            creds = flow.run_local_server(port=0)
        # Save the credentials for the next run
        with open('token.json', 'w') as token:
            token.write(creds.to_json())
    return creds

def schedule_caledar_event(summary, start, end):
    """
    schedule a calendar event
    """
    creds = get_creds()

    # Build a service object for interacting with the API.
    service = build('calendar', 'v3', credentials=creds)

    event = build_body(summary, start, end)
    # create event
    event = service.events().insert(calendarId='primary', body=event).execute()

    return event

def build_body(summary, start, end):
    """
    build event body
    """
    event = {
        'summary': summary,
        'start': {
            'dateTime': start,
            # central time zone
            'timeZone': 'America/Chicago',
            # 'timeZone': 'UTC'
        },
        'end': {
            'dateTime': end,
            'timeZone': 'America/Chicago',
            # 'timeZone': 'UTC'
        },
    }
    return event

def get_calendar_events(timeMin=datetime.datetime.utcnow().isoformat() + 'Z', timeMax=(datetime.datetime.utcnow() + timedelta(days=9)).isoformat() + 'Z'):
    """
    get calendar events from google api
    for a range of dates
    """

    creds = get_creds()

    # Build a service object for interacting with the API.
    service = build('calendar', 'v3', credentials=creds)

    events_result = service.events().list(calendarId='primary', timeMin=timeMin,
                                        timeMax=timeMax, singleEvents=True,
                                        orderBy='startTime').execute()

    events = events_result.get('items', [])
    return events


'''
检索答案
    full_doc = ["skuid \t attributes \t review  \t counter \t question \t answer"]

'''

import argparse
# import torch
# import transformer.Constants as Constants
# import json
# import math
# from time import time
# import os
# from Util import *
import os
import sys
from datetime import time
from bleu import bleu
from gen_data import *

def read_dict(path, begin=0, end=sys.maxsize):
    t0 = time()
    print("read正在读取", os.path.abspath(path))
    doc = open(path, "r", encoding="utf-8").read().splitlines()
    print(time() - t0, "秒读出", len(doc), "条")
    t0 = time()
    sku_dict = {}
    for i in range(len(doc)):
        if i < begin:
            continue
        if i > end:
            break
        sents = doc[i].split("\t")
        if sents[0] in sku_dict:
            sku_dict[sents[0]].append(sents[4:])
        else:
            sku_dict[sents[0]] = [sents[4:]]

    print(time() - t0, "秒拆开")
    return sku_dict

def read_qst(path):
    questions = []
    doc = open(path, "r", encoding="utf-8").read().splitlines()
    for line in doc:
        sents = line.split("\t")  # skuid attr review hot qs ans
        questions.append([sents[0], sents[4], sents[5]])
    return questions

def split_grams(tokens, n_gram=2):
    if(isinstance(tokens,str)):
        tokens = tokenize(tokens)
    if len(tokens) <= n_gram:
        return tokens
    grams = []
    for i in range(len(tokens) - n_gram):
        grams.append(''.join(tokens[i:i + n_gram]))
    return grams

def split_grams0(line, n_gram=2):
    tokens = tokenize(line)
    grams = [tokens[0]]
    if len(tokens) <= 1:
        return grams
    for i in range(1, len(tokens)):
        grams.append(tokens[i - 1:i + 1])
    return grams

def cdrate(q, k, ngram=2):
    if (not isinstance(q, list) or not isinstance(k, list)):
        print(" not all list!", k, q)
        return -1000000
    # comprehensive + determine -->  cdscore
    q = split_grams(q, ngram)
    k = split_grams(k, ngram)
    k = list(k)
    klen = len(k)
    if (klen == 0 or len(q) == 0):
        print("klen==0 or len(q)==0", k, q)
        return 0
    for gram in q:
        if gram in k:
            k.remove(gram)  # affect
        else:
            continue
    common = klen - len(k)
    # crate=common/len(q)
    # drate=common/klen
    # f1score=2*cdrate*drate/(cdrate+drate)
    score = 2.0 * common / (len(q) + klen)
    return score

def cdscore(hypothes, references, maxgrams=4):
    # 多个候选取平均
    total_score = 0
    for hypoth in hypothes:
        score = 0
        for reference in references:
            avg_gram = 0
            for i in range(1, 1 + maxgrams):
                avg_gram += cdrate(hypoth, reference, i)
            avg_gram /= maxgrams
            if avg_gram > score:
                score = avg_gram
        total_score += score
    total_score /= len(hypothes)
    return total_score

def nearest_asw(question, doc):  # question skuid q a
    re = ['no_question', 'no_answer', -1]  # lib_qs lib_ans score
    score = -1
    for pair in doc:  # q a
        tmp = cdrate(question, pair[0])
        if tmp > score:
            score = tmp
            re = pair + [score]
    return re

def answer(sku_dict, questions):
    n_answered, avg_score = 1, 0
    for i in range(len(questions)):  # modify var
        qpair = questions[i]  # skuid q a
        re = ['no_question', 'no_answer', -1]  # lib_qs lib_ans score
        if qpair[0] in sku_dict:
            re = nearest_asw(qpair[1], sku_dict[qpair[0]])
            if re[2] > 0:  # 大量不存在相似问题
                n_answered += 1
                avg_score += re[2]
        line = qpair[1:] + re[:-1] + [str(re[-1])]
        line = "\t".join(line)  # query_q query_a lib_q lib_a score
        questions[i] = line
    avg_score /= n_answered
    print("问题总计", len(questions), "可检索", n_answered, "平均分数", avg_score)
    return questions

def main():
    dir = "../data/jd/pure"
    sku_dict = read_dict(dir + "/train.txt")  # dict[id]=[q,a]
    questions = read_qst(dir + "/test.txt")  # [id,q]
    answers = answer(sku_dict, questions)
    with open("output/answers.txt", "w", encoding="utf-8") as f:
        f.write("\n".join(answers))
    print()

def test():
    # hypotheses = ["The brown fox jumps over the dog 笑"]
    # references = ["The quick brown fox jumps over the lazy dog 笑"]
    hypotheses = "我 的 的 买 次 天 ， 的 个 还 天 段 没 点 个 点"
    references = "我 们 刚 一 周 二 段 转 的 三 段 ， 二 段 还 有 一 点 点"
    # hypotheses = ["It is a guide to action which ensures that the military always obeys the commands of the party."]
    # references = ["It is a guide to action that ensures that the military will forever heed Party commands.","It is the guiding principle which guarantees the military forces always being under the command of the Party.","It is the practical guide for the army always to heed the directions of the party."]

    gram = 5
    for i in range(1, gram):
        cd_score = cdscore([hypotheses.strip().split()], [references.strip().split()], i)
        print(" cdscore  ngram:" + str(i) + "-->" + str(cd_score))
    bleu_score = bleu([hypotheses.strip().split()], [references.strip().split()])
    print(" bleu  score-->" + str(bleu_score))

if __name__ == '__main__':
    t0 = time()
    # main()
    test()
    print(time() - t0, "秒执行完main()")


# queue_pywin.py
# to be revised with default config and config init like elsewhere
from matplotlib.pyplot import text
import pygame as pg
from settings import *
import random
import math
import numpy

vec = pg.math.Vector2

class Fix_sprite(pg.sprite.Sprite):
    """
    fix postion sprite
    """

    def __init__(self, win, tmx_object, layer, **kwargs):
        self._layer = layer
        self.groups = win.all_sprites, win.background_group
        pg.sprite.Sprite.__init__(self, self.groups)
        self.win = win
        self.tmx_object = tmx_object

        self.x = self.tmx_object.x
        self.y = self.tmx_object.y
        self.width = self.tmx_object.width
        self.height = self.tmx_object.height
        self.pos = vec(self.x, self.y)
        self.rot = self.tmx_object.rotation

        self.image = self.win.map.tmxdata.get_tile_image_by_gid(self.tmx_object.gid)

        self.image = pg.transform.scale(
            self.image,
            (self.width, self.height),
        )

        self.image = pg.transform.rotate(
            self.image,
            -self.rot,
        )

        # dirty fix for stupid tiled/pygame rotation difference
        if self.rot == -90:
            self.x -= self.height
            self.y += self.height - self.width

        if self.rot == 90:
            self.y += self.height

        self.image_ini = self.image.copy()

    def update(self):
        pass

class Mob_fixed(pg.sprite.Sprite):
    """
    fix postion mob to be used elsewhere
    """

    def __init__(self, win, x0, y0, n, **kwargs):
        self._layer = MOB_LAYER
        self.groups = win.all_sprites, win.mobs
        pg.sprite.Sprite.__init__(self, self.groups)
        self.win = win
        self.n = n
        self.x = x0
        self.y = y0
        self.pos = vec(self.x, self.y)
        self.width = 0.8 * TILESIZE
        self.heigth = 0.8 * TILESIZE

        self.image = win.scaled_mob_img.copy()

        if self.win.zoom > 0.5:
            self.draw_tag()

    def draw_tag(self):
        text_tag = self.win.tag_font.render("{}".format(self.n), True, TAG_COLOR)
        text_rect = text_tag.get_rect(center=self.image.get_rect().center)
        self.image.blit(text_tag, text_rect)

    def update(self):
        # killed every frame
        self.kill()

class Mob_variable(pg.sprite.Sprite):
    """
    Mob spawns at (x0,y0), goes to a "queue" and disappears
    can also be used as a wandering mob
    can also be used to go to point
    """

    def __init__(self, win, x0, y0, n, target_queue, **kwargs):
        self._layer = MOB_LAYER
        self.groups = win.all_sprites, win.mobs, win.moving_mobs
        pg.sprite.Sprite.__init__(self, self.groups)
        self.win = win
        self.x = x0
        self.y = y0
        self.n = n
        self.queue = target_queue
        self.arrived = False
        self.wandering = False
        self.goto_mode = False

        # kwargs
        self.__dict__.update(kwargs)

        # image and initial position and hitbox
        self.image = win.scaled_mob_img.copy()
        self.pos = vec(self.x, self.y)
        self.vel = vec(random.uniform(-1, 1), random.uniform(-1, 1)).normalize()
        self.acc = vec(0, 0)
        self.rot = 0

    def goto(self, x, y):
        target = vec(x, y)
        target_dist = target - self.pos

        # go to target
        self.rot = target_dist.angle_to(vec(1, 0))
        self.image = pg.transform.rotate(self.win.scaled_mob_img, self.rot)
        self.rect = self.image.get_rect()
        self.rect.center = self.pos
        self.acc = vec(MOB_ACC, 0).rotate(-self.rot)

        self.vel += self.acc * self.win.dt
        if self.vel.length() > MOB_MAX_SPEED:
            self.vel.scale_to_length(MOB_MAX_SPEED)
        self.pos += self.vel * self.win.dt
        (self.x, self.y) = self.pos

        target_dist = target - self.pos
        return target_dist

    def wander(self):
        circle_pos = self.pos + self.vel.normalize() * WANDER_RING_DISTANCE
        target = circle_pos + vec(WANDER_RING_RADIUS, 0).rotate(random.uniform(0, 360))
        self.goto(*target)

    def goto_queue(self):
        target = self.queue.dct_centers_overflow[len(self.queue.pax_list)]
        target_dist = self.goto(*target)

        if target_dist.length() < REACH_TOLERANCE * TILESIZE:
            self.kill()
            self.arrived = True

    def goto_point(self):
        target = self.target_point
        target_dist = self.goto(*target)

        if target_dist.length() < REACH_TOLERANCE * TILESIZE:
            self.kill()
            self.arrived = True

    def update(self):
        if self.wandering == True:
            self.wander()
        elif self.goto_mode == True:
            self.goto_point()
        else:
            self.goto_queue()

        if self.win.zoom > 0.5:
            self.draw_tag()

    def draw_tag(self):
        text_tag = self.win.tag_font.render("{}".format(self.n), True, TAG_COLOR)
        text_rect = text_tag.get_rect(center=self.image.get_rect().center)
        self.image.blit(text_tag, text_rect)

class Snake_queue(pg.sprite.Sprite):
    def __init__(self, win, x, y, config={}):
        self._layer = QUEUE_LAYER
        self.groups = win.all_sprites, win.snake_queues
        pg.sprite.Sprite.__init__(self, self.groups)

        self.win = win

        # Set default configurations
        self.set_default_config()

        # Update configurations
        for attr, val in config.items():
            setattr(self, attr, val)

        # define position and image
        self.x = x
        self.y = y
        self.pos = vec(x, y)
        self.queue_dir_vec = vec(self.queue_dir_point.x, self.queue_dir_point.y) - vec(
            self.x_exit, self.y_exit
        )

        self.update_dct_centers_test()

    def set_default_config(self):
        self.height = 4 * TILESIZE
        self.width = 15 * TILESIZE
        self.overflow_ratio = 100
        self.pax_list = []
        self.child_sprites = []
        self.queue_dir_vec = vec(0, -1) * TILESIZE

    def update_dct_centers(self):
        # dict with the coordinates of each waiting position, including overflow
        dct_centers_overflow = {
            n: (0, 0) for n in range(self.overflow_ratio * self.N_position_total)
        }
        for n in dct_centers_overflow:
            row = n // self.N_position_1fold
            if row % 2 == 0:
                col = (n) % self.N_position_1fold
            else:
                col = self.N_position_1fold - ((n) % self.N_position_1fold) - 1
            x = self.x + col * TILESIZE
            y = self.y + self.height - (row + 1) * TILESIZE
            dct_centers_overflow[n] = vec(int(x), int(y))

        self.dct_centers_overflow = dct_centers_overflow

    def update_dct_centers_test(self):
        # dict with the coordinates of each waiting position, including overflow
        dct_centers_overflow = {}
        dct_centers_overflow[0] = vec(self.x_exit, self.y_exit)

        # define the queue fold depending on its direction
        if self.queue_dir_vec * vec(1, 0) == 0:
            self.N_fold = self.height // TILESIZE
            self.N_position_1fold = self.width // TILESIZE

        if self.queue_dir_vec * vec(0, 1) == 0:
            self.N_fold = self.width // TILESIZE
            self.N_position_1fold = self.height // TILESIZE

        self.N_position_total = self.N_fold * self.N_position_1fold

        # calculate queue parameters
        next_fold_vec = self.queue_dir_vec

        # prendre les deux vecteurs unitaire orthogonaux au self.queue_dir
        next_pos_vec_candidates = (
            self.queue_dir_vec.rotate(90),
            self.queue_dir_vec.rotate(-90),
        )
        # selectionner celui qui n'est pas hors du rectangle
        found_next_pos_vec = False
        for next_pos_vec_candidate in next_pos_vec_candidates:
            next_pos = dct_centers_overflow[0] + next_pos_vec_candidate
            if (self.x <= next_pos.x < self.x + self.width) and (
                self.y <= next_pos.y < self.y + self.height
            ):
                next_pos_vec = next_pos_vec_candidate
                found_next_pos_vec = True
                print("found next pos vec = {}".format(next_pos_vec))

        # si on trouve pas, c'est que c'est one column
        if found_next_pos_vec == False:
            next_pos_vec = self.queue_dir_vec

        # loop through all the positions
        dct_centers_overflow[0] = vec(self.x_exit, self.y_exit)
        for n in range(1, self.overflow_ratio * self.N_position_total):
            if n % self.N_position_1fold == 0:
                next_pos_vec = -next_pos_vec
                dct_centers_overflow[n] = dct_centers_overflow[n - 1] + next_fold_vec
            else:
                dct_centers_overflow[n] = dct_centers_overflow[n - 1] + next_pos_vec

        self.dct_centers_overflow = dct_centers_overflow
        print(self.dct_centers_overflow[0], self.x_exit, self.y_exit)

    # def change_config(self, x, y, N_position_1fold, N_fold):
    #     # define position and image
    #     self.x = x
    #     self.y = y
    #     self.N_position_1fold = N_position_1fold
    #     self.N_fold = N_fold
    #     self.pos = vec(x, y) * TILESIZE
    #     self.width = N_position_1fold
    #     self.height = N_fold
    #     self.x_exit = self.x
    #     self.y_exit = self.y + self.height

    #     self.N_position_total = self.N_fold * self.N_position_1fold
    #     self.image = pg.Surface(
    #         (
    #             self.width * TILESIZE * self.win.zoom,
    #             self.height * TILESIZE * self.win.zoom,
    #         )
    #     )
    #     self.image.fill(SNAKE_QUEUE_COLOR)
    #     self.update_dct_centers()

    def empty_queue_to_wander(self):
        self.pax_list = []
        pass

    def update(self):
        # update the number of people queueing
        for sprite in self.child_sprites:
            sprite.kill()
            del sprite
        self.child_sprites = []
        for index, n in enumerate(self.pax_list):
            pos = self.dct_centers_overflow[index]
            self.child_sprites.append(Mob_fixed(self.win, pos.x, pos.y, n))