import argparse
import copy
import json
import os
import time
from typing import Tuple, Dict, Any
import numpy as np

from nuscenes import NuScenes
from nuscenes.eval.common.config import config_factory
from nuscenes.eval.common.data_classes import EvalBoxes
from nuscenes.eval.detection.data_classes import DetectionConfig
from nuscenes.eval.detection.evaluate import NuScenesEval
from pyquaternion import Quaternion

from nuscenes import NuScenes
from nuscenes.eval.common.data_classes import EvalBoxes
from nuscenes.utils.data_classes import Box
from nuscenes.eval.common.loaders import add_center_dist, filter_eval_boxes
import tqdm
from nuscenes.utils.geometry_utils import view_points, BoxVisibility
import pycocotools.mask as mask_util
import argparse
import json
import os
import random
import time
from typing import Tuple, Dict, Any

import numpy as np

from nuscenes import NuScenes
from nuscenes.eval.common.config import config_factory
from nuscenes.eval.common.data_classes import EvalBoxes
from nuscenes.eval.common.loaders import add_center_dist, filter_eval_boxes
from nuscenes.eval.detection.algo import calc_ap, calc_tp
from nuscenes.eval.detection.constants import TP_METRICS
from nuscenes.eval.detection.data_classes import DetectionConfig, DetectionMetrics, DetectionBox, \
    DetectionMetricDataList
from nuscenes.eval.detection.render import summary_plot, class_pr_curve, dist_pr_curve, visualize_sample
from nuscenes.eval.common.utils import quaternion_yaw, Quaternion
from mmdet3d.core.bbox.iou_calculators import BboxOverlaps3D
from IPython import embed
import json
from typing import Any

import numpy as np
from matplotlib import pyplot as plt

from nuscenes import NuScenes
from nuscenes.eval.common.data_classes import EvalBoxes
from nuscenes.eval.common.render import setup_axis
from nuscenes.eval.common.utils import boxes_to_sensor
from nuscenes.eval.detection.constants import TP_METRICS, DETECTION_NAMES, DETECTION_COLORS, TP_METRICS_UNITS, \
    PRETTY_DETECTION_NAMES, PRETTY_TP_METRICS
from nuscenes.eval.detection.data_classes import DetectionMetrics, DetectionMetricData, DetectionMetricDataList
from nuscenes.utils.data_classes import LidarPointCloud
from nuscenes.utils.geometry_utils import view_points
from .eval_utils import load_prediction, load_gt, accumulate, accumulate_motion, \
    DetectionMotionBox, DetectionMotionBox_modified, DetectionMotionMetricData, \
    DetectionMotionMetrics, DetectionMotionMetricDataList
from .metric_utils import traj_fde
from prettytable import PrettyTable

TP_METRICS = [
    'trans_err',
    'scale_err',
    'orient_err',
    'vel_err',
    'attr_err',
    'min_ade_err',
    'min_fde_err',
    'miss_rate_err']
TP_TRAJ_METRICS = ['min_ade_err', 'min_fde_err', 'miss_rate_err']
Axis = Any

def class_tp_curve(md_list: DetectionMetricDataList,
                   metrics: DetectionMetrics,
                   detection_name: str,
                   min_recall: float,
                   dist_th_tp: float,
                   savepath: str = None,
                   ax: Axis = None) -> None:
    """
    Plot the true positive curve for the specified class.
    :param md_list: DetectionMetricDataList instance.
    :param metrics: DetectionMetrics instance.
    :param detection_name:
    :param min_recall: Minimum recall value.
    :param dist_th_tp: The distance threshold used to determine matches.
    :param savepath: If given, saves the the rendering here instead of displaying.
    :param ax: Axes onto which to render.
    """
    # Get metric data for given detection class with tp distance threshold.

    md = md_list[(detection_name, dist_th_tp)]
    min_recall_ind = round(100 * min_recall)
    if min_recall_ind <= md.max_recall_ind:
        # For traffic_cone and barrier only a subset of the metrics are
        # plotted.
        rel_metrics = [
            m for m in TP_METRICS if not np.isnan(
                metrics.get_label_tp(
                    detection_name, m))]
        ylimit = max([max(getattr(md, metric)[min_recall_ind:md.max_recall_ind + 1])
                     for metric in rel_metrics]) * 1.1
    else:
        ylimit = 1.0

    # Prepare axis.
    if ax is None:
        ax = setup_axis(
            title=PRETTY_DETECTION_NAMES[detection_name],
            xlabel='Recall',
            ylabel='Error',
            xlim=1,
            min_recall=min_recall)
    ax.set_ylim(0, ylimit)

    # Plot the recall vs. error curve for each tp metric.
    for metric in TP_METRICS:
        tp = metrics.get_label_tp(detection_name, metric)

        # Plot only if we have valid data.
        if tp is not np.nan and min_recall_ind <= md.max_recall_ind:
            recall, error = md.recall[:md.max_recall_ind +
                                      1], getattr(md, metric)[:md.max_recall_ind + 1]
        else:
            recall, error = [], []

        # Change legend based on tp value
        if tp is np.nan:
            label = '{}: n/a'.format(PRETTY_TP_METRICS[metric])
        elif min_recall_ind > md.max_recall_ind:
            label = '{}: nan'.format(PRETTY_TP_METRICS[metric])
        else:
            label = '{}: {:.2f} ({})'.format(
                PRETTY_TP_METRICS[metric], tp, TP_METRICS_UNITS[metric])
        if metric == 'trans_err':
            label += f' ({md.max_recall_ind})'  # add recall
            print(f'Recall: {detection_name}: {md.max_recall_ind/100}')
        ax.plot(recall, error, label=label)
    ax.axvline(x=md.max_recall, linestyle='-.', color=(0, 0, 0, 0.3))
    ax.legend(loc='best')

    if savepath is not None:
        plt.savefig(savepath)
        plt.close()

def center_in_image(box,
                    intrinsic: np.ndarray,
                    imsize: Tuple[int,
                                  int],
                    vis_level: int = BoxVisibility.ANY) -> bool:
    """
    Check if a box is visible inside an image without accounting for occlusions.
    :param box: The box to be checked.
    :param intrinsic: <float: 3, 3>. Intrinsic camera matrix.
    :param imsize: (width, height).
    :param vis_level: One of the enumerations of <BoxVisibility>.
    :return True if visibility condition is satisfied.
    """

    center_3d = box.center.reshape(3, 1)
    center_img = view_points(center_3d, intrinsic, normalize=True)[:2, :]

    visible = np.logical_and(
        center_img[0, :] > 0, center_img[0, :] < imsize[0])
    visible = np.logical_and(visible, center_img[1, :] < imsize[1])
    visible = np.logical_and(visible, center_img[1, :] > 0)
    visible = np.logical_and(visible, center_3d[2, :] > 1)

    # True if a corner is at least 0.1 meter in front of the camera.
    in_front = center_3d[2, :] > 0.1

    if vis_level == BoxVisibility.ALL:
        return all(visible) and all(in_front)
    elif vis_level == BoxVisibility.ANY:
        return any(visible) and all(in_front)
    elif vis_level == BoxVisibility.NONE:
        return True
    else:
        raise ValueError("vis_level: {} not valid".format(vis_level))

def exist_corners_in_image_but_not_all(box,
                                       intrinsic: np.ndarray,
                                       imsize: Tuple[int,
                                                     int],
                                       vis_level: int = BoxVisibility.ANY) -> bool:
    """
    Check if a box is visible in images but not all corners in image .
    :param box: The box to be checked.
    :param intrinsic: <float: 3, 3>. Intrinsic camera matrix.
    :param imsize: (width, height).
    :param vis_level: One of the enumerations of <BoxVisibility>.
    :return True if visibility condition is satisfied.
    """

    corners_3d = box.corners()
    corners_img = view_points(corners_3d, intrinsic, normalize=True)[:2, :]

    visible = np.logical_and(
        corners_img[0, :] > 0, corners_img[0, :] < imsize[0])
    visible = np.logical_and(visible, corners_img[1, :] < imsize[1])
    visible = np.logical_and(visible, corners_img[1, :] > 0)
    visible = np.logical_and(visible, corners_3d[2, :] > 1)

    # True if a corner is at least 0.1 meter in front of the camera.
    in_front = corners_3d[2, :] > 0.1

    if any(visible) and not all(visible) and all(in_front):
        return True
    else:
        return False

def filter_eval_boxes_by_id(nusc: NuScenes,
                            eval_boxes: EvalBoxes,
                            id=None,
                            verbose: bool = False) -> EvalBoxes:
    """
    Applies filtering to boxes. Distance, bike-racks and points per box.
    :param nusc: An instance of the NuScenes class.
    :param eval_boxes: An instance of the EvalBoxes class.
    :param is: the anns token set that used to keep bboxes.
    :param verbose: Whether to print to stdout.
    """

    # Accumulators for number of filtered boxes.
    total, anns_filter = 0, 0
    for ind, sample_token in enumerate(eval_boxes.sample_tokens):

        # Filter on anns
        total += len(eval_boxes[sample_token])
        filtered_boxes = []
        for box in eval_boxes[sample_token]:
            if box.token in id:
                filtered_boxes.append(box)
        anns_filter += len(filtered_boxes)
        eval_boxes.boxes[sample_token] = filtered_boxes

    if verbose:
        print("=> Original number of boxes: %d" % total)
        print("=> After anns based filtering: %d" % anns_filter)

    return eval_boxes

def filter_eval_boxes_by_visibility(
        ori_eval_boxes: EvalBoxes,
        visibility=None,
        verbose: bool = False) -> EvalBoxes:
    """
    Applies filtering to boxes. Distance, bike-racks and points per box.
    :param nusc: An instance of the NuScenes class.
    :param eval_boxes: An instance of the EvalBoxes class.
    :param is: the anns token set that used to keep bboxes.
    :param verbose: Whether to print to stdout.
    """

    # Accumulators for number of filtered boxes.
    eval_boxes = copy.deepcopy(ori_eval_boxes)
    total, anns_filter = 0, 0
    for ind, sample_token in enumerate(eval_boxes.sample_tokens):
        # Filter on anns
        total += len(eval_boxes[sample_token])
        filtered_boxes = []
        for box in eval_boxes[sample_token]:
            if box.visibility == visibility:
                filtered_boxes.append(box)
        anns_filter += len(filtered_boxes)
        eval_boxes.boxes[sample_token] = filtered_boxes

    if verbose:
        print("=> Original number of boxes: %d" % total)
        print("=> After visibility based filtering: %d" % anns_filter)

    return eval_boxes

def filter_by_sample_token(
        ori_eval_boxes,
        valid_sample_tokens=[],
        verbose=False):
    eval_boxes = copy.deepcopy(ori_eval_boxes)
    for sample_token in eval_boxes.sample_tokens:
        if sample_token not in valid_sample_tokens:
            eval_boxes.boxes.pop(sample_token)
    return eval_boxes

def filter_eval_boxes_by_overlap(nusc: NuScenes,
                                 eval_boxes: EvalBoxes,
                                 verbose: bool = False) -> EvalBoxes:
    """
    Applies filtering to boxes. basedon overlap .
    :param nusc: An instance of the NuScenes class.
    :param eval_boxes: An instance of the EvalBoxes class.
    :param verbose: Whether to print to stdout.
    """

    # Accumulators for number of filtered boxes.
    cams = ['CAM_FRONT',
            'CAM_FRONT_RIGHT',
            'CAM_BACK_RIGHT',
            'CAM_BACK',
            'CAM_BACK_LEFT',
            'CAM_FRONT_LEFT']

    total, anns_filter = 0, 0
    for ind, sample_token in enumerate(eval_boxes.sample_tokens):

        # Filter on anns
        total += len(eval_boxes[sample_token])
        sample_record = nusc.get('sample', sample_token)
        filtered_boxes = []
        for box in eval_boxes[sample_token]:
            count = 0
            for cam in cams:
                '''
                copy-paste form nuscens
                '''
                sample_data_token = sample_record['data'][cam]
                sd_record = nusc.get('sample_data', sample_data_token)
                cs_record = nusc.get(
                    'calibrated_sensor',
                    sd_record['calibrated_sensor_token'])
                sensor_record = nusc.get('sensor', cs_record['sensor_token'])
                pose_record = nusc.get('ego_pose', sd_record['ego_pose_token'])
                cam_intrinsic = np.array(cs_record['camera_intrinsic'])
                imsize = (sd_record['width'], sd_record['height'])
                new_box = Box(
                    box.translation,
                    box.size,
                    Quaternion(
                        box.rotation),
                    name=box.detection_name,
                    token='')

                # Move box to ego vehicle coord system.
                new_box.translate(-np.array(pose_record['translation']))
                new_box.rotate(Quaternion(pose_record['rotation']).inverse)

                #  Move box to sensor coord system.
                new_box.translate(-np.array(cs_record['translation']))
                new_box.rotate(Quaternion(cs_record['rotation']).inverse)

                if center_in_image(
                        new_box,
                        cam_intrinsic,
                        imsize,
                        vis_level=BoxVisibility.ANY):
                    count += 1
                # if exist_corners_in_image_but_not_all(new_box, cam_intrinsic, imsize, vis_level=BoxVisibility.ANY):
                #    count += 1

            if count > 1:
                with open('center_overlap.txt', 'a') as f:
                    try:
                        f.write(box.token + '\n')
                    except BaseException:
                        pass
                filtered_boxes.append(box)
        anns_filter += len(filtered_boxes)
        eval_boxes.boxes[sample_token] = filtered_boxes

    verbose = True

    if verbose:
        print("=> Original number of boxes: %d" % total)
        print("=> After anns based filtering: %d" % anns_filter)

    return eval_boxes

class MotionEval(NuScenesEval):
    """
    Dummy class for backward-compatibility. Same as DetectionEval.
    """

    def __init__(self,
                 nusc: NuScenes,
                 config: DetectionConfig,
                 result_path: str,
                 eval_set: str,
                 output_dir: str = None,
                 verbose: bool = True,
                 overlap_test=False,
                 eval_mask=False,
                 data_infos=None,
                 category_convert_type='motion_category',
                 ):
        """
        Initialize a DetectionEval object.
        :param nusc: A NuScenes object.
        :param config: A DetectionConfig object.
        :param result_path: Path of the nuScenes JSON result file.
        :param eval_set: The dataset split to evaluate on, e.g. train, val or test.
        :param output_dir: Folder to save plots and results to.
        :param verbose: Whether to print to stdout.
        """

        self.nusc = nusc
        self.result_path = result_path
        self.eval_set = eval_set
        self.output_dir = output_dir
        self.verbose = verbose
        self.cfg = config
        self.overlap_test = overlap_test
        self.eval_mask = eval_mask
        self.data_infos = data_infos
        # Check result file exists.
        assert os.path.exists(
            result_path), 'Error: The result file does not exist!'

        # Make dirs.
        self.plot_dir = os.path.join(self.output_dir, 'plots')
        if not os.path.isdir(self.output_dir):
            os.makedirs(self.output_dir)
        if not os.path.isdir(self.plot_dir):
            os.makedirs(self.plot_dir)

        # Load data.
        if verbose:
            print('Initializing nuScenes detection evaluation')
        self.pred_boxes, self.meta = load_prediction(self.result_path, self.cfg.max_boxes_per_sample, DetectionMotionBox,
                                                     verbose=verbose, category_convert_type=category_convert_type)
        self.gt_boxes = load_gt(
            self.nusc,
            self.eval_set,
            DetectionMotionBox_modified,
            verbose=verbose,
            category_convert_type=category_convert_type)

        assert set(self.pred_boxes.sample_tokens) == set(self.gt_boxes.sample_tokens), \
            "Samples in split doesn't match samples in predictions."

        # Add center distances.
        self.pred_boxes = add_center_dist(nusc, self.pred_boxes)
        self.gt_boxes = add_center_dist(nusc, self.gt_boxes)

        # Filter boxes (distance, points per box, etc.).

        if verbose:
            print('Filtering predictions')
        self.pred_boxes = filter_eval_boxes(
            nusc, self.pred_boxes, self.cfg.class_range, verbose=verbose)
        if verbose:
            print('Filtering ground truth annotations')
        self.gt_boxes = filter_eval_boxes(
            nusc, self.gt_boxes, self.cfg.class_range, verbose=verbose)

        if self.overlap_test:
            self.pred_boxes = filter_eval_boxes_by_overlap(
                self.nusc, self.pred_boxes)

            self.gt_boxes = filter_eval_boxes_by_overlap(
                self.nusc, self.gt_boxes, verbose=True)

        self.all_gt = copy.deepcopy(self.gt_boxes)
        self.all_preds = copy.deepcopy(self.pred_boxes)
        self.sample_tokens = self.gt_boxes.sample_tokens

        self.index_map = {}
        for scene in nusc.scene:
            first_sample_token = scene['first_sample_token']
            sample = nusc.get('sample', first_sample_token)
            self.index_map[first_sample_token] = 1
            index = 2
            while sample['next'] != '':
                sample = nusc.get('sample', sample['next'])
                self.index_map[sample['token']] = index
                index += 1

    def update_gt(self, type_='vis', visibility='1', index=1):
        if type_ == 'vis':
            self.visibility_test = True
            if self.visibility_test:
                '''[{'description': 'visibility of whole object is between 0 and 40%',
                'token': '1',
                'level': 'v0-40'},
                {'description': 'visibility of whole object is between 40 and 60%',
                'token': '2',
                'level': 'v40-60'},
                {'description': 'visibility of whole object is between 60 and 80%',
                'token': '3',
                'level': 'v60-80'},
                {'description': 'visibility of whole object is between 80 and 100%',
                'token': '4',
                'level': 'v80-100'}]'''

                self.gt_boxes = filter_eval_boxes_by_visibility(
                    self.all_gt, visibility, verbose=True)

        elif type_ == 'ord':

            valid_tokens = [
                key for (
                    key,
                    value) in self.index_map.items() if value == index]
            # from IPython import embed
            # embed()
            self.gt_boxes = filter_by_sample_token(self.all_gt, valid_tokens)
            self.pred_boxes = filter_by_sample_token(
                self.all_preds, valid_tokens)
        self.sample_tokens = self.gt_boxes.sample_tokens

    def evaluate(self) -> Tuple[DetectionMotionMetrics,
                                DetectionMotionMetricDataList]:
        """
        Performs the actual evaluation.
        :return: A tuple of high-level and the raw metric data.
        """
        start_time = time.time()

        # -----------------------------------
        # Step 1: Accumulate metric data for all classes and distance thresholds.
        # -----------------------------------
        if self.verbose:
            print('Accumulating metric data...')
        metric_data_list = DetectionMotionMetricDataList()

        # print(self.cfg.dist_fcn_callable, self.cfg.dist_ths)
        # self.cfg.dist_ths = [0.3]
        # self.cfg.dist_fcn_callable
        for class_name in self.cfg.class_names:
            for dist_th in self.cfg.dist_ths:
                md, _, _, _ = accumulate(
                    self.gt_boxes, self.pred_boxes, class_name, self.cfg.dist_fcn_callable, dist_th)
                metric_data_list.set(class_name, dist_th, md)

        # -----------------------------------
        # Step 2: Calculate metrics from the data.
        # -----------------------------------
        if self.verbose:
            print('Calculating metrics...')
        metrics = DetectionMotionMetrics(self.cfg)

        traj_metrics = {}
        for class_name in self.cfg.class_names:
            # Compute APs.
            for dist_th in self.cfg.dist_ths:
                metric_data = metric_data_list[(class_name, dist_th)]
                ap = calc_ap(
                    metric_data,
                    self.cfg.min_recall,
                    self.cfg.min_precision)
                metrics.add_label_ap(class_name, dist_th, ap)
            # Compute TP metrics.
            for metric_name in TP_METRICS:
                metric_data = metric_data_list[(
                    class_name, self.cfg.dist_th_tp)]
                if class_name in ['traffic_cone'] and metric_name in [
                        'attr_err', 'vel_err', 'orient_err']:
                    tp = np.nan
                elif class_name in ['barrier'] and metric_name in ['attr_err', 'vel_err']:
                    tp = np.nan
                else:
                    tp = calc_tp(metric_data, self.cfg.min_recall, metric_name)
                    if metric_name in TP_TRAJ_METRICS:
                        if class_name not in traj_metrics:
                            traj_metrics[class_name] = {}
                        traj_metrics[class_name][metric_name] = tp
                metrics.add_label_tp(class_name, metric_name, tp)
        print_traj_metrics(traj_metrics)

        # Compute evaluation time.
        metrics.add_runtime(time.time() - start_time)

        return metrics, metric_data_list

    def evaluate_motion(
            self) -> Tuple[DetectionMotionMetrics, DetectionMotionMetricDataList]:
        """
        Performs the actual evaluation.
        :return: A tuple of high-level and the raw metric data.
        """
        start_time = time.time()

        self.cfg.dist_ths = [1.0]
        self.cfg.dist_th_tp = 1.0  # center dist for detection
        traj_dist_th = 2.0  # FDE for traj

        # -----------------------------------
        # Step 1: Accumulate metric data for all classes and distance thresholds.
        # -----------------------------------
        if self.verbose:
            print('Accumulating metric data...')
        metric_data_list = DetectionMotionMetricDataList()

        for class_name in self.cfg.class_names:
            for dist_th in self.cfg.dist_ths:
                md, _, _, _ = accumulate_motion(
                    self.gt_boxes, self.pred_boxes, class_name, self.cfg.dist_fcn_callable, traj_fde, dist_th, traj_dist_th)
                metric_data_list.set(class_name, dist_th, md)

        # -----------------------------------
        # Step 2: Calculate metrics from the data.
        # -----------------------------------
        if self.verbose:
            print('Calculating metrics...')
        metrics = DetectionMotionMetrics(self.cfg)

        traj_metrics = {}
        for class_name in self.cfg.class_names:
            # Compute APs.
            for dist_th in self.cfg.dist_ths:
                metric_data = metric_data_list[(class_name, dist_th)]
                ap = calc_ap(
                    metric_data,
                    self.cfg.min_recall,
                    self.cfg.min_precision)
                metrics.add_label_ap(class_name, dist_th, ap)
            # Compute TP metrics.
            for metric_name in TP_METRICS:
                metric_data = metric_data_list[(
                    class_name, self.cfg.dist_th_tp)]
                if class_name in ['traffic_cone'] and metric_name in [
                        'attr_err', 'vel_err', 'orient_err']:
                    tp = np.nan
                elif class_name in ['barrier'] and metric_name in ['attr_err', 'vel_err']:
                    tp = np.nan
                else:
                    tp = calc_tp(metric_data, self.cfg.min_recall, metric_name)
                    if metric_name in TP_TRAJ_METRICS:
                        if class_name not in traj_metrics:
                            traj_metrics[class_name] = {}
                        traj_metrics[class_name][metric_name] = tp
                metrics.add_label_tp(class_name, metric_name, tp)
        print_traj_metrics(traj_metrics)

        # Compute evaluation time.
        metrics.add_runtime(time.time() - start_time)

        return metrics, metric_data_list

    def evaluate_epa(
            self) -> Tuple[DetectionMotionMetrics, DetectionMotionMetricDataList]:
        """
        Performs the actual evaluation.
        :return: A tuple of high-level and the raw metric data.
        """
        start_time = time.time()

        self.cfg.dist_ths = [2.0]
        self.cfg.dist_th_tp = 2.0  # center dist for detection
        traj_dist_th = 2.0  # FDE for traj

        # -----------------------------------
        # Step 1: Accumulate metric data for all classes and distance thresholds.
        # -----------------------------------
        if self.verbose:
            print('Accumulating metric data...')
        metric_data_list = DetectionMotionMetricDataList()

        for class_name in self.cfg.class_names:
            for dist_th in self.cfg.dist_ths:
                md, N_det_tp, N_det_fp, N_det_gt = accumulate(
                    self.gt_boxes, self.pred_boxes, class_name, self.cfg.dist_fcn_callable, dist_th)
                md, N_det_traj_tp, N_det_traj_fp, N_det_traj_gt = accumulate_motion(
                    self.gt_boxes, self.pred_boxes, class_name, self.cfg.dist_fcn_callable, traj_fde, dist_th, traj_dist_th)
                metric_data_list.set(class_name, dist_th, md)
                EPA = (N_det_traj_tp - 0.5 * N_det_fp) / (N_det_gt + 1e-5)
                print(N_det_traj_tp, N_det_fp, N_det_gt)
                print('EPA ', class_name, EPA)

        # -----------------------------------
        # Step 2: Calculate metrics from the data.
        # -----------------------------------
        if self.verbose:
            print('Calculating metrics...')
        metrics = DetectionMotionMetrics(self.cfg)

        traj_metrics = {}
        for class_name in self.cfg.class_names:
            # Compute APs.
            for dist_th in self.cfg.dist_ths:
                metric_data = metric_data_list[(class_name, dist_th)]
                ap = calc_ap(
                    metric_data,
                    self.cfg.min_recall,
                    self.cfg.min_precision)
                metrics.add_label_ap(class_name, dist_th, ap)
            # Compute TP metrics.
            for metric_name in TP_METRICS:
                metric_data = metric_data_list[(
                    class_name, self.cfg.dist_th_tp)]
                if class_name in ['traffic_cone'] and metric_name in [
                        'attr_err', 'vel_err', 'orient_err']:
                    tp = np.nan
                elif class_name in ['barrier'] and metric_name in ['attr_err', 'vel_err']:
                    tp = np.nan
                else:
                    tp = calc_tp(metric_data, self.cfg.min_recall, metric_name)
                    if metric_name in TP_TRAJ_METRICS:
                        if class_name not in traj_metrics:
                            traj_metrics[class_name] = {}
                        traj_metrics[class_name][metric_name] = tp
                metrics.add_label_tp(class_name, metric_name, tp)
        print_traj_metrics(traj_metrics)

        # Compute evaluation time.
        metrics.add_runtime(time.time() - start_time)

        return metrics, metric_data_list

    def main(self,
             plot_examples: int = 0,
             render_curves: bool = True,
             eval_mode: str = 'standard') -> Dict[str, Any]:
        """
        Main function that loads the evaluation code, visualizes samples, runs the evaluation and renders stat plots.
        :param plot_examples: How many example visualizations to write to disk.
        :param render_curves: Whether to render PR and TP curves to disk.
        :return: A dict that stores the high-level metrics and meta data.
        """
        if plot_examples > 0:
            # Select a random but fixed subset to plot.
            random.seed(42)
            sample_tokens = list(self.sample_tokens)
            random.shuffle(sample_tokens)
            sample_tokens = sample_tokens[:plot_examples]

            # Visualize samples.
            example_dir = os.path.join(self.output_dir, 'examples')
            if not os.path.isdir(example_dir):
                os.mkdir(example_dir)
            for sample_token in sample_tokens:
                visualize_sample(self.nusc,
                                 sample_token,
                                 self.gt_boxes if self.eval_set != 'test' else EvalBoxes(),
                                 # Don't render test GT.
                                 self.pred_boxes,
                                 eval_range=max(self.cfg.class_range.values()),
                                 savepath=os.path.join(example_dir, '{}.png'.format(sample_token)))

        # Run evaluation.
        if eval_mode == 'motion_map':
            metrics, metric_data_list = self.evaluate_motion()
        elif eval_mode == 'standard':
            metrics, metric_data_list = self.evaluate()
        elif eval_mode == 'epa':
            metrics, metric_data_list = self.evaluate_epa()
        else:
            raise NotImplementedError
        # Render PR and TP curves.
        if render_curves:
            self.render(metrics, metric_data_list)

        # Dump the metric data, meta and metrics to disk.
        if self.verbose:
            print('Saving metrics to: %s' % self.output_dir)
        metrics_summary = metrics.serialize()
        metrics_summary['meta'] = self.meta.copy()
        with open(os.path.join(self.output_dir, 'metrics_summary.json'), 'w') as f:
            json.dump(metrics_summary, f, indent=2)
        with open(os.path.join(self.output_dir, 'metrics_details.json'), 'w') as f:
            json.dump(metric_data_list.serialize(), f, indent=2)

        # Print high-level metrics.
        print('mAP: %.4f' % (metrics_summary['mean_ap']))
        err_name_mapping = {
            'trans_err': 'mATE',
            'scale_err': 'mASE',
            'orient_err': 'mAOE',
            'vel_err': 'mAVE',
            'attr_err': 'mAAE'
        }
        for tp_name, tp_val in metrics_summary['tp_errors'].items():
            print('%s: %.4f' % (err_name_mapping[tp_name], tp_val))
        print('NDS: %.4f' % (metrics_summary['nd_score']))
        print('Eval time: %.1fs' % metrics_summary['eval_time'])

        # Print per-class metrics.
        print()
        print('Per-class results:')
        print('Object Class\tAP\tATE\tASE\tAOE\tAVE\tAAE')
        class_aps = metrics_summary['mean_dist_aps']
        class_tps = metrics_summary['label_tp_errors']
        for class_name in class_aps.keys():
            print('%s\t%.3f\t%.3f\t%.3f\t%.3f\t%.3f\t%.3f'
                  % (class_name, class_aps[class_name],
                     class_tps[class_name]['trans_err'],
                     class_tps[class_name]['scale_err'],
                     class_tps[class_name]['orient_err'],
                     class_tps[class_name]['vel_err'],
                     class_tps[class_name]['attr_err']))

        return metrics_summary

    def render(self, metrics: DetectionMetrics,
               md_list: DetectionMetricDataList) -> None:
        """
        Renders various PR and TP curves.
        :param metrics: DetectionMetrics instance.
        :param md_list: DetectionMetricDataList instance.
        """
        if self.verbose:
            print('Rendering PR and TP curves')

        def savepath(name):
            return os.path.join(self.plot_dir, name + '.pdf')

        summary_plot(
            md_list,
            metrics,
            min_precision=self.cfg.min_precision,
            min_recall=self.cfg.min_recall,
            dist_th_tp=self.cfg.dist_th_tp,
            savepath=savepath('summary'))

        for detection_name in self.cfg.class_names:
            class_pr_curve(
                md_list,
                metrics,
                detection_name,
                self.cfg.min_precision,
                self.cfg.min_recall,
                savepath=savepath(
                    detection_name +
                    '_pr'))

            class_tp_curve(
                md_list,
                metrics,
                detection_name,
                self.cfg.min_recall,
                self.cfg.dist_th_tp,
                savepath=savepath(
                    detection_name +
                    '_tp'))

        for dist_th in self.cfg.dist_ths:
            dist_pr_curve(
                md_list,
                metrics,
                dist_th,
                self.cfg.min_precision,
                self.cfg.min_recall,
                savepath=savepath(
                    'dist_pr_' +
                    str(dist_th)))

def print_traj_metrics(metrics):
    class_names = metrics.keys()
    x = PrettyTable()
    x.field_names = ["class names"] + TP_TRAJ_METRICS
    for class_name in metrics.keys():
        row_data = [class_name]
        for m in TP_TRAJ_METRICS:
            row_data.append('%.4f' % metrics[class_name][m])
        x.add_row(row_data)
    print(x)

if __name__ == "__main__":

    # Settings.
    parser = argparse.ArgumentParser(
        description='Evaluate nuScenes detection results.',
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        'result_path',
        type=str,
        help='The submission as a JSON file.')
    parser.add_argument(
        '--output_dir',
        type=str,
        default='~/nuscenes-metrics',
        help='Folder to store result metrics, graphs and example visualizations.')
    parser.add_argument(
        '--eval_set',
        type=str,
        default='val',
        help='Which dataset split to evaluate on, train, val or test.')
    parser.add_argument('--dataroot', type=str, default='data/nuscenes',
                        help='Default nuScenes data directory.')
    parser.add_argument(
        '--version',
        type=str,
        default='v1.0-trainval',
        help='Which version of the nuScenes dataset to evaluate on, e.g. v1.0-trainval.')
    parser.add_argument(
        '--config_path',
        type=str,
        default='',
        help='Path to the configuration file.'
        'If no path given, the CVPR 2019 configuration will be used.')
    parser.add_argument(
        '--plot_examples',
        type=int,
        default=0,
        help='How many example visualizations to write to disk.')
    parser.add_argument('--render_curves', type=int, default=1,
                        help='Whether to render PR and TP curves to disk.')
    parser.add_argument('--verbose', type=int, default=1,
                        help='Whether to print to stdout.')
    args = parser.parse_args()

    result_path_ = os.path.expanduser(args.result_path)
    output_dir_ = os.path.expanduser(args.output_dir)
    eval_set_ = args.eval_set
    dataroot_ = args.dataroot
    version_ = args.version
    config_path = args.config_path
    plot_examples_ = args.plot_examples
    render_curves_ = bool(args.render_curves)
    verbose_ = bool(args.verbose)

    if config_path == '':
        cfg_ = config_factory('detection_cvpr_2019')
    else:
        with open(config_path, 'r') as _f:
            cfg_ = DetectionConfig.deserialize(json.load(_f))

    nusc_ = NuScenes(version=version_, verbose=verbose_, dataroot=dataroot_)
    nusc_eval = MotionEval(
        nusc_,
        config=cfg_,
        result_path=result_path_,
        eval_set=eval_set_,
        output_dir=output_dir_,
        verbose=verbose_)
    for vis in ['1', '2', '3', '4']:
        nusc_eval.update_gt(type_='vis', visibility=vis)
        print(f'================ {vis} ===============')
        nusc_eval.main(
            plot_examples=plot_examples_,
            render_curves=render_curves_)


"""TP-Link Deco."""
import logging

from homeassistant.components.sensor import SensorEntity
from homeassistant.components.sensor.const import SensorDeviceClass
from homeassistant.components.sensor.const import SensorStateClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfDataRate
from homeassistant.core import callback
from homeassistant.core import HomeAssistant
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import (
    COORDINATOR_CLIENTS_KEY,
)
from .const import COORDINATOR_DECOS_KEY
from .const import DOMAIN
from .const import SIGNAL_DECO_ADDED
from .coordinator import TpLinkDeco
from .coordinator import TplinkDecoClientUpdateCoordinator
from .coordinator import TplinkDecoUpdateCoordinator
from .device import create_device_info

_LOGGER: logging.Logger = logging.getLogger(__name__)

async def async_setup_entry(
    hass: HomeAssistant, entry: ConfigEntry, async_add_entities
):
    """Setup sensor platform."""
    data = hass.data[DOMAIN][entry.entry_id]
    coordinator_decos = data[COORDINATOR_DECOS_KEY]
    coordinator_clients = data[COORDINATOR_CLIENTS_KEY]

    def add_sensors_for_deco(
        deco: TpLinkDeco | None,
    ):
        name_prefix = "Total" if deco is None else deco.name
        unique_id_prefix = (
            f"{coordinator_decos.data.master_deco.mac}_total"
            if deco is None
            else deco.mac
        )
        async_add_entities(
            [
                TplinkTotalClientDataRateSensor(
                    coordinator_decos,
                    coordinator_clients,
                    f"{name_prefix} Down",
                    f"{unique_id_prefix}_down",
                    "down_kilobytes_per_s",
                    deco,
                ),
                TplinkTotalClientDataRateSensor(
                    coordinator_decos,
                    coordinator_clients,
                    f"{name_prefix} Up",
                    f"{unique_id_prefix}_up",
                    "up_kilobytes_per_s",
                    deco,
                ),
            ]
        )

    tracked_decos = set()

    @callback
    def add_untracked_deco_sensors():
        """Add new tracker entities for clients."""
        new_entities = []

        for mac, deco in coordinator_decos.data.decos.items():
            if mac in tracked_decos:
                continue

            _LOGGER.debug(
                "add_untracked_deco_sensors: Adding deco sensors for mac=%s", deco.mac
            )
            add_sensors_for_deco(deco)
            tracked_decos.add(mac)

        if new_entities:
            async_add_entities(new_entities)

    add_sensors_for_deco(None)  # Total
    add_untracked_deco_sensors()

    coordinator_decos.on_close(
        async_dispatcher_connect(hass, SIGNAL_DECO_ADDED, add_untracked_deco_sensors)
    )

class TplinkTotalClientDataRateSensor(CoordinatorEntity, SensorEntity):
    """TP Link Total Client Data Rate Sensor Entity."""

    def __init__(
        self,
        coordinator_decos: TplinkDecoUpdateCoordinator,
        coordinator_clients: TplinkDecoClientUpdateCoordinator,
        name: str,
        unique_id: str,
        client_attribute: str,
        deco: TpLinkDeco | None,
    ) -> None:
        self._coordinator_decos = coordinator_decos
        self._client_attribute = client_attribute
        self._deco = deco

        self._attr_device_class = SensorDeviceClass.DATA_RATE
        self._attr_name = name
        self._attr_native_unit_of_measurement = UnitOfDataRate.KILOBYTES_PER_SECOND
        self._attr_state_class = SensorStateClass.MEASUREMENT
        self._attr_unique_id = unique_id
        super().__init__(coordinator_clients)
        self._update_state()  # Must happen after init

    @property
    def device_info(self) -> DeviceInfo:
        """Return device info."""
        master_deco = self._coordinator_decos.data.master_deco
        return create_device_info(self._deco or master_deco, master_deco)

    @callback
    async def async_on_demand_update(self):
        """Request update from coordinator."""
        await self.coordinator.async_request_refresh()

    @callback
    def _handle_coordinator_update(self) -> None:
        """Handle updated data from the coordinator."""
        self._update_state()
        self.async_write_ha_state()

    def _update_state(self) -> None:
        """Handle updated data from the coordinator."""
        state = 0.0
        for client in self.coordinator.data.values():
            if self._deco is None or client.deco_mac == self._deco.mac:
                state += getattr(client, self._client_attribute)
        self._attr_native_value = state


from django.urls import path

from .views.categories import CategoryCreateAndListView, CategoryDetailView, FilterCategoryView
from .views.products import FilterProductView, ProductCreateAndListView, ProductDetailView

urlpatterns = [
    path('products', ProductCreateAndListView.as_view(), name='product_list'),
    path('products/filter', FilterProductView.as_view()),
    path('products/<int:pk>', ProductDetailView.as_view()),
    path('categories', CategoryCreateAndListView.as_view(), name='category_list'),
    path('categories/filter', FilterCategoryView.as_view()),
    path('categories/<int:pk>', CategoryDetailView.as_view()),
]


import json
import asyncio

from aiogram import types
from aiogram.dispatcher.storage import FSMContext
from aiogram.types import ReplyKeyboardMarkup
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.types.callback_query import CallbackQuery
from aiogram.utils.callback_data import CallbackData

from keyboards.inline.profile import profile_menu_callback
from keyboards.inline import profile_my_channels_kb, my_ads_posts_kb
from keyboards.inline.my_ads_posts_kb import ad_post_callback
from states.add_ad_post_state import AD_POST
from loader import dp,pgbotdb

admin_post_stats_callback = CallbackData('admin_kb', "act", 'user_id', 'page')
post_statistics_callback = CallbackData('post_stats', "act", 'user_id', 'page', 'reverse')

@dp.callback_query_handler(profile_menu_callback.filter())
async def statistics_ad(call: CallbackQuery):
    # pass
    # posts = pgbotdb.get_posts()
    text, kb = await POST_STATS().post_statistics_kb(call.from_user.id,0)
    if text== "no data": await call.answer("У вас еще нет Статистики рекламы")
    else: await call.message.edit_text(text, reply_markup=kb, disable_web_page_preview=True)

@dp.callback_query_handler(post_statistics_callback.filter())
async def choose_category(call: CallbackQuery, callback_data: dict):
    await POST_STATS().process_selection(call, callback_data)

@dp.callback_query_handler(text='my_channels')
async def my_channels(call: CallbackQuery):
    await call.message.edit_text(text='🦋Мои канали🦋', reply_markup=profile_my_channels_kb)

@dp.callback_query_handler(text='my_ads_posts')
async def my_ads_posts(call: CallbackQuery):
    """Назад до Мои рекл. пости"""
    ad_posts = pgbotdb.select_ads_post_for_admin(call.from_user.id,imgs='ALL')
    await call.message.edit_text("Ваши пости для рекламы/Добавить заготовку", reply_markup=my_ads_posts_kb(ad_posts))

@dp.callback_query_handler(text='my_ads_posts',state=[AD_POST.name])
async def my_ads_posts(call: CallbackQuery,state: FSMContext):
    """Назад до Мои рекл. пости FSM"""
    await state.finish()
    ad_posts = pgbotdb.select_ads_post_for_admin(call.from_user.id, imgs='ALL')
    await call.message.edit_text("Ваши пости для рекламы/Добавить заготовку", reply_markup=my_ads_posts_kb(ad_posts))

@dp.callback_query_handler(text='add_new_ad_post')
async def my_ads_posts(call: CallbackQuery):
    """Добавить новый пост в базу"""
    kb = InlineKeyboardMarkup(inline_keyboard=[
    [InlineKeyboardButton('Назад',callback_data='back_to_my_profile')]])
    await call.message.edit_text(text="Введите имя для этого поста, в будущем с помощью него можно будеть найты этот пост среди других", reply_markup=kb)
    await AD_POST.name.set()

@dp.callback_query_handler(text='del_new_ad_post')
async def my_ads_posts(call: CallbackQuery):
    """kb при удалении пост из бази"""
    ad_posts = pgbotdb.select_ads_post_for_admin(admin_id=call.from_user.id, imgs='ALL')
    kb = InlineKeyboardMarkup(row_width=1)
    kb_posts = [InlineKeyboardButton(text=adpost[6], callback_data='del_ad_post:'+str(adpost[0])) for adpost in ad_posts]
    kb.add(*kb_posts)
    kb.add(InlineKeyboardButton('Назад',callback_data='back_to_my_profile'))
    await call.message.edit_text(text="Выберите заготовку для удаления", reply_markup=kb)

@dp.callback_query_handler(text_contains='del_ad_post')
async def my_ads_posts(call: CallbackQuery):
    """Удалить пост из бази"""

    id_post = call.data.replace('del_ad_post:', '')
    pgbotdb.delete_ads_post_for_admin_by_id(post_id=id_post)
    await call.message.answer('Успешно удалено ✅')

@dp.callback_query_handler(ad_post_callback.filter())
async def ad_post_callback(call: CallbackQuery, callback_data: dict):
    """Отправка поста юзеру"""
    post_info = pgbotdb.select_ad_post_from_db_where_increment(callback_data['increment'])
    print('here')
    await send_post(call.from_user.id,post_info[2],post_info[5],post_info[3])
    await asyncio.sleep(1)
    kb = InlineKeyboardMarkup(row_width=1,
        inline_keyboard=[
            [InlineKeyboardButton(text="Назад", callback_data='my_ads_posts')]
            ])
    await call.message.answer(
        text='Назад к постам',
        reply_markup=kb)

@dp.callback_query_handler(text='change_role')
async def my_channels(call: CallbackQuery):
    role = 'Рекламодатель' if pgbotdb.user_is_admin(call.from_user.id) else 'Админ'
    if pgbotdb.user_is_admin(call.from_user.id):
        pgbotdb.upadte_user_role(call.from_user.id,role='advertiser')
    else: pgbotdb.upadte_user_role(call.from_user.id,role='admin')
    print('new_role', role)
    await call.answer(f'Вы сменили свою роль на {role}')

@dp.callback_query_handler(text='zajavka_in_progress')
async def posts_in_progres(call: CallbackQuery):
    print("progess")
    text, kb = await ADMIN_ADS().post_kb(call.from_user.id,0)
    if text== "no data": await call.answer("У вас еще нет Заявок на рекламу в ваших каналу (ах)")
    else: await call.message.edit_text(text, reply_markup=kb, disable_web_page_preview=True)

@dp.callback_query_handler(admin_post_stats_callback.filter())
async def choose_category(call: CallbackQuery, callback_data: dict):
    await ADMIN_ADS().kb_logic_change(call, callback_data)

async def send_post(channel_id,text,kb,file_id)->str:
    """Отправка поста"""
    msg = ''
    if file_id == '0':
        if kb=='-':
            msg = await dp.bot.send_message(chat_id=channel_id, text=text)
        else:
            msg = await dp.bot.send_message(chat_id=channel_id, text=text,reply_markup=post_kb(json.loads(kb)))
        return msg
    else:
        if kb=='-':
            msg = await dp.bot.send_photo(channel_id, file_id, text)
        else:
            msg = await dp.bot.send_photo(channel_id, file_id, text,reply_markup=post_kb(json.loads(kb)))
        return msg

def post_kb(kb):
    """Работа с клавиатурой"""
    keb = InlineKeyboardMarkup(row_width=1)
    print(kb)
    if '=' in kb[0]:
        btns = (InlineKeyboardButton(text=k.split('=')[0].strip(' '), url=k.split('=')[1].strip(' ')) for k in kb)
    else:
        btns = (InlineKeyboardButton(text=text, url=url) for text,url in kb)
    keb.add(*btns)
    print(keb)
    return keb

class ADMIN_ADS:

    async def post_kb(
        self,
        user_id,
        page:int=0,
        reverse: str = "False",
    ) -> ReplyKeyboardMarkup:
        up = '▲'
        down = '▼'
        stat_icon = down if reverse=='False' else up
        kb=InlineKeyboardMarkup(row_width=1)
        btns = [
            InlineKeyboardButton(text='По дате'+ stat_icon, callback_data=admin_post_stats_callback.new("SORT",user_id, 1)),
            ]
        kb.row(*btns)
        page, info_text = get_post_info_admin(user_id,page)
        if page=='no data':
            return page, ''
        # info_text = "OST_INFO"

        # берем по статистике ERR канали, отбираем первый 10 x штук и добавляем в клаву

        if page != 0:
            kb.row(InlineKeyboardButton(text='<<<', callback_data=admin_post_stats_callback.new("GO_BACK",user_id, page)),
        InlineKeyboardButton(text='^  ^', callback_data=admin_post_stats_callback.new('OPEN_UP', user_id, page)),
        InlineKeyboardButton(text='>>>', callback_data=admin_post_stats_callback.new("GO_FORTH",user_id, page)),)
        elif page == 0: kb.row(InlineKeyboardButton(text='Назад', callback_data='back_to_my_profile'),
        InlineKeyboardButton(text='^  ^', callback_data=admin_post_stats_callback.new('OPEN_UP', user_id, page)),
        InlineKeyboardButton(text='>>>', callback_data=admin_post_stats_callback.new("GO_FORTH",user_id, page)),)
        return info_text, kb

    async def kb_logic_change(self, call: CallbackQuery, data: CallbackData) -> tuple:
        """
        Process the callback_query. This method generates new kb for channels
        """
        print('IN PORCESSING')

        # user picked a day button, return date
        if data['act'] == "GO_FORTH":

            page = data["page"]
            user_id = data["user_id"]

            text, kb = await self.post_kb(user_id,int(page)+1)
            await call.message.edit_text(text, reply_markup=kb, disable_web_page_preview=True)

        if data['act'] == "GO_BACK":

            page = data["page"]
            user_id = data["user_id"]

            text, kb = await self.post_kb(call.from_user.id,int(page)-1)
            await call.message.edit_text(text, reply_markup=kb, disable_web_page_preview=True)

        if data['act'] == "SORT":
            await call.answer('Будет скоро...')

        if data['act'] == 'IGNORE':
            await call.answer(cache_time=60)

class POST_STATS:

    async def post_statistics_kb(
        self,
        user_id,
        page:int=0,
        reverse: str = "False",
    ):
        up = '▲'
        down = '▼'
        stat_icon = down if reverse=='False' else up
        kb=InlineKeyboardMarkup(row_width=1)
        btns = [
            InlineKeyboardButton(text='По дате'+ stat_icon, callback_data=post_statistics_callback.new("SORT",user_id, 1, reverse)),
            ]
        kb.row(*btns)
        page, info_text = get_post_info(user_id,page,reverse)
        if page=='no data':
            return page, ''
        # info_text = "OST_INFO"

        # берем по статистике ERR канали, отбираем первый 10 x штук и добавляем в клаву

        if page != 0:
            kb.row(InlineKeyboardButton(text='<<<', callback_data=post_statistics_callback.new("GO_BACK",user_id, page,reverse)),
        InlineKeyboardButton(text='^  ^', callback_data=post_statistics_callback.new('OPEN_UP', user_id, page,reverse)),
        InlineKeyboardButton(text='>>>', callback_data=post_statistics_callback.new("GO_FORTH",user_id, page,reverse)),)
        elif page == 0: kb.row(InlineKeyboardButton(text='Назад', callback_data='back_to_my_profile'),
        InlineKeyboardButton(text='^  ^', callback_data=post_statistics_callback.new('OPEN_UP', user_id, page,reverse)),
        InlineKeyboardButton(text='>>>', callback_data=post_statistics_callback.new("GO_FORTH",user_id, page,reverse)),)
        return info_text, kb

    async def process_selection(self, call: CallbackQuery, data: CallbackData) -> tuple:
        """
        Process the callback_query. This method generates new kb for channels
        """
        print('IN PORCESSING')

        # user picked a day button, return date
        if data['act'] == "GO_FORTH":
            rev = data['reverse']
            page = data["page"]
            user_id = data["user_id"]

            text, kb = await self.post_statistics_kb(user_id,int(page)+1, rev)
            await call.message.edit_text(text, reply_markup=kb, disable_web_page_preview=True)

            # if rev=='True':
            #     sorted_list.reverse()
            # await call.message.edit_reply_markup(await self.post_statistics_kb(
            # channel_statistics=sorted_list,
            # sort_by = data['sort_by'],
            # page = int(data['page'])+1,
            # reverse=rev
            # ))

        if data['act'] == "GO_BACK":
            rev = data['reverse']
            page = data["page"]
            user_id = data["user_id"]

            text, kb = await POST_STATS().post_statistics_kb(call.from_user.id,int(page)-1, rev)
            await call.message.edit_text(text, reply_markup=kb, disable_web_page_preview=True)

        """TO DO""" # сортировка по дате
        if data['act'] == "SORT":
            await call.answer('Будет скоро...')

        if data['act'] == 'IGNORE':
            await call.answer(cache_time=60)

from handlers.payments.btc_banker import is_number
def get_post_info(user_id,page,reverse):
    page, post_info = pgbotdb.posts_ads_statistics(user_id,page)
    if page=='no data':
        return page, ''
    print(post_info)
    status = post_info[10]
    msg_id = post_info[11]
    chan_id = post_info[1]
    print(page, 'pageeeeeeeeeeeeeee')
    views_count='-'

    if status == 'False'or status == False:
        cpv = 'Вы не оплатили пост, у вас есть максимум 5 минут чтобы это сделать!'
    elif status == 'PAID':
        cpv = 'Дождытесь окончание поста'
    elif status == 'SUSPANDED':
        cpv = 'Бот постер был удален админом или канал заблокирован, пишите @ad_manager, решим проблему'
    elif status == 'UNKNOWN':
        cpv = 'Незвестрая ошибка, пишите админу, исправим!!! @adx_admin @adx_manager'
    elif status == 'SENT':
        cpv = 'Пост отправлен в канал, просмотри идут'
    elif status == 'DONE':
        views_count = pgbotdb.posts_stats_views(msg_id,chan_id)
        try:
            cpv = post_info[6]/views_count*1000 if is_number(views_count) else 'Дождытесь окончание поста'
        except ZeroDivisionError: cpv = "∞"
    chann_info = pgbotdb.select_channel(chan_id)
    text=f"""Реклама в канале {chann_info[3]}
Дата публикации: {post_info[7]}
Дата завершения: {post_info[8]}
по МСК
Количество просмотров: {views_count}
Цена: {post_info[6]}
CPV(Цена за 1к просм.): {cpv}
Статус: {status}
"""
    return page, text

def get_post_info_admin(user_id,page):
    admin_channels = pgbotdb.select_channels(user_id)
    channels= []
    for i in admin_channels:
        channels.append(i)
    page, post_info = pgbotdb.posts_admin_ads_statistics(channels,page)
    if page=='no data':
        return page, ''
    msg_id = post_info[-2]
    chan_id = post_info[1]

    views_count = "Пост в процессе публикации" if msg_id == None or msg_id == '-' else pgbotdb.posts_stats_views(msg_id,chan_id)
    chann_info = pgbotdb.select_channel(chan_id)

    cpv = post_info[6]/views_count*1000 if is_number(views_count) else 'Дождытесь окончание поста'
    text=f"""Реклама в вашем канале {chann_info[3]}
Дата публикации: {post_info[7]}
Дата завершения: {post_info[8]}
по МСК
Количество просмотров: {views_count}
Получено: {post_info[6]}
CPV(Цена за 1к просм.): {cpv}
"""
    return page, text


"""
This module contains the class TedTalkDatabase, which provides an interface to
an SQLite database containing information about Ted Talks. It is designed to be
used for the HTML web pages in tedtalks_html.py and
REST API implementation in tedtalks_api.py.
"""

import os
import sqlite3
import csv

def row_to_dict_or_none(cur):
    """
    Given a cursor that has just been used to execute a query, try to fetch one
    row. If the there is no row to fetch, return None, otherwise return a
    dictionary representation of the row.

    :param cur: a cursor that has just been used to execute a query
    :return: a dict representation of the next row, or None
    """
    row = cur.fetchone()

    if row is None:
        return None
    else:
        return dict(row)

class TedTalkDatabase:
    """
    This class provides methods for getting and inserting information about
    speeches, speakers, topics, and reviews into an SQLite database.
    """

    def __init__(self, sqlite_filename):
        """
        Creates a connection to the database, and creates tables if the
        database file did not exist prior to object creation.

        :param sqlite_filename: the name of the SQLite database file
        """
        if os.path.isfile(sqlite_filename):
            create_tables = False
        else:
            create_tables = True

        self.conn = sqlite3.connect(sqlite_filename)
        self.conn.row_factory = sqlite3.Row

        cur = self.conn.cursor()
        cur.execute('PRAGMA foreign_keys = 1')
        cur.execute('PRAGMA journal_mode = WAL')
        cur.execute('PRAGMA synchronous = NORMAL')

        if create_tables:
            self.create_tables()

    def create_tables(self):
        """
        Create the tables speaker, topic, speech, and review.
        """
        cur = self.conn.cursor()
        cur.execute('CREATE TABLE speaker(speaker_id INTEGER PRIMARY KEY, '
                    '    speaker TEXT UNIQUE)')
        cur.execute('CREATE TABLE topic(topic_id INTEGER PRIMARY KEY, '
                    '    topic TEXT UNIQUE)')
        cur.execute('CREATE TABLE speech(speech_id INTEGER PRIMARY KEY, '
                    '    title TEXT, duration INTEGER, '
                    '    views INTEGER, date DATE, '
                    '    topic_id INTEGER, speaker_id INTEGER, '
                    '    FOREIGN KEY (topic_id) REFERENCES topic(topic_id), '
                    '    FOREIGN KEY (speaker_id) '
                    '       REFERENCES speaker(speaker_id))')
        cur.execute('CREATE TABLE review(review_id INTEGER PRIMARY KEY, '
                    '    content TEXT, speech_id INTEGER, '
                    '    FOREIGN KEY (speech_id) REFERENCES speech(speech_id))')
        self.conn.commit()

    def insert_review(self, content, speech_id):
        """
        Inserts a review into the database. The review's speech_id
        must already exist in the database.

        Returns a dictionary representation of the review.

        :param content: content of the review
        :param speech_id: id of the speech that the review refers to
        :return: a dict representing the review
        """
        cur = self.conn.cursor()
        speech_dict = self.get_speech_by_id(speech_id)
        try:
            speech_id = speech_dict['speech_id']

            query = ('INSERT INTO review(content, speech_id) '
                     'VALUES(?, ?)')

            cur.execute(query, (content, speech_id))

            self.conn.commit()

            return self.get_review_by_id(cur.lastrowid)

        except TypeError:
            print("Speech with the given ID does not exist.")
            return 1

    def get_review_by_id(self, review_id):
        """
        Given a review's primary key, return a dictionary representation of the
        review, or None if there is no review with that primary key.

        :param review_id: the primary key of the review
        :return: a dict representing the review
        """
        cur = self.conn.cursor()

        query = ('SELECT review.review_id as review_id, '
                 'review.content as content, '
                 'speech.speech_id as speech_id, '
                 'speech.title as speech_title '
                 'FROM speech, review '
                 'WHERE review.speech_id = speech.speech_id '
                 'AND review.review_id = ?')

        cur.execute(query, (review_id,))
        return row_to_dict_or_none(cur)

    def get_all_reviews(self):
        """
        Return a list dictionaries representing all of the reviews in the
        database.

        :return: a list of dict objects representing reviews
        """

        cur = self.conn.cursor()

        query = ('SELECT review.review_id as review_id, '
                 'review.content as content, '
                 'speech.speech_id as speech_id, '
                 'speech.title as speech_title '
                 'FROM speech, review '
                 'WHERE review.speech_id = speech.speech_id')

        reviews = []
        cur.execute(query)

        for row in cur.fetchall():
            reviews.append(dict(row))

        return reviews

    def get_reviews_by_speech(self, speech_id):
        """
        Given a speech's primary key, return a list of
        all reviews for that speech.

        :param speech_id: the primary key of the speech
        :return: a list dict representing the reviews for the given speech
        """
        cur = self.conn.cursor()

        query = ('SELECT review.review_id as review_id, '
                 'review.content as content, '
                 'speech.title as speech_title, '
                 'speech.speech_id as speech_id '
                 'FROM review, speech '
                 'WHERE speech.speech_id = review.speech_id '
                 'AND speech.speech_id = ?')

        reviews = []
        cur.execute(query, (speech_id,))
        for row in cur.fetchall():
            reviews.append(dict(row))

        return reviews

    def delete_review(self, review_id):
        """
        Delete the review with the given primary key.

        :param review_id: primary key of the review
        """

        cur = self.conn.cursor()

        query = 'DELETE FROM review WHERE review_id = ?'
        cur.execute(query, (review_id,))

        self.conn.commit()

    def insert_speech(self, title, duration, views, date, topic, speaker):
        """
        Inserts a speech into the database. If the speech's topic/speaker
        is not already in the database, inserts topic/speaker too.

        Returns a dictionary representation of the speech.

        :param title: title of the speech
        :param duration: duration of the speech, in seconds
        :param views: number of views of the speech
        :param date: the date of the speech
        :param topic: the topic of the speaker
        :param speaker: name of the speaker
        :return: a dict representing the speech
        """
        cur = self.conn.cursor()
        self.insert_topic(topic)
        self.insert_speaker(speaker)
        topic_dict = self.get_topic_by_name(topic)
        topic_id = topic_dict['topic_id']
        speaker_dict = self.get_speaker_by_name(speaker)
        speaker_id = speaker_dict['speaker_id']

        query = ('INSERT INTO speech(title, duration, views, '
                 '                   date, topic_id, speaker_id) '
                 'VALUES(?, ?, ?, ?, ?, ?)')

        cur.execute(query, (title, duration, views,
                            date, topic_id, speaker_id))
        self.conn.commit()

        return self.get_speech_by_id(cur.lastrowid)

    def get_speech_by_id(self, speech_id):
        """
        Given a speech's primary key, return a dictionary representation of the
        speech, or None if there is no speech with that primary key.

        :param speech_id: the primary key of the speech
        :return: a dict representing the speech
        """
        cur = self.conn.cursor()

        query = ('SELECT speech.speech_id as speech_id, speech.title as title, '
                 'speech.duration as duration, speech.views as views, '
                 'speech.date as date, topic.topic as topic, '
                 'topic.topic_id as topic_id, '
                 'speaker.speaker as speaker, '
                 'speaker.speaker_id as speaker_id '
                 'FROM speech, topic, speaker '
                 'WHERE speech.topic_id = topic.topic_id '
                 'AND speech.speaker_id = speaker.speaker_id '
                 'AND speech.speech_id = ?')

        cur.execute(query, (speech_id,))
        return row_to_dict_or_none(cur)

    def get_all_speeches(self):
        """
        Return a list dictionaries representing all of the speeches in the
        database.

        :return: a list of dict objects representing speeches
        """

        cur = self.conn.cursor()

        query = ('SELECT speech.speech_id as speech_id, speech.title as title, '
                 'speech.duration as duration, speech.views as views, '
                 'speech.date as date, topic.topic as topic, '
                 'topic.topic_id as topic_id, '
                 'speaker.speaker as speaker, '
                 'speaker.speaker_id as speaker_id '
                 'FROM speech, topic, speaker '
                 'WHERE speech.topic_id = topic.topic_id '
                 'AND speech.speaker_id = speaker.speaker_id')

        speeches = []
        cur.execute(query)

        for row in cur.fetchall():
            speeches.append(dict(row))

        return speeches

    def get_speeches_by_speaker(self, speaker_id):
        """
        Given a speaker's primary key, return a list of
        all speeches by that speaker.

        :param speaker_id: the primary key of the speaker
        :return: a list dict representing the speeches by that speaker
        """
        cur = self.conn.cursor()

        query = ('SELECT speech.speech_id as speech_id, speech.title as title, '
                 'speech.duration as duration, speech.views as views, '
                 'speech.date as date, topic.topic as topic, '
                 'topic.topic_id as topic_id, '
                 'speaker.speaker as speaker, '
                 'speaker.speaker_id as speaker_id '
                 'FROM speech, topic, speaker '
                 'WHERE speech.topic_id = topic.topic_id '
                 'AND speech.speaker_id = speaker.speaker_id '
                 'AND speaker.speaker_id = ?')

        speeches = []
        cur.execute(query, (speaker_id,))
        for row in cur.fetchall():
            speeches.append(dict(row))

        return speeches

    def get_speeches_by_topic(self, topic_id):
        """
        Given a topic's primary key, return a list of
        all speeches with that topic.

        :param topic_id: the primary key of the topic
        :return: a list dict representing the speeches with the given topic
        """
        cur = self.conn.cursor()

        query = ('SELECT speech.speech_id as speech_id, speech.title as title, '
                 'speech.duration as duration, speech.views as views, '
                 'speech.date as date, topic.topic as topic, '
                 'topic.topic_id as topic_id, '
                 'speaker.speaker as speaker, '
                 'speaker.speaker_id as speaker_id '
                 'FROM speech, topic, speaker '
                 'WHERE speech.topic_id = topic.topic_id '
                 'AND speech.speaker_id = speaker.speaker_id '
                 'AND topic.topic_id = ?')

        speeches = []
        cur.execute(query, (topic_id,))
        for row in cur.fetchall():
            speeches.append(dict(row))

        return speeches

    def delete_speech(self, speech_id):
        """
        Delete the speech with the given primary key.

        :param speech_id: primary key of the speech
        """

        cur = self.conn.cursor()

        query1 = 'DELETE FROM review WHERE speech_id = ?'
        cur.execute(query1, (speech_id,))
        query2 = 'DELETE FROM speech WHERE speech_id = ?'
        cur.execute(query2, (speech_id,))

        self.conn.commit()

    def insert_topic(self, topic):
        """
        Insert a topic into the database if it does not exist. Do nothing if
        there is already a topic with the given name in the database.

        Return a dict representation of the topic.

        :param topic: the topic of the speech
        :return: dict representing the topic
        """
        cur = self.conn.cursor()
        query = 'INSERT OR IGNORE INTO topic(topic) VALUES(?)'
        cur.execute(query, (topic,))
        self.conn.commit()
        return self.get_topic_by_name(topic)

    def get_all_topics(self):
        """
        Get a list of dictionary representations of all the topics in the
        database.

        :return: list of dicts representing all topics
        """
        cur = self.conn.cursor()

        query = 'SELECT * FROM topic'

        topics = []
        cur.execute(query)

        for row in cur.fetchall():
            topics.append(dict(row))

        return topics

    def get_topic_by_id(self, topic_id):
        """
        Get a dictionary representation of the topic with the given primary
        key. Return None if the topic does not exist.

        :param topic_id: primary key of the topic
        :return: a dict representing the topic, or None
        """
        cur = self.conn.cursor()
        query = 'SELECT topic_id, topic FROM topic WHERE topic_id = ?'
        cur.execute(query, (topic_id,))
        return row_to_dict_or_none(cur)

    def get_topic_by_name(self, topic):
        """
        Get a dictionary representation of the topic with the given name.
        Return None if there is no such topic.

        :param topic: the name of the topic
        :return: a dict representing the topic, or None
        """
        cur = self.conn.cursor()
        query = 'SELECT topic_id, topic FROM topic WHERE topic = ?'
        cur.execute(query, (topic,))
        return row_to_dict_or_none(cur)

    def delete_topic(self, topic_id):
        """
        Delete the topic with the given id, and
        delete relevant speeches and reviews for that topic.

        :param topic_id: primary key of the topic
        """

        cur = self.conn.cursor()

        speeches = []
        speeches = self.get_speeches_by_topic(topic_id)
        for speech in speeches:
            speech_id = speech['speech_id']
            query = 'DELETE FROM review WHERE speech_id = ?'
            cur.execute(query, (speech_id,))

        query1 = 'DELETE FROM speech WHERE topic_id = ?'
        cur.execute(query1, (topic_id,))
        query2 = 'DELETE FROM topic WHERE topic_id = ?'
        cur.execute(query2, (topic_id,))

        self.conn.commit()

    def insert_speaker(self, speaker):
        """
        Insert a speaker into the database if he/she/they does not exist.
        Do nothing if there is already a speaker
        with the given name in the database.

        Return a dict representation of the speaker.

        :param speaker: name of the speaker
        :return: dict representing the speaker
        """
        cur = self.conn.cursor()
        query = 'INSERT OR IGNORE INTO speaker(speaker) VALUES(?)'
        cur.execute(query, (speaker,))
        self.conn.commit()
        return self.get_speaker_by_name(speaker)

    def get_all_speakers(self):
        """
        Get a list of dictionary representations of all the speakers in the
        database.

        :return: list of dicts representing all speakers
        """
        cur = self.conn.cursor()

        query = 'SELECT * FROM speaker'

        speakers = []
        cur.execute(query)

        for row in cur.fetchall():
            speakers.append(dict(row))

        return speakers

    def get_speaker_by_id(self, speaker_id):
        """
        Get a dictionary representation of the speaker with the given primary
        key. Return None if the speaker does not exist.

        :param speaker_id: primary key of the speaker
        :return: a dict representing the speaker, or None
        """
        cur = self.conn.cursor()
        query = 'SELECT speaker_id, speaker FROM speaker WHERE speaker_id = ?'
        cur.execute(query, (speaker_id,))
        return row_to_dict_or_none(cur)

    def get_speaker_by_name(self, speaker):
        """
        Get a dictionary representation of the speaker with the given name.
        Return None if there is no such speaker.

        :param speaker: name of the speaker
        :return: a dict representing the speaker, or None
        """
        cur = self.conn.cursor()
        query = 'SELECT speaker_id, speaker FROM speaker WHERE speaker = ?'
        cur.execute(query, (speaker,))
        return row_to_dict_or_none(cur)

    def delete_speaker(self, speaker_id):
        """
        Delete the speaker and the speeches with the given speaker id.

        :param speaker_id: primary key of the speaker
        """

        cur = self.conn.cursor()

        speeches = []
        speeches = self.get_speeches_by_speaker(speaker_id)
        for speech in speeches:
            speech_id = speech['speech_id']
            query = 'DELETE FROM review WHERE speech_id = ?'
            cur.execute(query, (speech_id,))

        query1 = 'DELETE FROM speech WHERE speaker_id = ?'
        cur.execute(query1, (speaker_id,))
        query2 = 'DELETE FROM speaker WHERE speaker_id = ?'
        cur.execute(query2, (speaker_id,))

        self.conn.commit()

if __name__ == '__main__':

    db = TedTalkDatabase('tedtalks.sqlite')
    with open('sample.csv', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for row in reader:
            db.insert_speech(row['title'], row['duration'], row['views'],
                             row['date'], row['topic'], row['speaker'])


import pygame
class Button:
    def __init__(self,pos,image,action):
        self.action=action
        self.pos=pos
        self.image=image
        self.rect=self.image.get_rect()
        self.rect.center=pos
        self.clicked=False
    def draw(self,surface):
        surface.blit(self.image,self.rect)
        pos = pygame.mouse.get_pos()
        if self.rect.collidepoint(pos):
            if pygame.mouse.get_pressed()[0] == 1 and self.clicked==False:
                self.clicked=True
                return True
        if pygame.mouse.get_pressed()[0] == 0:
            self.clicked=False
        return False


#! /bin/env python
# encoding=utf-8
# gusimiu@baidu.com
#

import sys
import copy

import numpy
import sklearn
from sklearn import cross_validation

import pydev
import mlreader

if __name__=='__main__':
    arg = pydev.Arg('A simple tool for machine learning based ont scikit-learn')
    arg.str_opt('filename', 'f', 'input file of data')
    arg.str_opt('test', 't', 'data file for test')
    arg.str_opt('reader_config', 'r', 'config of reader, format: filename#section')
    arg.bool_opt('auc', 'a', 'output auc score of features in training data.')
    opt = arg.init_arg()

    train_reader = mlreader.DataReader()
    if opt.reader_config:
        train_reader.config(opt.reader_config)
    train_reader.read(opt.filename)

    train_X = train_reader.data
    train_Y = train_reader.label
    test_X = None
    test_Y = None

    test_reader = None
    if opt.test:
        # copy the config from train_reader.
        # make two reader the same config.
        test_reader = copy.copy( train_reader )
        test_reader.clear_data()

        test_reader.read(opt.test)
        test_X = test_reader.data
        test_Y = test_reader.label
    else:
        train_X, test_X, train_Y, test_Y = cross_validation.train_test_split(
                train_X, train_Y, test_size=0.3, random_state=0)

    if opt.auc:
        print >> sys.stderr, 'Calculate AUC score'
        train_reader.auc(train_X, train_Y, file('auc.txt', 'w'))

    from sklearn import svm
    from sklearn import linear_model
    from sklearn import neighbors
    from sklearn import naive_bayes
    from sklearn import tree
    from sklearn import ensemble
    from simple_nn import SimpleNetwork
    import nnet_tf

    models = [
        #('lr', linear_model.LogisticRegression() ),
        #('knn', neighbors.KNeighborsClassifier() ),
        #('gnb', naive_bayes.GaussianNB() ),
        #('tree', tree.DecisionTreeClassifier() ),
        #('simple_gbdt', ensemble.GradientBoostingClassifier(n_estimators=50, learning_rate=0.1, max_depth=5, random_state=0) ),
        #('best_gbdt', ensemble.GradientBoostingClassifier(n_estimators=500, learning_rate=0.05, max_depth=6, random_state=0) ),
        #('ada', ensemble.AdaBoostClassifier(n_estimators=100) ),
        #('rf', ensemble.RandomForestClassifier(n_estimators=100) ),
        #('svm', svm.SVC() ),
        #('simp_nn', SimpleNetwork(len(train_X[0]), 1, [256, 256, 128], output_01=True)),
        #('simp_nn', SimpleNetwork( len(train_X[0]), 1, [12, 12], output_01=True)),
        #('simp_nn_lr', SimpleNetwork(len(train_X[0]), [], output_01=True)),
        #('fc3_nnet', nnet_tf.ConfigNetwork('conf/net.conf', 'fc3_net', output_01=True)),
        #('mnist_fc', nnet_tf.ConfigNetwork('conf/net.conf', 'mnist_fc', output_01=True)),
        #('mnist_conv2d', nnet_tf.ConfigNetwork('conf/net.conf', 'mnist_conv2d', output_01=True)),
        ('mnist_conv2d_big', nnet_tf.ConfigNetwork('conf/net.conf', 'mnist_conv2d_big', output_01=True)),
        ]

    def report(pred, label, X, reader, error_writer, out_stream):

        if len(pred.shape) > len(label.shape):
            # try to flatten.
            print >> sys.stderr, 'Force flatten! [%s] => [%s]' % (pred.shape, label.shape)
            pred.shape = label.shape

        if isinstance(label.shape, int) or len(label.shape) == 1 or label.shape[1]==1:
            true_negative = len(filter(lambda x:x==0, pred + label))
            true_positive = len(filter(lambda x:x==2, pred + label))
            false_positive = len(filter(lambda x:x==1, pred - label))
            false_negative = len(filter(lambda x:x==-1, pred - label))

            for i in range(len(pred)):
                if pred[i] != label[i]:
                    if reader is None:
                        print >> error_writer, '%d\tP=%.1f\tT=%.1f\t%s' % (
                                i, pred[i], label[i], X[i])
                    else:
                        print >> error_writer, '%d\tP=%.1f\tT=%.1f\t%s\t%s' % (
                                i, pred[i], label[i], reader.info[i], reader.text_format(X[i]))

            diff_count = len(filter(lambda x:x!=0, pred - label))
            accuracy = (len(label) - diff_count) * 100. / len(label)

            print >> out_stream, 'Accuracy : ### %.2f%% (%d/%d) ###' % (accuracy, len(label)-diff_count, len(label))
            print >> out_stream, '     P: %.3f%%   R: %.3f%%' % (
                                    100. * true_positive / (true_positive + false_positive),
                                    100. * true_positive / (true_positive + false_negative)
                                    )

            print >> out_stream, ' Positive: true:%d false:%d' % (true_positive, false_positive)
            print >> out_stream, ' Negative: true:%d false:%d' % (true_negative, false_negative)

        else:
            same_count = len(filter(lambda x:(x[0]-x[1]).dot(x[0]-x[1])<1e-4, zip(pred, label)))
            accuracy = same_count * 100. / len(label)
            print >> out_stream, 'Accuracy : ### %.2f%% (%d/%d) ###' % (accuracy, same_count, len(label))

    for name, model in models:
        print >> sys.stderr, '====> model [%s] <====' % name

        model.fit(train_X, train_Y)
        print >> sys.stderr, 'Training over.'

        # TEST: training-set
        # test for best performance(ignore overfitting.).
        pred = model.predict(train_X)
        print >> sys.stderr, 'Predict [Training-set] over.'
        report(pred, train_Y, train_X, train_reader, file('error_of_train.txt', 'w'), sys.stderr)

        # TEST: test-set.
        pred = model.predict(test_X)
        print >> sys.stderr, 'Predict [Test-set] over.'
        report(pred, test_Y, test_X, test_reader, file('error_of_test.txt', 'w'), sys.stderr)


import requests
from requests_html import HTMLSession

import json
import time

def get_yahoo_finance_USD_SGD_rate():
    """ Get USDSGD exchange rate from yahoo finance by scapping the website """
    url = "https://sg.finance.yahoo.com/quote/USDSGD=X/"
    try:
        session = HTMLSession()
        response = session.get(url)
    except requests.exceptions.RequestException as e:
        print(e)
    USDSGD = float(
        response.html.xpath(
            "//fin-streamer[@data-field='regularMarketPrice'][@data-symbol='SGD=X']")[0].text)
    print(USDSGD)
    return (USDSGD)

def get_mas_USD_SGD_rate():
    """ Get USDSGD exchange rate from MAS API """
    api_link = "https://eservices.mas.gov.sg/api/action/datastore/search.json?resource_id=95932927-c8bc-4e7a-b484-68a66a24edfe&fields=usd_sgd,end_of_day&limit=1&sort=end_of_day%20desc"
    r = requests.get(api_link).json()
    while r["success"] != True:
        print("Failed to get USDSGD rate. Trying again...")
        r = requests.get(api_link).json()
    USDSGD = float(r["result"]["records"][0]["usd_sgd"])

    return USDSGD

def get_price(coin_id_param):
    """ Gets the current price of active coins from CoinGecko API """
    currencyParam = ','.join(['usd', 'sgd'])
    payload = {'ids': coin_id_param, 'vs_currencies': currencyParam}
    r = requests.get('https://api.coingecko.com/api/v3/simple/price',
                     params=payload)
    return r.json()

def get_price_all(coin_id_param):
    """ Gets the current price of all coins (including inactive ones) from CoinGecko API """
    currencyParam = ','.join(['usd', 'sgd'])
    payload = {'ids': coin_id_param, 'vs_currencies': currencyParam}
    r = requests.get('https://api.coingecko.com/api/v3/simple/price',
                     params=payload)
    return r.json()

def getHistoricalPrice(coinID, date, currency='usd'):
    #coingecko format: dd-mm-yyyy
    query_date = date[:2] + '-' + date[3:5] + '-' + date[6:]
    r = requests.get(
        'https://api.coingecko.com/api/v3/coins/{}/history?date={}&localization=false'
        .format(coinID, query_date))
    success = False
    price = 0
    if r.status_code == 404:
        return 0
    while not success:
        try:
            price = r.json()['market_data']['current_price'][currency]
            success = True
        except json.decoder.JSONDecodeError:
            print('Json decoder error! Trying again...')
            time.sleep(5)
            r = requests.get(
                'https://api.coingecko.com/api/v3/coins/{}/history?date={}&localization=false'
                .format(coinID, query_date))
    return price


from PySide6.QtWidgets import *
from PySide6.QtGui import (
    QScreen,
    QIcon,
    QFont,
    QRegularExpressionValidator,
)
from PySide6.QtCore import QSize, Qt, QRegularExpression
import libs.functons as functons
from validate_docbr import CPF

import sys

class Window(QWidget):
    def __init__(self):
        super().__init__()

        # configurações da janela
        # --------------------------------------- #
        self.setWindowTitle("SysFinan :: Dashboard")
        self.setWindowFlag(Qt.FramelessWindowHint)
        # self.setGeometry(50, 50, 1080, 680) # esquerda, topo, largura e altura
        # dimensiono a janela com o tamanho do desktop
        screensize = QScreen.availableGeometry(QApplication.primaryScreen())
        # print(screensize)
        self.setGeometry(screensize)

        self.setAutoFillBackground(True)
        self.setStyleSheet("background-color: #8b8378")

        appIcon = QIcon("icons/dollar-sign.png")
        self.setWindowIcon(appIcon)
        # --------------------------------------- #
        # conteudo da janela
        # --------------------------------------- #
        self.initUI()
        # --------------------------------------- #
        # exibe a janela
        self.show()

    def initUI(self):

        self.font8 = QFont("fonts/Roboto/Roboto-Bold", 8)
        self.font10 = QFont("fonts/Roboto/Roboto-Regular", 10, QFont.Bold)
        self.font10TXT = QFont("fonts/Roboto/Roboto-Light", 10)
        self.headerImg = QIcon("icons/brown/pie-chart.svg")
        self.iconHome = QIcon("icons/brown/home.svg")
        self.styleButton = """QPushButton {padding: 10px; border-radius: 5px; background-color: rgba(33, 43, 51, 100); color: white;}
                         QPushButton:hover {background-color: rgb(120, 157, 186);}
                      """

        self.styleLineEdit = """
                        QLineEdit {padding: 1px; border-style: solid; border: 2px solid #474747; border-radius: 8px;}
                        QLineEdit:hover, QLineEdit:focus {border: 2px solid #00aaaa; }
                       """

        # menu esquerdo
        barraMenu = QFrame(self)
        barraMenu.setGeometry(0, 0, 200, 736)

        # logo e texto do header
        headerMenu = QFrame(barraMenu)
        headerMenu.setGeometry(0, 0, 200, 50)
        headerMenu.setStyleSheet("color: white;background-color: #154650;")
        headerMenu.setFrameShape(QFrame.NoFrame)
        headerMenu.setFrameShadow(QFrame.Raised)
        # Imagem do header
        headerIcon = QLabel()
        headerIcon.setMinimumSize(QSize(32, 32))
        headerIcon.setMaximumSize(QSize(32, 32))
        headerIcon.setPixmap(
            functons.setImagem(self.headerImg, 32, QIcon.Active)
        )
        headerIcon.setScaledContents(True)
        # texto do header
        headerLabel = QLabel("DASHBOARD")
        headerLabel.setFont(self.font10)
        headerLabel.setMinimumSize(QSize(0, 30))
        headerLabel.setMaximumSize(QSize(16777215, 30))
        headerLabel.setAlignment(Qt.AlignCenter)

        # gerencia o layout do headerMenu
        headerHBox = QHBoxLayout(headerMenu)
        headerHBox.setSpacing(10)
        headerHBox.setContentsMargins(0, 9, 0, 9)
        headerHBox.addWidget(headerIcon)
        headerHBox.addWidget(headerLabel)

        # botoes do menu
        self.barraDePushbutton = QFrame(barraMenu)
        self.barraDePushbutton.setObjectName(u"buttons")
        self.barraDePushbutton.setFrameShape(QFrame.NoFrame)
        self.barraDePushbutton.setFrameShadow(QFrame.Raised)
        # self.barraDePushbutton.setStyleSheet("background-color: green")
        self.barraDePushbutton.setGeometry(0, 50, 200, 250)
        self.verticalLayout_1 = QVBoxLayout(self.barraDePushbutton)
        self.verticalLayout_1.setSpacing(0)
        self.verticalLayout_1.setContentsMargins(0, 0, 0, 0)

        # home button
        self.buttonHome = QPushButton(self.barraDePushbutton)
        self.buttonHome.setObjectName(u"btnHome")
        self.buttonHome.setText(u"Início")
        self.buttonHome.setObjectName(u"btnInicio")
        self.buttonHome.setIcon(self.iconHome)
        self.buttonHome.setFlat(True)
        self.buttonHome.setStyleSheet(self.styleButton)
        self.buttonHome.clicked.connect(self.homepage)

        # matricula alunos
        self.buttonMatricula = QPushButton(self.barraDePushbutton)
        self.buttonMatricula.setObjectName(u"btnMatricula")
        self.buttonMatricula.setIcon(QIcon("icons/brown/user-plus.svg"))
        self.buttonMatricula.setText("Matrícula")
        self.buttonMatricula.setFlat(True)
        self.buttonMatricula.setStyleSheet(self.styleButton)
        self.buttonMatricula.clicked.connect(self.matriculapage)
        # cadastro pais
        self.buttonResponsavel = QPushButton(self.barraDePushbutton)
        self.buttonResponsavel.setText("Responsável")
        self.buttonResponsavel.setObjectName(u"btnResponsavel")
        self.buttonResponsavel.setIcon(QIcon("icons/brown/watch.svg"))
        self.buttonResponsavel.setFlat(True)
        self.buttonResponsavel.setStyleSheet(self.styleButton)
        # relatórios
        self.buttonRelatorio = QPushButton(self.barraDePushbutton)
        self.buttonRelatorio.setText("Relatório")
        self.buttonRelatorio.setObjectName(u"btnRelatorio")
        self.buttonRelatorio.setIcon(QIcon("icons/brown/file-text.svg"))
        self.buttonRelatorio.setFlat(True)
        self.buttonRelatorio.setStyleSheet(self.styleButton)

        # sair
        self.buttonFechar = QPushButton(self.barraDePushbutton)
        self.buttonFechar.setText("Sair do APP")
        self.buttonFechar.setObjectName(u"btnFechar")
        self.buttonFechar.setIcon(QIcon("icons/brown/power.svg"))
        self.buttonFechar.setFlat(True)
        self.buttonFechar.setStyleSheet(self.styleButton)
        self.buttonFechar.clicked.connect(self.sairDoApp)

        # adiciona os buttons ao layout
        self.verticalLayout_1.addWidget(self.buttonHome)
        self.verticalLayout_1.addWidget(self.buttonMatricula)
        self.verticalLayout_1.addWidget(self.buttonResponsavel)
        self.verticalLayout_1.addWidget(self.buttonRelatorio)
        self.verticalLayout_1.addWidget(self.buttonFechar)

        """ FRAME HOME"""
        # conteudo direito
        self.frmHome = QFrame(self)
        self.frmHome.setGeometry(200, 0, 1166, 736)
        """ FIM HOME"""

        """ FRAME DE CRUD DE MATRICULA"""
        # conteudo direito
        self.frmMatricula = QFrame(self)
        self.frmMatricula.setGeometry(200, 0, 1166, 736)
        self.frmMatricula.setStyleSheet("background-color: #eadce0;")
        self.frmMatricula.setVisible(False)

        """ FIM CRUD DE ALUNOS"""

        global meus_frames
        self.meus_frames = (self.frmHome, self.frmMatricula)

        # inicializa a visão principal
        self.homepage()

    def ocultarFrames(self):
        global meus_frames
        for f in self.meus_frames:
            if f.isVisible() == True:
                f.setVisible(False)

    # cria o formulario inicial
    def homepage(self):
        self.ocultarFrames()
        frase = u"Área de notificações"

        self.observactions = QTextEdit(self.frmHome)
        self.observactions.setText(
            """
            21.03.2022 - Sempre ao realizar a matricular do Aluno, faça antes o cadastro do(s) Responsável(is) pelo mesmo.
            """
        )
        self.observactions.setGeometry(20, 50, 1166, 736)
        self.observactions.setStyleSheet(
            "background-color: #eae480; color: white"
        )
        self.observactions.setReadOnly(True)

        self.criarHeader(frase, self.frmHome)
        self.frmHome.setVisible(True)

    # cria o formulario de matricula
    def matriculapage(self):
        self.ocultarFrames()
        frase = u"Área de Cadastro/Edição/Pesquisa de Matrícula de Alunos"

        self.nomeValidator = QRegularExpressionValidator(
            QRegularExpression(r"^[A-Za-záàâãéèêíïóôõöúçñÁÀÂÃÉÈÍÏÓÔÕÖÚÇÑ ]+$")
        )
        self.emailValidator = QRegularExpressionValidator(
            QRegularExpression(r"^[a-z0-9]+[\._]?[a-z0-9]+[@]\w+[.]\w{2,3}$")
        )
        self.lblNome = QLabel(u"Nome", self.frmMatricula)
        self.lblNome.setGeometry(20, 70, 55, 16)
        self.txtNome = QLineEdit(self.frmMatricula)
        self.txtNome.setGeometry(80, 70, 721, 22)  # left top largura altura
        self.txtNome.setStyleSheet("background: none")
        self.txtNome.setValidator(self.nomeValidator)

        self.lblNick = QLabel(u"Nick", self.frmMatricula)
        self.lblNick.setGeometry(20, 110, 55, 16)
        self.txtNick = QLineEdit(self.frmMatricula)
        self.txtNick.setGeometry(80, 110, 200, 22)
        self.txtNick.setStyleSheet("background: none")
        self.lblEmail = QLabel(u"Email", self.frmMatricula)
        self.lblEmail.setGeometry(20, 150, 55, 16)
        self.txtEmail = QLineEdit(self.frmMatricula)
        self.txtEmail.setGeometry(80, 150, 721, 22)
        self.txtEmail.setStyleSheet("background: none")
        self.txtEmail.setValidator(self.emailValidator)
        self.txtEmail.textChanged.connect(self._adjustText)

        self.criarHeader(frase, self.frmMatricula)
        self.frmMatricula.setVisible(True)

    def _adjustText(self):
        if not self.txtEmail.hasAcceptableInput():
            self.txtEmail.setStyleSheet("color: red; background: none;")
        else:
            self.txtEmail.setStyleSheet("color: black; background: none;")

    def criarHeader(self, frase, form):
        self.frmHeader = QFrame(form)
        self.frmHeader.setGeometry(0, 0, 1366, 50)
        self.frmHeader.setStyleSheet("background-color: #154650; color: white")
        self.frmHeader.setFont(self.font10TXT)

        self.lblIconMessage = QLabel(self.frmHeader)
        self.lblIconMessage.setMinimumSize(QSize(32, 32))
        self.lblIconMessage.setMaximumSize(QSize(32, 32))
        self.lblIconMessage.setPixmap(
            functons.setImagem(
                QIcon("icons/brown/message-square.svg"), 32, QIcon.Active
            )
        )
        self.lblIconMessage.setScaledContents(True)
        self.lblIconMessage.setGeometry(20, 10, 32, 32)

        self.lblHeader = QLabel(frase, self.frmHeader)
        self.lblHeader.setGeometry(80, 10, 721, 22)

    def sairDoApp(self):
        self.close()

def executa():
    myApp = QApplication.instance()
    if myApp is None:
        myApp = QApplication(sys.argv)

    janela = Window()
    janela.show()

    myApp.exec()

# executa()


"""Module for options and configuration."""

from typing import List, Tuple

DEFAULT_FORMAT = '%Y%m%d'

DEFAULT_FORMAT_CANDIDATES = [
    ('%Y%m', 6),
    ('%Y%m%d', 8),
    ('%Y%m%d%H%M%S', 14),
    ('%Y%m%d %H%M%S', 15),
]

def get_date_format() -> str:
    """Return the date format according to configuration value.

    Returns:
        str: date format
    """
    return DEFAULT_FORMAT

def get_format_candidates() -> List[Tuple[str, int]]:
    """Return a sequence of date format candidates to try parsing the input value.

    Returns:
        List[Tuple[str, int]]: sequence of date format candidates, begin the first
            element of the tuple the format string, and the second the expected length
            of the input value.
    """
    return DEFAULT_FORMAT_CANDIDATES