from __future__ import division
import math
import time
import tqdm
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import torchvision
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches

def to_cpu(tensor):
    return tensor.detach().cpu()

def load_classes(path):
    """
    Loads class labels at 'path'
    """
    fp = open(path, "r")
    names = fp.read().split("\n")[:-1]
    return names

def weights_init_normal(m):
    classname = m.__class__.__name__
    if classname.find("Conv") != -1:
        torch.nn.init.normal_(m.weight.data, 0.0, 0.02)
    elif classname.find("BatchNorm2d") != -1:
        torch.nn.init.normal_(m.weight.data, 1.0, 0.02)
        torch.nn.init.constant_(m.bias.data, 0.0)

def rescale_boxes(boxes, current_dim, original_shape):
    """ Rescales bounding boxes to the original shape """
    orig_h, orig_w = original_shape
    # The amount of padding that was added
    pad_x = max(orig_h - orig_w, 0) * (current_dim / max(original_shape))
    pad_y = max(orig_w - orig_h, 0) * (current_dim / max(original_shape))
    # Image height and width after padding is removed
    unpad_h = current_dim - pad_y
    unpad_w = current_dim - pad_x
    # Rescale bounding boxes to dimension of original image
    boxes[:, 0] = ((boxes[:, 0] - pad_x // 2) / unpad_w) * orig_w
    boxes[:, 1] = ((boxes[:, 1] - pad_y // 2) / unpad_h) * orig_h
    boxes[:, 2] = ((boxes[:, 2] - pad_x // 2) / unpad_w) * orig_w
    boxes[:, 3] = ((boxes[:, 3] - pad_y // 2) / unpad_h) * orig_h
    return boxes

def xywh2xyxy(x):
    y = x.new(x.shape)
    y[..., 0] = x[..., 0] - x[..., 2] / 2
    y[..., 1] = x[..., 1] - x[..., 3] / 2
    y[..., 2] = x[..., 0] + x[..., 2] / 2
    y[..., 3] = x[..., 1] + x[..., 3] / 2
    return y

def xywh2xyxy_np(x):
    y = np.zeros_like(x)
    y[..., 0] = x[..., 0] - x[..., 2] / 2
    y[..., 1] = x[..., 1] - x[..., 3] / 2
    y[..., 2] = x[..., 0] + x[..., 2] / 2
    y[..., 3] = x[..., 1] + x[..., 3] / 2
    return y

def ap_per_class(tp, conf, pred_cls, target_cls):
    """ Compute the average precision, given the recall and precision curves.
    Source: https://github.com/rafaelpadilla/Object-Detection-Metrics.
    # Arguments
        tp:    True positives (list).
        conf:  Objectness value from 0-1 (list).
        pred_cls: Predicted object classes (list).
        target_cls: True object classes (list).
    # Returns
        The average precision as computed in py-faster-rcnn.
    """

    # Sort by objectness
    i = np.argsort(-conf)
    tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]

    # Find unique classes
    unique_classes = np.unique(target_cls)

    # Create Precision-Recall curve and compute AP for each class
    ap, p, r = [], [], []
    for c in tqdm.tqdm(unique_classes, desc="Computing AP"):
        i = pred_cls == c
        n_gt = (target_cls == c).sum()  # Number of ground truth objects
        n_p = i.sum()  # Number of predicted objects

        if n_p == 0 and n_gt == 0:
            continue
        elif n_p == 0 or n_gt == 0:
            ap.append(0)
            r.append(0)
            p.append(0)
        else:
            # Accumulate FPs and TPs
            fpc = (1 - tp[i]).cumsum()
            tpc = (tp[i]).cumsum()

            # Recall
            recall_curve = tpc / (n_gt + 1e-16)
            r.append(recall_curve[-1])

            # Precision
            precision_curve = tpc / (tpc + fpc)
            p.append(precision_curve[-1])

            # AP from recall-precision curve
            ap.append(compute_ap(recall_curve, precision_curve))

    # Compute F1 score (harmonic mean of precision and recall)
    p, r, ap = np.array(p), np.array(r), np.array(ap)
    f1 = 2 * p * r / (p + r + 1e-16)

    return p, r, ap, f1, unique_classes.astype("int32")

def compute_ap(recall, precision):
    """ Compute the average precision, given the recall and precision curves.
    Code originally from https://github.com/rbgirshick/py-faster-rcnn.

    # Arguments
        recall:    The recall curve (list).
        precision: The precision curve (list).
    # Returns
        The average precision as computed in py-faster-rcnn.
    """
    # correct AP calculation
    # first append sentinel values at the end
    mrec = np.concatenate(([0.0], recall, [1.0]))
    mpre = np.concatenate(([0.0], precision, [0.0]))

    # compute the precision envelope
    for i in range(mpre.size - 1, 0, -1):
        mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])

    # to calculate area under PR curve, look for points
    # where X axis (recall) changes value
    i = np.where(mrec[1:] != mrec[:-1])[0]

    # and sum (\Delta recall) * prec
    ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
    return ap

def get_batch_statistics(outputs, targets, iou_threshold):
    """ Compute true positives, predicted scores and predicted labels per sample """
    batch_metrics = []
    for sample_i in range(len(outputs)):

        if outputs[sample_i] is None:
            continue

        output = outputs[sample_i]
        pred_boxes = output[:, :4]
        pred_scores = output[:, 4]
        pred_labels = output[:, -1]

        true_positives = np.zeros(pred_boxes.shape[0])

        annotations = targets[targets[:, 0] == sample_i][:, 1:]
        target_labels = annotations[:, 0] if len(annotations) else []
        if len(annotations):
            detected_boxes = []
            target_boxes = annotations[:, 1:]

            for pred_i, (pred_box, pred_label) in enumerate(zip(pred_boxes, pred_labels)):

                # If targets are found break
                if len(detected_boxes) == len(annotations):
                    break

                # Ignore if label is not one of the target labels
                if pred_label not in target_labels:
                    continue

                iou, box_index = bbox_iou(pred_box.unsqueeze(0), target_boxes).max(0)
                if iou >= iou_threshold and box_index not in detected_boxes:
                    true_positives[pred_i] = 1
                    detected_boxes += [box_index]
        batch_metrics.append([true_positives, pred_scores, pred_labels])
    return batch_metrics

def bbox_wh_iou(wh1, wh2):
    wh2 = wh2.t()
    w1, h1 = wh1[0], wh1[1]
    w2, h2 = wh2[0], wh2[1]
    inter_area = torch.min(w1, w2) * torch.min(h1, h2)
    union_area = (w1 * h1 + 1e-16) + w2 * h2 - inter_area
    return inter_area / union_area

def bbox_iou(box1, box2, x1y1x2y2=True):
    """
    Returns the IoU of two bounding boxes
    """
    if not x1y1x2y2:
        # Transform from center and width to exact coordinates
        b1_x1, b1_x2 = box1[:, 0] - box1[:, 2] / 2, box1[:, 0] + box1[:, 2] / 2
        b1_y1, b1_y2 = box1[:, 1] - box1[:, 3] / 2, box1[:, 1] + box1[:, 3] / 2
        b2_x1, b2_x2 = box2[:, 0] - box2[:, 2] / 2, box2[:, 0] + box2[:, 2] / 2
        b2_y1, b2_y2 = box2[:, 1] - box2[:, 3] / 2, box2[:, 1] + box2[:, 3] / 2
    else:
        # Get the coordinates of bounding boxes
        b1_x1, b1_y1, b1_x2, b1_y2 = box1[:, 0], box1[:, 1], box1[:, 2], box1[:, 3]
        b2_x1, b2_y1, b2_x2, b2_y2 = box2[:, 0], box2[:, 1], box2[:, 2], box2[:, 3]

    # get the corrdinates of the intersection rectangle
    inter_rect_x1 = torch.max(b1_x1, b2_x1)
    inter_rect_y1 = torch.max(b1_y1, b2_y1)
    inter_rect_x2 = torch.min(b1_x2, b2_x2)
    inter_rect_y2 = torch.min(b1_y2, b2_y2)
    # Intersection area
    inter_area = torch.clamp(inter_rect_x2 - inter_rect_x1 + 1, min=0) * torch.clamp(
        inter_rect_y2 - inter_rect_y1 + 1, min=0
    )
    # Union Area
    b1_area = (b1_x2 - b1_x1 + 1) * (b1_y2 - b1_y1 + 1)
    b2_area = (b2_x2 - b2_x1 + 1) * (b2_y2 - b2_y1 + 1)

    iou = inter_area / (b1_area + b2_area - inter_area + 1e-16)

    return iou

def build_targets(pred_boxes, pred_cls, target, anchors, ignore_thres):

    BoolTensor = torch.cuda.BoolTensor if pred_boxes.is_cuda else torch.BoolTensor
    FloatTensor = torch.cuda.FloatTensor if pred_boxes.is_cuda else torch.FloatTensor

    nB = pred_boxes.size(0)
    nA = pred_boxes.size(1)
    nC = pred_cls.size(-1)
    nG = pred_boxes.size(2)

    # Output tensors
    obj_mask = BoolTensor(nB, nA, nG, nG).fill_(0)
    noobj_mask = BoolTensor(nB, nA, nG, nG).fill_(1)
    class_mask = FloatTensor(nB, nA, nG, nG).fill_(0)
    iou_scores = FloatTensor(nB, nA, nG, nG).fill_(0)
    tx = FloatTensor(nB, nA, nG, nG).fill_(0)
    ty = FloatTensor(nB, nA, nG, nG).fill_(0)
    tw = FloatTensor(nB, nA, nG, nG).fill_(0)
    th = FloatTensor(nB, nA, nG, nG).fill_(0)
    tcls = FloatTensor(nB, nA, nG, nG, nC).fill_(0)

    # Convert to position relative to box
    target_boxes = target[:, 2:6] * nG
    gxy = target_boxes[:, :2]
    gwh = target_boxes[:, 2:]
    # Get anchors with best iou
    ious = torch.stack([bbox_wh_iou(anchor, gwh) for anchor in anchors])
    best_ious, best_n = ious.max(0)
    # Separate target values
    b, target_labels = target[:, :2].long().t()
    gx, gy = gxy.t()
    gw, gh = gwh.t()
    gi, gj = gxy.long().t()
    # Set masks
    obj_mask[b, best_n, gj, gi] = 1
    noobj_mask[b, best_n, gj, gi] = 0

    # Set noobj mask to zero where iou exceeds ignore threshold
    for i, anchor_ious in enumerate(ious.t()):
        noobj_mask[b[i], anchor_ious > ignore_thres, gj[i], gi[i]] = 0

    # Coordinates
    tx[b, best_n, gj, gi] = gx - gx.floor()
    ty[b, best_n, gj, gi] = gy - gy.floor()
    # Width and height
    tw[b, best_n, gj, gi] = torch.log(gw / anchors[best_n][:, 0] + 1e-16)
    th[b, best_n, gj, gi] = torch.log(gh / anchors[best_n][:, 1] + 1e-16)
    # One-hot encoding of label
    tcls[b, best_n, gj, gi, target_labels] = 1
    # Compute label correctness and iou at best anchor
    class_mask[b, best_n, gj, gi] = (pred_cls[b, best_n, gj, gi].argmax(-1) == target_labels).float()
    iou_scores[b, best_n, gj, gi] = bbox_iou(pred_boxes[b, best_n, gj, gi], target_boxes, x1y1x2y2=False)

    tconf = obj_mask.float()
    return iou_scores, class_mask, obj_mask, noobj_mask, tx, ty, tw, th, tcls, tconf

def box_iou(box1, box2):
    # https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py
    """
    Return intersection-over-union (Jaccard index) of boxes.
    Both sets of boxes are expected to be in (x1, y1, x2, y2) format.
    Arguments:
        box1 (Tensor[N, 4])
        box2 (Tensor[M, 4])
    Returns:
        iou (Tensor[N, M]): the NxM matrix containing the pairwise
            IoU values for every element in boxes1 and boxes2
    """

    def box_area(box):
        # box = 4xn
        return (box[2] - box[0]) * (box[3] - box[1])

    area1 = box_area(box1.T)
    area2 = box_area(box2.T)

    # inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2)
    inter = (torch.min(box1[:, None, 2:], box2[:, 2:]) - torch.max(box1[:, None, :2], box2[:, :2])).clamp(0).prod(2)
    return inter / (area1[:, None] + area2 - inter)  # iou = inter / (area1 + area2 - inter)

def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, labels=()):
    """Performs Non-Maximum Suppression (NMS) on inference results
    Returns:
         detections with shape: nx6 (x1, y1, x2, y2, conf, cls)
    """

    nc = prediction.shape[2] - 5  # number of classes
    xc = prediction[..., 4] > conf_thres  # candidates

    # Settings
    min_wh, max_wh = 2, 4096  # (pixels) minimum and maximum box width and height
    max_det = 300  # maximum number of detections per image
    max_nms = 30000  # maximum number of boxes into torchvision.ops.nms()
    time_limit = 1.0  # seconds to quit after
    redundant = True  # require redundant detections
    multi_label = nc > 1  # multiple labels per box (adds 0.5ms/img)
    merge = False  # use merge-NMS

    t = time.time()
    output = [torch.zeros((0, 6), device=prediction.device)] * prediction.shape[0]
    for xi, x in enumerate(prediction):  # image index, image inference
        # Apply constraints
        # x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0  # width-height
        x = x[xc[xi]]  # confidence

        # Cat apriori labels if autolabelling
        if labels and len(labels[xi]):
            l = labels[xi]
            v = torch.zeros((len(l), nc + 5), device=x.device)
            v[:, :4] = l[:, 1:5]  # box
            v[:, 4] = 1.0  # conf
            v[range(len(l)), l[:, 0].long() + 5] = 1.0  # cls
            x = torch.cat((x, v), 0)

        # If none remain process next image
        if not x.shape[0]:
            continue

        # Compute conf
        x[:, 5:] *= x[:, 4:5]  # conf = obj_conf * cls_conf

        # Box (center x, center y, width, height) to (x1, y1, x2, y2)
        box = xywh2xyxy(x[:, :4])

        # Detections matrix nx6 (xyxy, conf, cls)
        if multi_label:
            i, j = (x[:, 5:] > conf_thres).nonzero(as_tuple=False).T
            x = torch.cat((box[i], x[i, j + 5, None], j[:, None].float()), 1)
        else:  # best class only
            conf, j = x[:, 5:].max(1, keepdim=True)
            x = torch.cat((box, conf, j.float()), 1)[conf.view(-1) > conf_thres]

        # Filter by class
        if classes is not None:
            x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)]

        # Apply finite constraint
        # if not torch.isfinite(x).all():
        #     x = x[torch.isfinite(x).all(1)]

        # Check shape
        n = x.shape[0]  # number of boxes
        if not n:  # no boxes
            continue
        elif n > max_nms:  # excess boxes
            x = x[x[:, 4].argsort(descending=True)[:max_nms]]  # sort by confidence

        # Batched NMS
        c = x[:, 5:6] * (0 if agnostic else max_wh)  # classes
        boxes, scores = x[:, :4] + c, x[:, 4]  # boxes (offset by class), scores
        i = torchvision.ops.nms(boxes, scores, iou_thres)  # NMS
        if i.shape[0] > max_det:  # limit detections
            i = i[:max_det]
        if merge and (1 < n < 3E3):  # Merge NMS (boxes merged using weighted mean)
            # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4)
            iou = box_iou(boxes[i], boxes) > iou_thres  # iou matrix
            weights = iou * scores[None]  # box weights
            x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True)  # merged boxes
            if redundant:
                i = i[iou.sum(1) > 1]  # require redundancy

        output[xi] = x[i]
        if (time.time() - t) > time_limit:
            print(f'WARNING: NMS time limit {time_limit}s exceeded')
            break  # time limit exceeded

    return output


from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase

from ledger.forms import CSVUploadForm

class CSVUploadFormTest(TestCase):
    def test_valid_csv_file(self):
        file_data = b"name,age\nAlice,30\nBob,40\n"
        csv_file = SimpleUploadedFile("test.csv", file_data, content_type="text/csv")
        data = {"csv_file": csv_file}
        form = CSVUploadForm({}, data)
        self.assertTrue(form.is_valid())

    def test_invalid_csv_file(self):
        file_data = b"name,age\nAlice,30\nBob,40\n"
        text_file = SimpleUploadedFile("test.txt", file_data, content_type="text/plain")
        data = {"csv_file": text_file}
        form = CSVUploadForm({}, data)
        self.assertFalse(form.is_valid())
        self.assertEqual(
            form.errors["csv_file"],
            ["Invalid file format: Only CSV files are allowed."],
        )


# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt

# 拡張ラグランジュ
def ala(l0, c, max_itr=20):
    nl = l0
    l_ls = [l0]
    x_ls = []
    y_ls = []
    # c = c0 + i * 2.0
    for _ in range(max_itr):
        l = nl
        x = (c - l)/(6+5*c/2)
        y = (c - l)/(4+5*c/3)
        nl = l + c*(x + y - 1.0)
        l_ls.append(nl)
        x_ls.append(x)
        y_ls.append(y)
        if abs(nl - l) <= 0.01:
            break
    return l_ls, x_ls, y_ls

# 初期値設定
l0 = 0.0
c0 = 2.0

fig, axes = plt.subplots(nrows=5, ncols=2, figsize=(10, 18))

for i in range(5):
    c = i * 2 + c0
    l_ls, x_ls, y_ls = ala(l0, c)

    axes[i, 0].plot(x_ls,y_ls,marker='o')
    for t, (x, y) in enumerate(zip(x_ls, y_ls)):
        axes[i, 0].annotate(t+1, (x-0.03, y+0.03))
    axes[i, 0].set_xlabel('$x$')
    axes[i, 0].set_ylabel('$y$')
    n = 100
    x = np.linspace(-0.2,1.2,n)
    y = np.linspace(-0.2,1.2,n)
    X, Y = np.meshgrid(x,y)
    Z = 3*X**2 + 2*Y**2
    axes[i, 0].contour(X,Y,Z,20)
    axes[i, 0].set_aspect('equal')
    axes[i, 0].set_title('$c = %.1f$' % c)

    time = range(len(l_ls))
    axes[i, 1].plot(time,l_ls,marker='o')
    axes[i, 1].set_xlabel('iteration')
    axes[i, 1].set_ylabel('$\lambda$')
    axes[i, 1].set_title('$c = %.1f$' % c)

fig.tight_layout()
plt.savefig("output.png")


from collections.abc import Callable, Iterable, Iterator
from collections import namedtuple
import os
import csv
from typing import NamedTuple
import utilparse as prs
from itertools import compress

def iter_csv(file_path:str, skip_header=True)->Iterator:
    with open(file_path, 'r', newline='') as file:
        skip_header and next(file)
        yield from csv.reader(file, delimiter=',', quotechar='"')

def get_filename(file_path:str)->str:
    return os.path.splitext(
        os.path.basename(file_path)
    )[0]

def read_file(
        file_path: str,
        parser: Callable = lambda x: x,
    )->Iterator:
    return prs.parse_lines(iter_csv(file_path), parser)

# ====================================
#  FROM CONFIG
# ====================================

from constants import (
    FPATH, # file path
    TPNAME, # tuplename
    TPFIELDS, # tuplename
    DTYPES, # data types
    MASK, # bool filter to compress imputs
)
def make_namedtuple_from_config(
        cfg:dict, name:str = None,
        fields:Iterable=None)->NamedTuple:
    tuplename = name or cfg.get(TPNAME, None) \
        or prs.std_tuplename(get_filename(cfg.get(FPATH)))
    tuplefields = fields or cfg.get(TPFIELDS, None) \
        or map(
            prs.std_fieldname,
            next(iter_csv(
                cfg.get(FPATH),
                skip_header=False
            ))
        )
    return namedtuple(tuplename, tuplefields)

def make_extractor_from_config(cfg:dict)->Callable:
    return prs.make_extractor(
        dtypes = cfg.get(DTYPES, None),
        mask = cfg.get(MASK, None)
    )

def make_parser_from_config(cfg:dict)->Callable:
    ntp_class = make_namedtuple_from_config(cfg)
    extractor = make_extractor_from_config(cfg)
    return prs.make_lineparser(ntp_class, extractor)

def from_config(cfg:dict)->Iterator:
    file_path = cfg.get(FPATH)
    parser = make_parser_from_config(cfg)
    return read_file(file_path, parser)

# ====================================
#  OBJECT ORIENTED
# ====================================

class Filereader():
    def __init__(self, file_path:str, cast_types:tuple)->None:
        self.file_path = file_path
        self.cast_types = cast_types
        self._Tupleclass = None

    @property
    def Tupleclass(self)->NamedTuple:
        if self._Tupleclass is None:
            header = next(iter_csv(self.file_path, skip_header=False))
            tuplename = prs.std_tuplename(prs.get_filename(self.file_path))
            self._Tupleclass = namedtuple(tuplename,
                tuple(map(prs.std_fieldname, header))
            )
        return self._Tupleclass

    def __iter__(self)->Iterator:
        for row in iter_csv(self.file_path):
            yield self.Tupleclass(*tuple(
                cast(val) for val, cast
                in zip(row, self.cast_types)
            ))


from flask import Blueprint, session, request, jsonify
from app.models import Artist, Album

artist_routes = Blueprint("artists", __name__)

@artist_routes.route("/")
def get_all_artists():
    everyArtist = Artist.query.all()
    artist_dict = [artist.to_dict(albums=True) for artist in everyArtist]
    return {"artists": artist_dict}

@artist_routes.route("/<int:id>/")
def get_artist(id):
    oneArtist = Artist.query.get(id)
    artist_dict = oneArtist.to_dict(albums=True)
    return artist_dict

@artist_routes.route("/<int:id>/songs")
def songs_of_anArtist(id):
    artist = Artist.query.get(id)
    artist_dict = artist.to_dict(songs=True)
    return artist_dict


# coding: utf-8

import sys
import os
import traceback
import json

# 実行スクリプトのパスを取得して、追加
current_path = os.path.abspath(os.path.dirname(__file__))
sys.path.append(current_path)
sys.path.append(current_path + "/../trade_algorithm")
sys.path.append(current_path + "/../obj")
sys.path.append(current_path + "/../lib")
sys.path.append(current_path + "/../lstm_lib")

from mysql_connector import MysqlConnector
from datetime import datetime, timedelta

con = MysqlConnector()

instrument_list = ["EUR_GBP", "EUR_USD", "EUR_JPY", "GBP_USD", "GBP_JPY", "USD_JPY"]
#instrument_list = ["EUR_USD", "EUR_JPY", "GBP_USD", "GBP_JPY", "USD_JPY"]
insert_time = '2019-06-13 14:00:00'
insert_time = datetime.strptime(insert_time, "%Y-%m-%d %H:%M:%S")
now = datetime.now()

while insert_time < now:
    result_list = []
    for instrument in instrument_list:
        sql = "select open_ask, open_bid, close_ask, close_bid, insert_time from %s_30m_TABLE where insert_time < '%s' order by insert_time desc limit 1" % (instrument, insert_time)
        response = con.select_sql(sql)

        open_price = (response[0][0]+response[0][1])/2
        close_price = (response[0][2]+response[0][3])/2
        result = close_price/open_price
        res_insert_time = response[0][4]
        result_list.append(result)

#        print("%s : %s = %s" % (res_insert_time, instrument, result))
#        print("========================================")

    if result_list[0] > 1.0 and result_list[1] > 1.0 and result_list[2] > 1.0 and result_list[5] > 1.0:
        print("%s: EUR_JPY buy" % insert_time)
    if result_list[0] > 1.0 and result_list[1] > 1.0 and result_list[2] > 1.0 and result_list[5] < 1.0:
        print("%s: EUR_USD buy" % insert_time)
    if result_list[0] < 1.0 and result_list[3] > 1.0 and result_list[4] > 1.0 and result_list[5] > 1.0:
        print("%s: GBP_JPY buy" % insert_time)
    if result_list[0] < 1.0 and result_list[3] > 1.0 and result_list[4] > 1.0 and result_list[5] < 1.0:
        print("%s: GBP_USD buy" % insert_time)

    insert_time = insert_time + timedelta(minutes=30)


"""
Operations on EDS
"""

import warnings
from itertools import count

from delphin import variable
from delphin import scope
from delphin import eds
from delphin import util

def from_mrs(m, predicate_modifiers=True, unique_ids=True,
             representative_priority=None):
    """
    Create an EDS by converting from MRS *m*.

    In order for MRS to EDS conversion to work, the MRS must satisfy
    the intrinsic variable property (see
    :func:`delphin.mrs.has_intrinsic_variable_property`).

    Args:
        m: the input MRS
        predicate_modifiers: if `True`, include predicate-modifier
            edges; if `False`, only include basic dependencies; if a
            callable, then call on the converted EDS before creating
            unique ids (if `unique_ids=True`)
        unique_ids: if `True`, recompute node identifiers to be unique
            by the LKB's method; note that ids from *m* should already
            be unique by PyDelphin's method
        representative_priority: a function for ranking candidate
            representative nodes; see :func:`scope.representatives`
    Returns:
        EDS
    Raises:
        EDSError: when conversion fails.
    """
    # EP id to node id map; create now to keep ids consistent
    hcmap = {hc.hi: hc for hc in m.hcons}
    reps = scope.representatives(m, priority=representative_priority)
    ivmap = {p.iv: (p, q)
             for p, q in m.quantification_pairs()
             if p is not None}

    top = _mrs_get_top(m.top, hcmap, reps, m.index, ivmap)
    deps = _mrs_args_to_basic_deps(m, hcmap, ivmap, reps)
    nodes = _mrs_to_nodes(m, deps)

    e = eds.EDS(
        top=top,
        nodes=nodes,
        lnk=m.lnk,
        surface=m.surface,
        identifier=m.identifier)

    if predicate_modifiers is True:
        predicate_modifiers = find_predicate_modifiers
    if predicate_modifiers:
        addl_deps = predicate_modifiers(e, m, representatives=reps)
        for id, node_deps in addl_deps.items():
            e[id].edges.update(node_deps)

    if unique_ids:
        make_ids_unique(e, m)

    return e

def _mrs_get_top(top, hcmap, reps, index, ivmap):
    if top in hcmap and hcmap[top].lo in reps:
        lbl = hcmap[top].lo
        top = reps[lbl][0].id
    else:
        if top in hcmap:
            warnings.warn(
                f'broken handle constraint: {hcmap[top]}',
                eds.EDSWarning
            )
        if top in reps:
            top = reps[top][0].id
        elif index in ivmap and ivmap[index][0].label in reps:
            lbl = ivmap[index][0].label
            top = reps[lbl][0].id
        else:
            warnings.warn('unable to find a suitable TOP', eds.EDSWarning)
            top = None
    return top

def _mrs_args_to_basic_deps(m, hcmap, ivmap, reps):
    edges = {}
    for src, roleargs in m.arguments().items():
        if src in ivmap:
            p, q = ivmap[src]
            # non-quantifier EPs
            edges[src] = {}
            for role, tgt in roleargs:
                # qeq
                if tgt in hcmap:
                    lbl = hcmap[tgt].lo
                    if lbl in reps:
                        tgt = reps[lbl][0].id
                    else:
                        warnings.warn(
                            f'broken handle constraint: {hcmap[tgt]}',
                            eds.EDSWarning
                        )
                        continue
                # label arg
                elif tgt in reps:
                    tgt = reps[tgt][0].id
                # regular arg
                elif tgt in ivmap:
                    tgt = ivmap[tgt][0].id
                # other (e.g., BODY, dropped arguments, etc.)
                else:
                    continue
                edges[src][role] = tgt
            # add BV if the EP has a quantifier
            if q is not None:
                edges[q.id] = {eds.BOUND_VARIABLE_ROLE: src}

    return edges

def _mrs_to_nodes(m, edges):
    nodes = []
    for ep in m.rels:
        properties, type = None, None
        if not ep.is_quantifier():
            iv = ep.iv
            properties = m.properties(iv)
            type = variable.type(iv)
        nodes.append(
            eds.Node(ep.id,
                     ep.predicate,
                     type,
                     edges.get(ep.id, {}),
                     properties,
                     ep.carg,
                     ep.lnk,
                     ep.surface,
                     ep.base))
    return nodes

def find_predicate_modifiers(e, m, representatives=None):
    """
    Return an argument structure mapping for predicate-modifier edges.

    In EDS, predicate modifiers are edges that describe a relation
    between predications in the original MRS that is not evident on
    the regular and scopal arguments. In practice these are EPs that
    share a scope but do not select any other EPs within their scope,
    such as when quantifiers are modified ("nearly every...") or with
    relative clauses ("the chef whose soup spilled..."). These are
    almost the same as the MOD/EQ links of DMRS, except that predicate
    modifiers have more restrictions on their usage, mainly due to
    their using a standard role (`ARG1`) instead of an
    idiosyncratic one.

    Generally users won't call this function directly, but by calling
    :func:`from_mrs` with `predicate_modifiers=True`, but it is
    visible here in case users want to inspect its results separately
    from MRS-to-EDS conversion. Note that when calling it separately,
    *e* should use the same predication ids as *m* (by calling
    :func:`from_mrs` with `unique_ids=False`). Also, users may define
    their own function with the same signature and return type and use
    it in place of this one. See :func:`from_mrs` for details.

    Args:
        e: the EDS converted from *m* as by calling :func:`from_mrs`
            with `predicate_modifiers=False` and `unique_ids=False`,
            used to determine if parts of the graph are connected
        m: the source MRS
        representatives: the scope representatives; this argument is
            mainly to prevent :func:`delphin.scope.representatives`
            from being called twice on *m*
    Returns:
        A dictionary mapping source node identifiers to
        role-to-argument dictionaries of any additional
        predicate-modifier edges.
    Examples:
        >>> e = eds.from_mrs(m, predicate_modifiers=False)
        >>> print(eds.find_predicate_modifiers(e.argument_structure(), m)
        {'e5': {'ARG1': '_1'}}
    """
    if representatives is None:
        representatives = scope.representatives(m)
    role = eds.PREDICATE_MODIFIER_ROLE

    # find connected components so predicate modifiers only connect
    # separate components
    ids = {ep.id for ep in m.rels}
    edges = []
    for node in e.nodes:
        for _, tgt in node.edges.items():
            edges.append((node.id, tgt))
    components = util._connected_components(ids, edges)

    ccmap = {}
    for i, component in enumerate(components):
        for id in component:
            ccmap[id] = i

    addl = {}
    if len(components) > 1:
        for label, eps in representatives.items():
            if len(eps) > 1:
                first = eps[0]
                joined = set([ccmap[first.id]])
                for other in eps[1:]:
                    occ = ccmap[other.id]
                    type = variable.type(other.args.get(role, 'u0'))
                    needs_edge = occ not in joined
                    edge_available = type.lower() == 'u'
                    if needs_edge and edge_available:
                        addl.setdefault(other.id, {})[role] = first.id
                        joined.add(occ)
    return addl

def make_ids_unique(e, m):
    """
    Recompute the node identifiers in EDS *e* to be unique.

    MRS objects used in conversion to EDS already have unique
    predication ids, but they are created according to PyDelphin's
    method rather than the LKB's method, namely with regard to
    quantifiers and MRSs that do not have the intrinsic variable
    property. This function recomputes unique EDS node identifiers by
    the LKB's method.

    .. note::
        This function works in-place on *e* and returns nothing.

    Args:
        e: an EDS converted from MRS *m*, as from :func:`from_mrs`
            with `unique_ids=False`
        m: the MRS from which *e* was converted
    """
    # deps can be used to single out ep from set sharing ARG0s
    new_ids = (f'_{i}' for i in count(start=1))
    nids = {}
    used = {}
    # initially only make new ids for quantifiers and those with no IV
    for ep in m.rels:
        nid = ep.iv
        if nid is None or ep.is_quantifier():
            nid = next(new_ids)
        nids[ep.id] = nid
        used.setdefault(nid, set()).add(ep.id)
    # for ill-formed MRSs, more than one non-quantifier EP may have
    # the same IV. Select a winner like selecting a scope
    # representatives: the one not taking others in its group as an
    # argument.
    deps = {node.id: node.edges.items() for node in e.nodes}
    for nid, ep_ids in used.items():
        if len(ep_ids) > 1:
            ep_ids = sorted(
                ep_ids,
                key=lambda n: any(d in ep_ids for _, d in deps.get(n, []))
            )
            for nid in ep_ids[1:]:
                nids[nid] = next(new_ids)

    # now use the unique ID mapping for reassignment
    if e.top is not None:
        e.top = nids[e.top]
    for node in e.nodes:
        node.id = nids[node.id]
        edges = {role: nids[arg] for role, arg in node.edges.items()}
        node.edges = edges


import sys
import pygame
from time import sleep

import sound
import highscore_screen_functions as hsf
import game_over_screen_functions as gosf
from bullet import Bullet
from alien import Alien
from button import Button

def check_keydown_events(event, game_settings, screen, ship, bullets):
	"""respond to key presses"""
	if event.key == pygame.K_RIGHT:
		#ship.moving_left = False
		ship.moving_right = True
	elif event.key == pygame.K_LEFT:
		#ship.moving_right = False
		ship.moving_left = True
	elif event.key == pygame.K_SPACE:
		fire_bullet(game_settings, screen, ship, bullets)
	elif event.key == pygame.K_q:
		sys.exit()

def fire_bullet(game_settings, screen, ship, bullets):
	if len(bullets) < game_settings.bullets_allowed:
		sound.play_sound(game_settings.shoot)
		new_bullet = Bullet(game_settings, screen, ship)
		bullets.add(new_bullet)

def check_keyup_events(event, ship):
	"""respond to key releases"""
	if event.key == pygame.K_RIGHT:
		ship.moving_right = False
	elif event.key ==  pygame.K_LEFT:
		ship.moving_left = False

def check_events(game_settings, screen, game_stats, scoreboard, play_button,
		highscore_button, ship, aliens, bullets):
	"""Respond to keypresses and mouse events"""

	for event in pygame.event.get():
		if event.type == pygame.QUIT:
			sys.exit()
		elif event.type == pygame.KEYDOWN:
			check_keydown_events(event, game_settings, screen, ship, bullets)
		elif event.type == pygame.KEYUP:
			check_keyup_events(event, ship)
		elif event.type == pygame.MOUSEBUTTONDOWN:
			mouse_x, mouse_y = pygame.mouse.get_pos()
			check_play_button(game_settings, screen, game_stats, scoreboard,
				play_button, ship, aliens, bullets, mouse_x, mouse_y)
			hsf.check_highscore_button(game_settings, screen, game_stats, scoreboard,
				highscore_button, mouse_x, mouse_y)

def check_play_button(game_settings, screen, game_stats, scoreboard,
		play_button, ship, aliens, bullets, mouse_x, mouse_y):
	"""start a new game when the player clicks the play button"""
	play_button_pushed = play_button.rect.collidepoint(mouse_x, mouse_y)
	if play_button_pushed and not game_stats.game_active:
		#reset the speed of ship, bullets and aliens
		game_settings.reset()
		pygame.mouse.set_visible(False)
		start_game(game_settings, screen, game_stats, scoreboard, ship, aliens,
			bullets)

def start_game(game_settings, screen, game_stats, scoreboard, ship, aliens,
		bullets):
	#reset the game stats
	game_stats.reset_stats()
	game_stats.game_active = True

	#reset the scoreboard
	scoreboard.prep_score()
	scoreboard.prep_high_score()
	scoreboard.prep_level()
	scoreboard.prep_lives()

	#empty the alien and bullet lists
	aliens.empty()
	bullets.empty()

	#create the alien fleet and center our ship
	create_fleet(game_settings, game_stats, screen, ship, aliens)
	ship.center_ship()

def update_screen(game_settings, game_stats, scoreboard, screen, ship,
			bullets, aliens, play_button, highscore_rankings_button):
	"""Update images on the screen and flip to the new screen"""

	screen.fill(game_settings.bg_color)

	for bullet in bullets.sprites():
		bullet.draw_bullet()

	ship.blitme()
	aliens.draw(screen)
	scoreboard.show_score()

	if not game_stats.game_active:
		play_button.draw_button()
		highscore_rankings_button.draw_button()

	#Make the most recently drawn screen visible
	pygame.display.flip()

def check_high_score(game_stats, scoreboard):
	"""See if there is a new high score, called from check_collisions func"""

	if game_stats.score > game_stats.high_score:
		game_stats.high_score = game_stats.score
		scoreboard.prep_high_score()

def update_bullets(game_settings, game_stats, scoreboard, screen, ship,
		bullets, aliens):
	#Calls update() for each bullet we put in the group
	bullets.update()

	#Get rid of bullets that have left the screen
	for bullet in bullets.copy():
		if bullet.rect.bottom <= 0:
			bullets.remove(bullet)

	check_collisions(game_settings, game_stats, scoreboard, screen, ship,
		bullets, aliens)

def check_collisions(game_settings, game_stats, scoreboard, screen, ship,
		bullets, aliens):
	#Check for collisions between bullets and aliens, removes both
	collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)

	#updates the score
	if collisions:
		for aliens_hit in collisions.values():
			#checks whether the hit alien is destroyed and scores accordingly
			while aliens_hit:
				alien = aliens_hit.pop()
				if(alien.change_color()):
					sound.play_sound(game_settings.hit_2)
					aliens.add(alien)
				else:
					sound.play_sound(game_settings.alien_destroyed)
					game_stats.score += alien.point_value
					scoreboard.prep_score()

		check_high_score(game_stats, scoreboard)

	#repopulate fleet if no aliens left
	if len(aliens) <= 0:
		bullets.empty()
		game_settings.increase_speed()

		#Increase the level
		game_stats.level += 1
		scoreboard.prep_level()

		create_fleet(game_settings, game_stats, screen, ship, aliens)

def get_number_aliens_x(game_settings, alien_width):
	available_space_x = game_settings.screen_width - (2 * alien_width)
	number_aliens_x = int(available_space_x / (2 * alien_width))
	return number_aliens_x

def create_alien(game_settings, game_stats, screen, aliens, alien_number, row_number):
	alien = Alien(game_settings, game_stats, screen)
	alien_width = alien.rect.width
	alien.x = alien_width + 2 * alien_width * alien_number
	alien.rect.x = alien.x

	alien_height = alien.rect.height
	alien.rect.y = alien_height + 2 * alien_height * row_number
	aliens.add(alien)

def get_number_rows(game_settings, ship_height, alien_height):
	availabe_space_y = (game_settings.screen_height -
		(alien_height * 3) - ship_height)
	number_rows = int(availabe_space_y / (2 * alien_height))
	return number_rows

def create_fleet(game_settings, game_stats, screen, ship, aliens):
	"""Creates the alien fleet"""
	alien = Alien(game_settings, game_stats, screen)
	number_aliens_x = get_number_aliens_x(game_settings, alien.rect.width)
	number_rows = get_number_rows(game_settings, ship.rect.height,
		alien.rect.height)

	for row_number in range(number_rows):
		for alien_number in range(number_aliens_x):
			create_alien(game_settings, game_stats, screen, aliens,
				alien_number, row_number)

def check_fleet_edges(game_settings, aliens):
	"""Checks each alien if it has hit the edge of the screen, if it has
	change directions"""
	for alien in aliens.sprites():
		if alien.check_edges():
			change_fleet_direction(game_settings, aliens)
			break

def change_fleet_direction(game_settings, aliens):
	"""after hitting an edge, move aliens down and change the direction the
	fleet is moving in"""
	for alien in aliens.sprites():
		alien.rect.y += game_settings.fleet_drop_speed
	game_settings.fleet_direction *= -1

def ship_hit(game_settings, game_stats, screen, scoreboard, ship, bullets, aliens):
	"""if ship is hit, check if we still have lives left otherwise end the game"""
	if game_stats.ships_left > 0:
		game_stats.ships_left -= 1
		scoreboard.prep_lives()
		aliens.empty()
		bullets.empty()
		create_fleet(game_settings, game_stats, screen, ship, aliens)
		ship.center_ship()
		sound.play_sound(game_settings.ship_destroyed)
		sleep(1)
	else:
		game_stats.game_active = False
		sleep(1)
		gosf.game_over_screen(game_settings, game_stats, screen)
		pygame.mouse.set_visible(True)

def check_bottom(game_settings, game_stats, screen, scoreboard,ship, bullets, aliens):
	"""checks if aliens has hit bottom of the screen"""
	for alien in aliens.sprites():
		if alien.rect.bottom >= game_settings.screen_height:
			ship_hit(game_settings, game_stats, screen, scoreboard, ship, bullets, aliens)
			break

def update_aliens(game_settings, game_stats, screen, scoreboard, ship, bullets, aliens):
	"""handles moving the alien and checking to if anything has been hit"""
	aliens.update()
	check_fleet_edges(game_settings, aliens)

	#Look for alien ship collisions
	if pygame.sprite.spritecollideany(ship, aliens):
		ship_hit(game_settings, game_stats, screen, scoreboard, ship, bullets, aliens)

	#look for aliens hitting bottom
	check_bottom(game_settings, game_stats, screen, scoreboard, ship, bullets, aliens)


#!/usr/bin/env python
import rospy
from std_msgs.msg import Float64
from geometry_msgs.msg import Point
from dynamixel_msgs.msg import JointState
from random import randint

from math import pi, sin, cos, sqrt, atan2, acos, degrees, radians
#from numpy import arange

def cart2sph(x,y,z):
	XsqPlusYsq = x**2 + y**2
	r = sqrt(XsqPlusYsq + z**2)               # r
	theta = atan2(sqrt(XsqPlusYsq), z)     # theta
	phi = atan2(y,x)                           # phi
	if r == 0.0:
		theta = pi/2;
	return r, theta, phi

def sph2cart(r, theta, phi):
	x = r*sin(theta)*cos(phi)
	y = r*sin(theta)*sin(phi)
	z = r*cos(theta)
	return x, y, z

def main():
	order = Point(100,10,10);
	eyeOrder = Point(0,0,0);

	rospy.init_node('headTestPublisher', anonymous=True)

	directionPub = rospy.Publisher("/neckControlCamera/servosDirection", Point)

	r = rospy.Rate(5) # hz

	while not rospy.is_shutdown():
		order.x = order.x + randint(-1,1)*1;
		order.y = order.y + randint(-1,1)*10;
		order.z = order.z + randint(-1,1)*10;

		# publish data
		directionPub.publish(order);

		# for eyes
		#(R, theta, phi) = cart2sph(order.x, order.y, order.z);
		#eyeOrder.x = degrees(phi); eyeOrder.y = degrees(theta);
		#directionPub.publish(eyeOrder);
		#rospy.logwarn("eyes order: bot = %g up = %g", eyeOrder.x, eyeOrder.y);

		r.sleep()

if __name__ == '__main__':
	try:
		main()
	except rospy.ROSInterruptException: pass


from django.urls import path, include
from .views import (
    HelloAPIView,
    TestAPIViewSet,
    UserProfileViewSet,
    UserLoginAPIView,
    UserProfileFreedItemView,
    CreateUserView,
    CreateTokenView,
)
from rest_framework import routers

app_name = "profile"
router = routers.DefaultRouter()
router.register("api_viewset", TestAPIViewSet, basename="api_viewset")
router.register("profiles_api", UserProfileViewSet, basename="profiles_api")
router.register("profile_feed", UserProfileFreedItemView)

urlpatterns = [
    path("test_api_view/", HelloAPIView.as_view(), name="test_api_view"),
    path("", include(router.urls)),
    path("login/", UserLoginAPIView.as_view()),
    path("create/", CreateUserView.as_view(), name="create"),
    path("token/", CreateTokenView.as_view(), name="token"),
]


from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from pathlib import Path
import uvicorn
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Configure CORS
origins = [
    "http://localhost:5173",  # Add your frontend URL here
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["*"],
)

# Serve the static files (Excel files)
app.mount("/files", StaticFiles(directory="files"), name="files")

uploaded_files = []

@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
    file_path = f"files/{file.filename}"
    with open(file_path, "wb") as buffer:
        buffer.write(await file.read())
    uploaded_files.append(file.filename)
    return {"filename": file.filename}

@app.get("/files")
def get_files(option: str, year: int, month: int):
    filename = f"{option}_{year}-{month:02d}.xlsx"
    filepath = Path(f"files/{filename}")

    if not filepath.exists():
        raise HTTPException(status_code=404, detail="File not found")

    return FileResponse(filepath, filename=filename, media_type="application/vnd.ms-excel")

    # filtered_files = filter_files(option, period)
    # return {"files": filtered_files}

# def filter_files(option: str, period: str):
#     print(option, period)
#     # Implement logic to filter files based on the selected option and period
#     filtered_files = []
#     for file in uploaded_files:
#         if option in file and period in file:
#             filtered_files.append(file)
#     return filtered_files

@app.get("/download/{filename}")
def download_file(filename: str):
    file_path = f"files/{filename}"
    return FileResponse(file_path, filename=filename)

if __name__ == "__main__":
    uvicorn.run("server:app", host="0.0.0.0", port=23231, reload=True)


# -*- coding: utf-8 -*-
# author:xiaoming
#文件类型列表
def list_profile_types(conn):
    print("List Profile Types:")
    for pt in conn.cluster.profile_types():
        print(pt.to_dict())
#得到文件类型
def get_profile_type(conn):
    print("Get Profile Type:")
    pt = conn.cluster.get_profile_type('os.nova.server-1.0')
    print(pt.to_dict())
#检查配置文件列表
def list_profiles(conn):
    print("List Profiles:")
    for profile in conn.cluster.profiles():
        print(profile.to_dict())
    for profile in conn.cluster.profiles(sort='name:asc'):
        print(profile.to_dict())
#创建一个想要的配置文件

def create_profile(conn):
    SERVER_NAME, FLAVOR_NAME, IMAGE_NAME, NETWORK_NAME = 0
    print("Create Profile:")
    spec = {
        'profile': 'os.nova.server',
        'version': 1.0,
        'properties': {
            'name': SERVER_NAME,
            'flavor': FLAVOR_NAME,
            'image': IMAGE_NAME,
            'networks': {
                'network': NETWORK_NAME
            }
        }
    }
    profile = conn.cluster.create_profile('os_server', spec)
    print(profile.to_dict())
#查找配置文件
def find_profile(conn):
    print("Find Profile:")
    profile = conn.cluster.find_profile('os_server')
    print(profile.to_dict())
#获取配置文件
def get_profile(conn):
    print("Get Profile:")
    profile = conn.cluster.get_profile('os_server')
    print(profile.to_dict())
#更新配置文件
def update_profile(conn):
    print("Update Profile:")
    profile = conn.cluster.update_profile('os_server', name='old_server')
    print(profile.to_dict())
#删除配置文件
def delete_profile(conn):
    print("Delete Profile:")
    conn.cluster.delete_profile('os_server')
    print("Profile deleted.")


import sys
n,m = map(int,sys.stdin.readline().rstrip().split())

dy = [-1,1,0,0]
dx = [0,0,-1,1]

paper = []
maxx = 0

def dfs(i,j,summ,cnt):
    global maxx
    if cnt==4:
        maxx = max(maxx,summ)
        return

    visited[i][j] = True
    summ+=paper[i][j]

    for u in range(4):
        y = i+dy[u]
        x = j+dx[u]
        if ((0<=y<n) & (0<=x<m)):
            if visited[y][x] == False:
                dfs(y,x,summ,cnt+1)

for i in range(n):
    paper.append(list(map(int,sys.stdin.readline().rstrip().split())))

for i in range(n):
    for j in range(m):
        visited = [[False] * m for _ in range(n)]
        dfs(i,j,0,0)
        print(maxx)
print(maxx)


from orderbook import *
from update import *
import numpy as np
import copy

class Market:
    # simulate transactions in the market, calculate the orderbook at a given time
    # Attributes:
    #   initial_orderbook - the orderbook storing the initial orders by the beginning of inspection
    #   current_orderbook - an orderbook object storing current orderbook, if need to recaculate, use reset()
    #   updates_matrix - a numpy array containing the information of updates in the form of matrix
    # Methods:
    #   initializer - construct the initial orderbook
    #   calculate_orderbook - calculate an orderbook using all updates before a given timestamp
    def __init__(self, order_data, update_data):
        # construct a market object, initialize the orderbook, store the update matrix
        # Input:
        #   order_date - a numpy array with desired format
        #   update_date - a numpy array with desired format
        # Returns:
        # Modifies:
        assert (isinstance(order_data, np.ndarray) and isinstance(update_data, np.ndarray))
        self.initial_orderbook = Orderbook(order_data)
        self.current_orderbook = Orderbook(order_data)
        self.updates_matrix = update_data
        self.updates_counter = 0
        self.malicious_updates_counter = 0
        self.time = 0

    def calculate_orderbook(self, time):
        # calculate an orderbook at the given time
        # Input:
        #   time - an integer representing the time of inspection
        # Returns:
        #   an orderbook object
        # Modifies:
        #orderbook = copy.deepcopy(self.initial_orderbook)
        next_update = Update(self.updates_matrix[max(self.updates_counter - 1, 0), :])
        if time < self.time:
            print("query a time later than", self.time)
            print("or use 'reset' to reset the orderbook")
            raise INVALID_TIME("INVALID TIME")
        while (next_update.get_timestamp() < time and self.updates_counter < self.updates_matrix.shape[0]):
            next_update = Update(self.updates_matrix[self.updates_counter, :])
            try:
                self.current_orderbook.execute_update(next_update)
            except:
                print("malicious update found at time", next_update.get_timestamp())
                self.malicious_updates_counter += 1
            self.updates_counter += 1
        self.time = time
        return self.current_orderbook

    def reset(self):
        #reset the orderbook to time 0
        self.current_orderbook = copy.deepcopy(self.initial_orderbook)
        self.updates_counter = 0
        self.malicious_updates_counter = 0

    def get_num_malicious(self):
        #return the number of malicious updates
        return self.malicious_updates_counter

    def print_num_malicious(self):
        #return the number of malicious updates
        print("found abnormal updates:", self.malicious_updates_counter)


"""Collection of utility functions."""

import hashlib
import importlib
import logging
import os
import os.path as op
import random
import sys
import tempfile
import time
from contextlib import contextmanager
from io import BytesIO
from uuid import uuid4

import fsspec
import janitor  # noqa
import joblib
import numpy as np
import pandas as pd
import pandas_flavor as pf
import panel as pn
import yaml

import ta_lib

from .base_utils import silence_common_warnings

logger = logging.getLogger(__name__)
pd.options.mode.use_inf_as_na = True

def import_python_file(py_file_path):
    mod_name, ext = op.splitext(op.basename(op.abspath(py_file_path)))
    if ext != ".py":
        raise ValueError("Invalid file extension : {ext}. Expected a py file")
    spec = importlib.util.spec_from_file_location(mod_name, py_file_path)
    mod = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod

def save_pipeline(pipeline, loc):
    """Save an sklearn pipeline in a location.

    Parameters
    ----------
    pipeline : sklearn.Pipeline
        Pipeline object to be saved
    loc : str
        Path string of the location where the pipeline has to be saved
    """
    logger.info(f"Saving pipeline to location {loc}")

    os.makedirs(op.dirname(loc), exist_ok=True)
    joblib.dump(pipeline, loc)

def create_job_id(context):
    """Create a unique id for a job.

    Parameters
    ----------
    context : ta_lib.core.context.Context

    Returns
    -------
    string
        unique string identifier
    """
    return f"job-{uuid4()}"

def initialize_environment(debug=True, hide_warnings=True):
    """Initialize the OS Environ with relevant values.

    Parameters
    ----------
    debug: bool, optional
        Whether to set TA_DEBUG to True of False in the environment, default=True
    hide_warnings: bool, optional
        True will hide warnings, default True
    """
    # FIXME: support config
    if debug:
        os.environ["TA_DEBUG"] = "True"
    else:
        os.environ["TA_DEBUG"] = "False"

    # force tigerml to raise an exception on failure
    os.environ["TA_ALLOW_EXCEPTIONS"] = "True"

    if hide_warnings:
        silence_common_warnings()

def is_debug_mode():
    """Check if the current environ is in debug mode."""
    debug_mode = os.environ.get("TA_DEBUG", "True")
    return debug_mode.upper() == "TRUE"

@contextmanager
def timed_log(msg):
    """Log the provided ``msg`` with the execution time."""
    start_time = time.time()
    try:
        yield
    finally:
        end_time = time.time()
        logging.info(f"{msg} : {end_time-start_time} seconds")

@contextmanager
def disable_logging(highest_level=logging.CRITICAL):
    """Disable all logs below ``highest_level``."""
    # NOTE: this is the attribute that seems to be modified
    # by the call to logging.disable. so we first save this
    # and reset it when exiting the context.
    orig_level = logging.root.manager.disable
    logging.disable(highest_level)
    try:
        yield
    finally:
        logging.disable(orig_level)
        logging.info("*" * 80)

@contextmanager
def silence_stdout():
    """Silence print stmts on the console unless in debug mode."""
    # debug mode. do nothing.
    if is_debug_mode():
        try:
            yield
        finally:
            return

    # not in debug mode. silence the output by writing to the null device.
    with open(os.devnull, "w") as fp:
        old_dunder_stdout = sys.__stdout__
        old_sys_stdout = sys.stdout
        # FIXME: This doesen't help with notebooks
        # with redirect_stdout(fp):
        #    yield
        try:
            sys.__stdout__ = fp
            sys.stdout = fp
            yield

        except Exception as e:
            sys.stderr.write("Error: {}".format(str(e)))
        finally:
            sys.__stdout__ = old_dunder_stdout
            sys.stdout = old_sys_stdout

def display_as_tabs(figs, width=300, height=300, cloud_env="local"):
    """To display multiple dataset outputs as tabbed panes.

    Parameters
    ----------
    figs: list(tuples)
        List of ('tab_name',widget) to be displayed
    width: int, optional
        width of the output, default 300
    height: int, optional
        height of the output, default 300

    Returns
    -------
    pn.Tabs()
    """
    if cloud_env == "local":
        tabs = pn.Tabs()

        plts = []
        for name, wdgt in figs:
            if isinstance(wdgt, pd.DataFrame):
                wdgt.columns = map(str, wdgt.columns)
                cols = wdgt.select_dtypes("object").columns.tolist()
                wdgt = wdgt.transform_columns(cols, str)
                wdgt = pn.widgets.DataFrame(wdgt, name=name)
            plts.append((name, wdgt))

        tabs.extend(plts)
        return tabs
    elif cloud_env == "Databricks":
        report_dict = {}
        for name, wdgt in figs:
            report_dict[name] = wdgt
        from tigerml.core.reports import create_report

        create_report(report_dict, name="temp", format=".html")
        with open("temp.html", "r") as f:
            html_string = f.read()
        return html_string
    else:
        raise Exception("Input value for cloud_env is incorrect")

def is_relative_path(path):
    """To check if `path` is a relative path or not.

    Parameters
    ----------
    path : str
        path string to be evaluated

    Returns
    -------
    bool
        True if input path is relative else False
    """
    npath = op.normpath(path)
    return op.abspath(npath) != npath

def get_package_path():
    """Get the path of the current installed ta_lib package.

    Returns
    -------
    str
        path string in the current system where the ta_lib package is loaded from
    """
    path = ta_lib.__path__
    return op.dirname(op.abspath(path[0]))

def get_package_version():
    """Return the version of the package."""
    return ta_lib.__version__

def get_data_dir_path():
    """Fetch the data directory path."""
    return op.join(get_package_path(), "..", "data")

def get_fs_and_abs_path(path, storage_options=None):
    """Get the Filesystem and paths from a urlpath and options.

    Parameters
    ----------
    path : string or iterable
        Absolute or relative filepath, URL (may include protocols like
        ``s3://``), or globstring pointing to data.
    storage_options : dict, optional
        Additional keywords to pass to the filesystem class.

    Returns
    -------
    fsspec.FileSystem
       Filesystem Object
    list(str)
        List of paths in the input path.
    """
    fs, _, paths = fsspec.core.get_fs_token_paths(path, storage_options=storage_options)
    if len(paths) == 1:
        return fs, paths[0]
    else:
        return fs, paths

def load_yml(path, *, fs=None, **kwargs):
    """Load a yml file from the input `path`.

    Parameters
    ----------
    path : string
        Absolute or relative filepath, URL (may include protocols like
        ``s3://``).
    fs : fsspec.filesystem, optional
        Filesystem of the url, by default ``None``

    Returns
    -------
    dict
        dictionery of the loaded yml file
    """
    fs = fs or fsspec.filesystem("file")
    with fs.open(path, mode="r") as fp:
        return yaml.safe_load(fp, **kwargs)

def create_yml(path, config, fs=None):
    """Dump a dictionary as yaml to output `path`.

    Parameters
    ----------
    path : string
        Absolute or relative filepath, URL (may include protocols like
        ``s3://``).
    config: dict
        config dictionary to be dumped as yaml file.
    fs : fsspec.filesystem, optional
        Filesystem of the url, by default ``None``
    """
    fs = fs or fsspec.filesystem("file")
    with fs.open(path, "w") as out_file:
        yaml.safe_dump(config, out_file, default_flow_style=False)

def load_csv(path, *, fs=None, **kwargs):
    """Load a csv file from the file system as specified in the path variable.

    Parameters
    ----------
    path : string
        Absolute or relative filepath, URL (may include protocols like
        ``s3://``).
    fs : fsspec.filesystem, optional
        Filesystem of the url, by default ``None``

    Returns
    -------
    pd.DataFrame
        Dataframe load from the input csv path
    """
    fs = fs or fsspec.filesystem("file")
    with fs.open(path, mode="r") as fp:
        return pd.read_csv(fp, **kwargs)

def load_parquet(path, *, fs=None, **kwargs):
    """Load a parquet file from the file system as specified in the path variable.

    Parameters
    ----------
    path : string
        Absolute or relative filepath, URL (may include protocols like
        ``s3://``).
    fs : fsspec.filesystem, optional
        Filesystem of the url, by default ``None``

    Returns
    -------
    pd.DataFrame
        Dataframe load from the input parquet path
    """
    fs = fs or fsspec.filesystem("file")
    with fs.open(path, mode="rb") as fp:
        # FIXME: can be pandas or spark
        return pd.read_parquet(fp, **kwargs)

def save_parquet(df, path, *, fs=None, **kwargs):
    """Save a parquet file in the fs as specified in the path variable.

    Parameters
    ----------
    path : string
        Absolute or relative filepath, URL (may include protocols like
        ``s3://``).
    fs : fsspec.filesystem, optional
        Filesystem of the url, by default ``None``

    """
    fs = fs or fsspec.filesystem("file")
    if fs.protocol == "file":
        # FIXME: utility functions to robustify this
        fs.makedirs(op.dirname(path), exist_ok=True)

    if isinstance(df, pd.Series):
        df = pd.DataFrame(df)

    with fs.open(path, mode="wb") as fp:
        # FIXME: can be pandas or spark
        return df.to_parquet(fp, **kwargs)

def save_csv(df, path, *, fs=None, **kwargs):
    """Save a csv file in the fs as specified in the path variable.

    Parameters
    ----------
    path : string
        Absolute or relative filepath, URL (may include protocols like
        ``s3://``).
    fs : fsspec.filesystem, optional
        Filesystem of the url, by default ``None``

    """
    fs = fs or fsspec.filesystem("file")
    if fs.protocol == "file":
        # FIXME: utility functions to robustify this
        fs.makedirs(op.dirname(path), exist_ok=True)

    if isinstance(df, pd.Series):
        df = pd.DataFrame(df)

    with fs.open(path, mode="wt", newline="") as fp:
        # FIXME: can be pandas or spark
        return df.to_csv(fp, **kwargs)

def load_data(path, *, fs=None, **kwargs):
    """Load data from the given path. type of data is inferred automatically.

    ``.csv`` and ``.parquet`` are compatible now

    Parameters
    ----------
    path : string
        Absolute or relative filepath, URL (may include protocols like
        ``s3://``).
    fs : fsspec.filesystem, optional
        Filesystem of the url, by default ``None``

    Returns
    -------
    pd.DataFrame
    """
    # FIXME: Move io utils to a separate module and make things generic
    if path.endswith(".parquet"):
        return load_parquet(path, fs=fs, **kwargs)
    elif path.endswith(".csv"):
        return load_csv(path, fs=fs, **kwargs)
    else:
        raise NotImplementedError()

def save_data(data, path, *, fs=None, **kwargs):
    """Save data into the given path. type of data is inferred automatically.

    ``.csv`` and ``.parquet`` are compatible now

    Parameters
    ----------
    path : string
        Absolute or relative filepath, URL (may include protocols like
        ``s3://``).
    fs : fsspec.filesystem, optional
        Filesystem of the url, by default ``None``

    """
    # FIXME: Move io utils to a separate module and make things generic
    if path.endswith(".parquet"):
        return save_parquet(data, path, **kwargs)
    elif path.endswith(".csv"):
        return save_csv(data, path, **kwargs)
    else:
        raise NotImplementedError()

def df_to_X_y(df, target_col):
    """Create X and y training variables from the provided dataframe.

    Parameters
    ----------
    target_col : string
        column name of the dependant feature

    Returns
    -------
    pd.DataFrame
        Dataframe with only independant features
    pd.DataFrame
        DataFramw with only target_col

    NOTE: This function creates a copy of the data.
    """
    X = df.drop(target_col, axis=1)
    y = df[target_col].copy()
    return X, y

def initialize_random_seed(seed):
    """Initialise random seed using the input ``seed``.

    Parameters
    ----------
    seed : int

    Returns
    -------
    int
        seed integer
    """
    logger.info(f"Initialized Random Seed : {seed}")
    random.seed(seed)
    os.environ["PYTHONHASHSEED"] = str(seed)
    np.random.seed(seed)
    return seed

def get_fsspec_storage_options(resource_type, credentials):
    """Get storage options from the credentials based on the resource type.

    Parameters
    ----------
    resource_type : string
        'aws' or 'azure' or 'local' etc.,
    credentials : dict
        Dictionery of the credentials

    Returns
    -------
    dict
        Dictionary of the relevant storage options

    Raises
    ------
    NotImplementedError
        Raised for all resourcetype inputs other than 'aws'
    """
    if resource_type == "aws":
        return {
            "key": credentials["aws_access_key_id"],
            "secret": credentials["aws_secret_access_key"],
        }
    else:
        raise NotImplementedError(f"resource type: {resource_type}")

def setanalyse(listA, listB, simplify=True, exceptions_only=False):
    """Given two lists, returns a dictionary of set analysis.

        A-B: set(A) - set(B)
        B-A: set(B) - set(A)
        AuB: A union B
        A^B: A intersection B

    Parameters
    ----------
    listA : list
        input list 1 to be evaluated
    listB : list
        input list 2 to be evaluated
    simplify: bool
        if True, gives only len in each space False gives the entire list.
    exceptions_only: False
        if True, gives only A-B & B-A False gives all 4.
        True is efficient while dealing with large sets or analyzing expections alone.

    Returns
    -------
    dict
        dictionery with the following keys 'A-B', 'B-A', 'A^B', 'AuB'

    """
    A = set(listA)
    B = set(listB)
    output = {"A-B": A - B, "B-A": B - A}
    if ~exceptions_only:
        output["AuB"] = A.union(B)
        output["A^B"] = A.intersection(B)
    if simplify:
        for key, value in output.items():
            output[key] = len(value)
    return output

def setanalyse_df(dfA, dfB, key_cols=None, simplify=True, exceptions_only=False):
    """Given two lists, returns a dictionary of set analysis.

        A-B: set(A) - set(B)
        B-A: set(B) - set(A)
        AuB: A union B
        A^B: A intersection B

    Parameters
    ----------
    dfA : pd.DataFrame
        input list 1 to be evaluated
    dfB : pd.DataFrame
        input list 2 to be evaluated
    key_cols: list
        list of join column names. When None, common column names are used.
    simplify: bool
        if True, gives only len in each space. False gives the entire list.
    exceptions_only: bool
        if True, gives only A-B & B-A. False gives all 4.
        True is efficient while dealing with large sets or analyzing expections alone.

    Returns
    -------
    dict
        dictionery with the following keys 'A-B', 'B-A', 'A^B', 'AuB'
    """
    if key_cols is None:
        key_cols = list(set(dfA.columns).intersection(set(dfB.columns)))

    df1 = dfA.copy()
    df2 = dfB.copy()
    df1["ind"] = 1
    df2["ind"] = 1
    df1 = df1.groupby(key_cols).ind.sum().reset_index()
    df2 = df2.groupby(key_cols).ind.sum().reset_index()
    df = df1.merge(df2, how="outer", on=key_cols)
    ab = df.ind_y.isnull()
    ba = df.ind_x.isnull()
    output = {"A-B": ab, "B-A": ba}
    if ~exceptions_only:
        output["A^B"] = (~ab) & (~ba)
        output["AuB"] = True | ab
    if simplify:
        for key, value in output.items():
            output[key] = value.sum()
    else:
        for key, value in output.items():
            output[key] = df.loc[value, key_cols]

    return output

def merge_expectations(dfA, dfB, onA, onB=None, how="inner"):
    """Given merged dataframe and expectations analysis.

        expectations_result: dict

    Parameters
    ----------
    dfA : pd.DataFrame
        input list 1 to be evaluated
    dfB : pd.DataFrame
        input list 2 to be evaluated
    onA: list or column_name
        list of join column names. When None, common column names are used.
    onB: column_name
        if dfA and dfB to be merged on different column names
    how: merge_type
        {‘left’, ‘right’, ‘outer’, ‘inner’, ‘cross’}

    Returns
    -------
    expectations_result: dict

    Merge Recommendations:
    - Left expectations:
        Template Expectations:
            : B-A = 0 (Warning)
            : Same data type check (Error)
            : Column existance (Error)
            : Nulls in - On column from A & B = 0 (Warning)
    - Right expectations:
        Template Expectations:
            : A-B = 0 (Warning)
            : Same data type check (Error)
            : Column existance (Error)
            : Nulls in - On column from A & B = 0 (Warning)
    - Inner expectations
        Template Expectations:
            : B-A = 0 (Warning)
            : A-B = 0 (Warning)
            : Same data type check (Error)
            : Column existance (Error)
            : Nulls in - On column from A & B = 0 (Warning)
    - Cross expectations
        Template Expectations:
            : Same data type check (Error)
            : Column existance (Error)
            : Nulls in - On column from A & B = 0 (Warning)

    """
    dfb = dfB.copy()
    if onB is not None:
        dfb = dfb.rename({onB: onA}, axis="columns")

    # Set diff validation
    expectations_result = setanalyse_df(
        dfA, dfb, key_cols=onA, simplify=True, exceptions_only=False
    )

    # Data type check
    data_type_match_dict = {}
    data_type_match_warning = False
    if type(onA) == list:
        for col in onA:
            data_type_match_dict[col] = dfA[col].dtypes == dfb[col].dtypes
            if data_type_match_dict[col] is False:
                data_type_match_warning = True
    else:
        data_type_match_dict[onA] = dfA[onA].dtypes == dfb[onA].dtypes
        if data_type_match_dict[onA] is False:
            data_type_match_warning = True

    expectations_result["data_type_check"] = data_type_match_dict

    # None check
    expectations_result["dfA_nulls"] = dfA[onA].isnull().any()
    expectations_result["dfB_nulls"] = dfb[onA].isnull().any()

    # Assertions
    expect_warnings = {}
    expect_warnings["data_type_mismatch"] = data_type_match_warning
    expect_warnings["nulls_warning"] = (
        expectations_result["dfA_nulls"][expectations_result["dfA_nulls"] is True].size
        > 0
    )
    expect_warnings["nulls_warning"] = (
        expectations_result["dfB_nulls"][expectations_result["dfB_nulls"] is True].size
        > 0
    )
    if how == "left":
        expect_warnings["data_loss_B-A"] = expectations_result["B-A"] != 0
    elif how == "right":
        expect_warnings["data_loss_A-B"] = expectations_result["A-B"] != 0
    elif how == "inner":
        expect_warnings["data_loss_B-A"] = expectations_result["B-A"] != 0
        expect_warnings["data_loss_A-B"] = expectations_result["A-B"] != 0
    else:
        pass

    expectations_result["actionable_warnings"] = expect_warnings
    return expectations_result

def get_feature_names_from_column_transformer(col_trans):
    """Get feature names from a sklearn column transformer.

    The `ColumnTransformer` class in `scikit-learn` supports taking in a
    `pd.DataFrame` object and specifying `Transformer` operations on columns.
    The output of the `ColumnTransformer` is a numpy array that can used and
    does not contain the column names from the original dataframe. The class
    provides a `get_feature_names` method for this purpose that returns the
    column names corr. to the output array. Unfortunately, not all
    `scikit-learn` classes provide this method (e.g. `Pipeline`) and still
    being actively worked upon.
    This utility function is a temporary solution until the proper fix is
    available in the `scikit-learn` library.
    """
    from sklearn.pipeline import Pipeline
    from sklearn.impute import SimpleImputer
    from sklearn.preprocessing import OneHotEncoder as skohe

    # SimpleImputer has add_indicator attribute that distinguishes it from other transformers
    # Encoder had get_feature_names attribute that distinguishes it from other transformers
    col_name = []

    for (
        transformer_in_columns
    ) in (
        col_trans.transformers_
    ):  # the last transformer is ColumnTransformer's 'remainder'
        is_pipeline = 0
        raw_col_name = list(transformer_in_columns[2])

        if isinstance(transformer_in_columns[1], Pipeline):
            # if pipeline, get the last transformer
            transformer = transformer_in_columns[1].steps[-1][1]
            is_pipeline = 1
        else:
            transformer = transformer_in_columns[1]
        try:
            if isinstance(transformer, str):
                if transformer == "passthrough":
                    names = col_trans._feature_names_in[raw_col_name].tolist()

                elif transformer == "drop":
                    names = []

                else:
                    raise RuntimeError(
                        f"Unexpected transformer action for unaccounted cols :"
                        f"{transformer} : {raw_col_name}"
                    )

            elif isinstance(transformer, skohe):
                names = list(transformer.get_feature_names(raw_col_name))

            elif isinstance(transformer, SimpleImputer) and transformer.add_indicator:
                missing_indicator_indices = transformer.indicator_.features_
                missing_indicators = [
                    raw_col_name[idx] + "_missing_flag"
                    for idx in missing_indicator_indices
                ]

                names = raw_col_name + missing_indicators

            else:
                names = list(transformer.get_feature_names())

        except AttributeError:
            names = raw_col_name
        if is_pipeline:
            names = [f"{transformer_in_columns[0]}_{col_}" for col_ in names]
        col_name.extend(names)

    return col_name

def get_dataframe(arr, feature_names):
    """Convert an an numpy array into a dataframe.

    Parameters
    ----------
    arr : np.array
        Input 2D Array
    feature_names : list(string)
        List of column names in the same order of the data in the array

    Returns
    -------
    pd.DataFrame
    """
    return pd.DataFrame(arr, columns=feature_names)

# Pyflav utils
@pf.register_dataframe_method
def add_column_from_dt(df, col, new_col, op):
    """Add a column to the input dataframe with the relevant `op` funtion applied.

    Parameters
    ----------
    df : pd.DataFrame
        Input Dataframe where the new column has to be added
    col : string
        Column name of the column to be transformed
    new_col : string
        Column name of the transformed column
    op : `function`
        Operator or function to be run on the `col`. this takes pd.Series as input.

    Returns
    -------
    pd.DataFrame
        DataFrame with the new column
    """
    mask = ~df[col].isna()
    df.loc[mask, new_col] = op(df[col][mask])
    return df

@pf.register_dataframe_method
def remove_duplicate_rows(df, col_names, keep_first=True):
    """Remove duplicate rows wrt to the input columns.

    Parameters
    ----------
    df : pd.DataFrame
        Input dataframe where duplicates have to be removed
    col_names : list(str)
        List of column names to be used as key to drop duplicates
    keep_first : bool, optional
        option whether to keep the first row in a set of duplicates, by default True

    Returns
    -------
    pd.DataFrame
        Datsframe with duplicates removed
    """
    keep = "first" if keep_first else "last"
    mask = df.duplicated(subset=col_names, keep=keep)
    return df[~mask]

@pf.register_dataframe_method
def passthrough(df):
    """Return the input dataframe."""
    return df

def merge_info(left_df, right_df, merge_df):
    """Get the column and row comparison summary of the provided dataframes.

    The returned data is useful to quickly sanity check a merge operation by
    checking the number of rows and columns.

    Parameters
    ----------
    left_df : pd.DataFrame
    right_df : pd.DataFrame
    merge_df : pd.DataFrame
        merged dataframe of left_df and right_df

    Returns
    -------
    pd.DataFrame
        Merge columns and rows comparison summary dataframe
    """
    n_cols = [len(left_df.columns), len(right_df.columns), len(merge_df.columns)]
    n_rows = [len(left_df), len(right_df), len(merge_df)]
    index = ["left_df", "right_df", "merged_df"]
    return pd.DataFrame({"n_cols": n_cols, "n_rows": n_rows}, index=index)

def custom_train_test_split(df, splitter=None, by=None):
    """Split the provided dataset using custom criteria.

    The primary utility of this function is to be able to provide a custom
    criteria to split the datasets.

    Parameters
    ----------
    df: pd.DataFrame
        The tabular dataset to be split.

    splitter: object
        The splitter object should have a method named `split` that takes
        in a dataframe and a series. Any of the ``sklearn`` splitters listed
        here are acceptable: https://scikit-learn.org/stable/modules/classes.html#splitter-classes  # noqa

    by: str or callable
        The criteria can simply be a ``column`` in the input dataframe but
        can also be a ``function``. When the latter option is used, the
        function should take in the input ``dataframe`` and return a series
        that will then be used by the splitter object.

    Returns
    -------
    splits: list
        The list of splits generated by the splitter object.
    """

    if isinstance(by, str):
        split_by = df[by]
    elif callable(by):
        split_by = by(df)
    else:
        raise ValueError(
            "`by` must be a column name or a callable that returns a series"
        )
    split_sets = []
    for indexes in splitter.split(df, split_by):
        for index in indexes:
            split_sets.append(df.loc[index])
    return split_sets

def load_dataframe(path):
    """Load parquet file from the `path` specified.

    Parameters
    ----------
    path : string
        urlpath for the data to be loaded

    Returns
    -------
    pd.DataFrame
    """
    df = pd.read_parquet(path).reset_index()
    if "index" in df.columns:
        df = df.drop(columns="index")
    return df

def load_pipeline(path):
    """Load model pipeline from the `path` specified.

    Parameters
    ----------
    path : string
        urlpath for the pipeline to be loaded

    Returns
    -------
    pd.DataFrame
    """
    return joblib.load(path)

def hash_object(obj, expensive=False, block_size=4096):
    """Return a content based hash for the input `obj`.

    The returned hash value can be used to verify equality of two objects.
    If the hash values of two objects are equal, then they are identical and
    one can be replaced with another.

    Parameters
    ----------
    obj: Object

    Returns
    -------
    string
    """
    hasher = hashlib.sha256()

    if expensive:
        with tempfile.TemporaryDirectory() as tmp_dir:
            tmp_fname = op.join(tmp_dir, "tmp.joblib")
            joblib.dump(obj, tmp_fname)
            with open(tmp_fname, "rb") as fp:
                while True:
                    data = fp.read(block_size)
                    if len(data) <= 0:
                        break
                    hasher.update(data)
    else:
        fp = BytesIO()
        joblib.dump(obj, fp)
        data = fp.getvalue()
        hasher.update(data)

    return hasher.hexdigest()


import numpy as np

class Conv2D:
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=1):
        self.filters = np.random.randn(
            out_channels, in_channels, kernel_size, kernel_size
        )
        self.biases = np.random.randn(out_channels, 1)

        self.stride = stride
        self.padding = padding

    def set_weights_biases(self, filters, biases):
        self.filters = filters
        self.biases = biases

    def forward(self, input_data):
        self.last_input_data = input_data

        batch_size, in_channels, in_height, in_width = input_data.shape
        out_channels, _, filter_height, filter_width = self.filters.shape

        out_height = (
            int((in_height - filter_height + 2 * self.padding) / self.stride) + 1
        )
        out_width = int((in_width - filter_width + 2 * self.padding) / self.stride) + 1

        self.last_input_data_padded = np.pad(
            input_data,
            (
                (0, 0),
                (0, 0),
                (self.padding, self.padding),
                (self.padding, self.padding),
            ),
        )

        output_data = np.zeros((batch_size, out_channels, out_height, out_width))

        for c_out in range(out_channels):
            for c_in in range(in_channels):
                for i in range(out_height):
                    for j in range(out_width):
                        input_window = self.last_input_data_padded[
                            :,
                            c_in,
                            i * self.stride : i * self.stride + filter_height,
                            j * self.stride : j * self.stride + filter_width,
                        ]
                        filters = self.filters[c_out, c_in, :, :]
                        gradients = input_window * filters + self.biases[c_out]
                        output_data[:, c_out, i, j] += np.sum(gradients, axis=(1, 2))

        return output_data

    def backward(self, d_out):
        batch_size, out_channels, out_height, out_width = d_out.shape
        _, in_channels, filter_height, filter_width = self.filters.shape

        d_filters = np.zeros_like(self.filters)
        d_biases = np.zeros_like(self.biases)
        d_input_padded = np.zeros_like(self.last_input_data_padded, dtype=float)

        for c_out in range(out_channels):
            for c_in in range(in_channels):
                for i in range(out_height):
                    for j in range(out_width):
                        input_window = self.last_input_data_padded[
                            :,
                            c_in,
                            i * self.stride : i * self.stride + filter_height,
                            j * self.stride : j * self.stride + filter_width,
                        ]
                        gradients = input_window * d_out[:, c_out, i, j, None, None]
                        d_filters[c_out, c_in, :, :] += np.sum(
                            gradients, axis=(0, 1, 2)
                        )
                        d_biases[c_out] += np.sum(d_out[:, c_out, i, j])
                        d_input_padded[
                            :,
                            c_in,
                            i * self.stride : i * self.stride + filter_height,
                            j * self.stride : j * self.stride + filter_width,
                        ] += np.sum(
                            self.filters[c_out, c_in, :, :, None, None]
                            * d_out[:, c_out, i, j, None, None, None],
                            axis=(1, 2),
                        )

        d_input = d_input_padded[
            :, :, self.padding : -self.padding, self.padding : -self.padding
        ]

        return d_filters, d_biases, d_input

    def update(self, d_filters, d_biases, learning_rate):
        self.filters -= learning_rate * d_filters
        self.biases -= learning_rate * d_biases


import dash
import dash_leaflet as dl
import dash_leaflet.express as dlx
from dash import dcc, html,Input, Output, callback
import geopandas as gpd
import json
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
from shapely.geometry import box, Point, Polygon
from dash_extensions.javascript import arrow_function, assign
import dash_bootstrap_components as dbc
from dash_loading_spinners import Hash
from pages.ranking_ambiental.bases_mapa import bsas,VAR_PUNTAJE,  classes

# Establecer el renderizador predeterminado para Plotly.
pio.renderers.default = 'browser'

# Agregar informacion de etiquetas
bsas["tooltip"] = bsas["nam"]

#Transformar a geobuf
bsas_geojson = json.loads(bsas.to_json(na="keep"))
geobuf = dlx.geojson_to_geobuf(bsas_geojson)

# Definir colores para las clases.
colorscale = ['#F5BBCB', '#F2A4B6', '#EF8DA1', '#ED769C', '#EB5F87', '#E94872', '#E7315D', '#E51A48']
style = dict(weight=2, opacity=1, color='white', dashArray='3', fillOpacity=0.7)

# Crear colorbar.
ctg = ["{}+".format(cls, classes[i + 1]) for i, cls in enumerate(classes[:-1])] + ["{}+".format(classes[-1])]

# # Barra de colores categóricos personalizada
# colorbar = dlx.categorical_colorbar(
#     categories=ctg,
#     colorscale=colorscale,
#     width=200,  # Ancho personalizado
#     height=20,  # Altura personalizada
#     position="bottomleft",  # Posición
# )

# Lógica de representación del GeoJSON.
style_handle =  assign("""function(feature, context){
    const {classes, colorscale, style, colorProp} = context.props.hideout;
    const value = feature.properties[colorProp];
    if (value === null || isNaN(value)) {
        // Asigna color gris para observaciones sin datos
        style.fillColor = 'rgb(128, 128, 128)';
        style.weight = 1;
        style.dashArray = false;
    } else {
        for (let i = 0; i < classes.length; ++i) {
            if (value > classes[i]) {
                style.fillColor = colorscale[i];
                style.weight = 1;
                style.dashArray = false;
            }
        }
    }
    return style;
}""")

# Crear la figura rectangular con degradé de colores
color_bar = html.Div(
    style={
        'width': '100%',
        'height': '25px',
        'background': f'linear-gradient(to right, {", ".join(colorscale)})'
    }
)

colorscale_reference = html.Div(
    id='colorscale-reference',
    children=[dbc.Col([
                dbc.Row([html.Div("PROTECCIÓN", style={'text-align': 'center','font-size': '12px','color': 'black', 'font-weight': 'bold'})]),
                dbc.Row([dbc.Col([html.Div("MENOR", style={'text-align':'right','color': 'black'})], md=3),
                       dbc.Col([color_bar], md=6),
                       dbc.Col([html.Div("MAYOR", style={'text-align': 'left','color': 'black'})], md=3),
                    ]),
            ]),
        ],

    style={
        'font-size': '12px',
    }
)

Mapa =dl.Map(
                            id="mapa",
                            zoom=15,
                            dragging=False,
                            # touchZoom=False,
                            zoomControl=False,
                            scrollWheelZoom=False,
                            doubleClickZoom=False,
                            children=[

                                dl.GeoJSON(
                                    data=geobuf,
                                    format='geobuf',
                                    zoomToBounds=True,
                                    zoomToBoundsOnClick=False,
                                    options=dict(style=style_handle),
                                    hoverStyle=arrow_function(dict(weight=5, dashArray='')),
                                    hideout=dict(colorscale=colorscale, classes=classes, style=style, colorProp=VAR_PUNTAJE)
                                ),

                            ],
                            className="min-vh-50 bg-white"
                        )

titulo_del_mapa_1 = html.H6(
    "PROVINCIA DE BUENOS AIRES",
    style={
        'font-weight': 'bold',
        'color': 'black',
        'white-space': 'pre-line',  # Forzar el salto de línea
    },
    className="d-flex justify-content-center align-items-center"
)
titulo_del_mapa_2 = html.H6(
    "CONURBANO",
    style={
        'font-weight': 'bold',  # Hacer que el texto sea negrita
        'color': 'black',
    },
    className="d-flex justify-content-center align-items-center"
)

mapa_layout = html.Div(
    id="mapa-container",
    children=[
        dbc.Row([
            dbc.Row([dbc.Col([titulo_del_mapa_1], md=7), dbc.Col([titulo_del_mapa_2], md=2)]),
            dbc.Row([dbc.Col([Mapa], md=12)]),
            dbc.Row([dbc.Col([html.Div()], md=3),dbc.Col([colorscale_reference], md=6),dbc.Col([html.Div()], md=3) ]),
            ])
    ],
)

# Crea la tarjeta centrada
mapa_card = dbc.Card(
    [
        dbc.CardBody(mapa_layout),
    ],
    color="light",
    class_name="shadow",
    outline=True,
    id="censo-empleo"
)