# ----------------------------------------------------------------------
# |
# |  DismissStalePullRequestApprovalsPlugin.py
# |
# |  David Brownell <db@DavidBrownell.com>
# |      2023-11-10 14:49:10
# |
# ----------------------------------------------------------------------
# |
# |  Copyright David Brownell 2023
# |  Distributed under the Boost Software License, Version 1.0. See
# |  accompanying file LICENSE_1_0.txt or copy at
# |  http://www.boost.org/LICENSE_1_0.txt.
# |
# ----------------------------------------------------------------------
"""Contains the Plugin object"""

import textwrap

from typing import Any

from semantic_version import Version as SemVer

from GitHubConfigurationValidatorLib.Plugin import Plugin as PluginBase
from GitHubConfigurationValidatorLib.Impl.PluginImpl import CreateEnablePlugin

# ----------------------------------------------------------------------
def _GetValue(
    configuration: dict[str, Any],
) -> PluginBase.ValidateResultType:
    settings = configuration.get("required_pull_request_reviews", None)
    if settings is None:
        return None

    return settings["dismiss_stale_reviews"]

# ----------------------------------------------------------------------
Plugin = CreateEnablePlugin(
    "DismissStalePullRequestApprovals",
    PluginBase.ConfigurationType.BranchProtection,
    SemVer("0.1.0"),
    True,
    "--no-dismiss-stale-pull-request-approvals",
    "settings/branches",
    "Protect matching branches",
    "Dismiss stale pull request approvals when new commits are pushed",
    _GetValue,
    rationale=textwrap.dedent(
        """\
        The default behavior is to dismiss stale pull request approvals when new commits are pushed.

        Reasons for this Default
        ------------------------
        - Approvals apply to the changes as they existed when the approval was granted. The approval
          may no longer be valid when new changes are pushed.

        Reasons to Override this Default
        --------------------------------
        <unknown>
        """,
    ),
)


#import modules
import json
import pandas as pd
from dotenv import load_dotenv
from envs import env
import time
import re
import os
import datetime
from datetime import timedelta
import statistics
import numpy as np
from scipy.stats import kurtosis, skew

#import classes from ./ folder
import postgresdbAccess

#read fibonacci retracements  from json
with open('fibLvl.json') as file:
    fibLvl = json.load(file)

def writeTrade(openPositions, row, postgres):
    openPositions['resultPercent'] = (((row[4] -
        openPositions['askPrice']) / openPositions['askPrice']) *
        100 - brokerFees * 2.55)
    openPositions['stopId'] = row[0]
    closeSql = ("UPDATE " + dbTable + " SET" +
        " startId = '" + str(int(openPositions['startId'])) +
        "', fibLevel = '" + str(openPositions['fibLevel']) +
        "', midId = '" + str(int(openPositions['midId'])) +
        "', corValue = '" + str(openPositions['corValue']) +
        "', takeProfit = '" + str(openPositions['takeProfit']) +
        "', stopLoss = '" + str(openPositions['stopLoss']) +
        "', resultpercent = '" + str(openPositions['resultPercent']) +
        "', stopid = '" + str(openPositions['stopId']) +
        "', skew = '" + str(openPositions['skew']) +
        "', stdev = '" + str(openPositions['stDev']) +
        "', kurtosis = '" + str(openPositions['kurtosis']) +
        "' WHERE id = '" + str(int(openPositions['id'])) +
        "';")
    postgres.sqlUpdate(closeSql)

def backtest():
    #initiate empty open positions
    openPositions = {}
    #initiate postgresdb class
    postgres = postgresdbAccess.postgresAccess()
    #get unique simbol list
    sql = ("SELECT distinct symbol FROM " + dbTable + ";")
    uniqueSymbol = pd.DataFrame(postgres.sqlQuery(sql))
    uniqueSymbol = uniqueSymbol[0]
    #initiate empty percent bar
    percentBar = 0
    #loop over every symbol in symbol list
    for symbol in uniqueSymbol:
        #print progress
        print(percentBar, " / ", len(uniqueSymbol))
        percentBar = percentBar + 1
        #skip if symbol is not to be considered
        #null askprice is sorted out with crawler already
        if not (symbol.endswith(baseCurrency) and
               len(symbol) < 11):
            continue
        #check if open position was left over from last symbol and simulate close
        if openPositions:
            writeTrade(openPositions, row, postgres)
            openPositions = {}
        #query for every line for symbol
        sql = ("SELECT id, time, symbol, askprice, " +
               "bidprice, highprice, quotevolume, " +
               "pricechangepercent, weightedavgprice, openprice" +
            " FROM " + dbTable + " WHERE" +
            " symbol like '" + symbol + "' order by id;")
        print(sql)
        bigData = pd.DataFrame(postgres.sqlQuery(sql))
        bigData[0] = pd.to_numeric(bigData[0], errors='coerce', downcast='float')
        bigData[1] = pd.to_datetime(bigData[1])
        bigData[3] = pd.to_numeric(bigData[3], errors='coerce', downcast='float')
        bigData[4] = pd.to_numeric(bigData[4], errors='coerce', downcast='float')
        bigData[6] = pd.to_numeric(bigData[6], errors='coerce', downcast='float')
        bigData[7] = pd.to_numeric(bigData[7], errors='coerce', downcast='float')
        bigData[8] = pd.to_numeric(bigData[8], errors='coerce', downcast='float')
        bigData[9] = pd.to_numeric(bigData[9], errors='coerce', downcast='float')
        #get start of timedelta
        bigData[5] = bigData[1] - timedelta(hours=33)
        #loop over every row
        for index, row in bigData.iterrows():
            before_start_date = bigData[1] <= row[5]
            if len(bigData.loc[before_start_date]) > 0:
                #get data for consideration
                after_start_date = bigData[1] >= row[5]
                before_end_date = bigData[1] <= row[1]
                between_two_dates = after_start_date & before_end_date
                fibDates = bigData.loc[between_two_dates]
                diff = fibDates[3].max() - fibDates[3].min()
                # calculate fibRetracements
                fibRetracement = pd.DataFrame(fibLvl)
                maxAsk = fibDates[3].max()
                for lvl in fibRetracement:
                    fibRetracement[1] =  maxAsk - diff * fibRetracement[0]
                    fibRetracement[2] = fibRetracement[1] * 0.999
                    fibRetracement[3] = fibRetracement[1] * 1.001

                #calculate corvalue
                corValue = fibDates[0].corr(fibDates[3])

                #get statistical parameters
                statisticsTools = {}
                statisticsTools["stDev"] = statistics.stdev(fibDates[3])
                statisticsTools["skew"] = skew(fibDates[3])
                statisticsTools["kurtosis"] = kurtosis(fibDates[3])

                #if an open position exists, check if it can be closed
                if openPositions:
                    if (row[3] <= openPositions['stopLoss'] or
                        row[3] >= openPositions['takeProfit']):
                            writeTrade(openPositions, row, postgres)
                            #clear open position
                            openPositions = {}
                else:
                    #loop over considered fibonacciretracements
                    for i in [7]:
                        #check if buy requirements are met
                        if (row[7] <= -10):
                            #if (statisticsTools["skew"] <= -0.1 and
                            #    row[3] >= fibRetracement[2][i] and
                            #    row[3] <= fibRetracement[3][i]):
                                    openPositions['startId'] = fibDates[0].min()
                                    openPositions['id'] = row[0]
                                    openPositions['midId'] = fibDates[0].max()
                                    openPositions['bidPrice'] = row[4]
                                    openPositions['fibLevel'] = i
                                    openPositions['symbol'] = symbol
                                    openPositions['askPrice'] = row[3]
                                    openPositions['corValue'] = corValue
                                    openPositions['stDev'] = statisticsTools["stDev"]
                                    openPositions['skew'] = statisticsTools["skew"]
                                    openPositions['kurtosis'] = statisticsTools["kurtosis"]
                                    openPositions['takeProfit'] = fibRetracement[2][i+5]
                                    openPositions['stopLoss'] = fibRetracement[2][i-7]
    #close database connection
    postgres.databaseClose()

load_dotenv('../.env')
try:
    dbTable="backtesting"
    baseCurrency=env('baseCurrency')
    brokerFees=float(env('brokerFees'))
except KeyError:
    print("No env variables set.")
    sys.exit(1)
backtest()


import cv2
import numpy as np
from sklearn.metrics import average_precision_score, roc_auc_score
import matplotlib.pyplot as plt
from shapely.geometry import Polygon

from scripts.utils import part_corpus_to_idxs

def compute_iou(gt_coord, c, target_width, H):
    gt_polygon = Polygon(gt_coord)
    # minx, miny, maxx, maxy = Polygon(gt_coord).bounds
    # gt_polygon = Polygon([(minx, miny), (minx, maxy), (maxx, maxy), (maxx, miny)])
    c_polygon = Polygon(
        [[c["x"], c["y"]],
         [c["x"] + target_width, c["y"]],
         [c["x"] + target_width, c["y"] + H],
         [c["x"], c["y"] + H]])
    # Calculate Intersection and union, and tne IOU
    try:
        polygon_intersection = gt_polygon.intersection(c_polygon).area
        polygon_union = gt_polygon.union(c_polygon).area
        return polygon_intersection / polygon_union
    except Exception as e:
        print(e)
        return 0

def draw_blank(axs, x_plot, H, target_width, y_offset_sim):
    blank = np.zeros((H, target_width, 3), np.uint8)
    blank[:] = (255, 255, 255)
    axs[x_plot].axis('off')
    axs[x_plot].imshow(blank, interpolation='nearest', cmap='Greys_r')
    axs[x_plot].text(0.5, y_offset_sim, "...", size=12, ha="center",
                     transform=axs[x_plot].transAxes)

def do_evaluate(images, gt_words, word, candidates, template, H, widths,
                target_width, worst_candidates, corpus_name, draw_results=False):
    gt_coords = [t["coords"] for t in gt_words]

    i = 1
    i_last_found = 0
    nbr_found = 0
    for c in candidates:
        c["found"] = False
        c["rank"] = i
        c["IOU"] = 0
        for gt_coord in gt_coords:
            c["IOU"] = max(compute_iou(gt_coord, c, target_width, H), c["IOU"])
            if c["IOU"] > 0.5:
                i_last_found = i
                nbr_found += 1
                c["found"] = True
                break

        if nbr_found == len(gt_coords):
            break
        i += 1

    if nbr_found == 0:
        return 0, 0, 0, 0, 0
    else:

        # cut the candidates list to the last found candidate
        candidates = candidates[:i_last_found]

        # Extract the predicted probabilities and ground truth labels from the candidates
        probs = [candidate['sim'] for candidate in candidates]
        labels = [candidate['found'] for candidate in candidates]
        ap = average_precision_score(labels, probs)

        # Compute the AUC
        if sum(labels) == 0:
            roc_auc = 0
        elif sum(labels) == len(labels):
            roc_auc = 1
        else:
            roc_auc = roc_auc_score(labels, probs)

        n_retrieved_relevant = sum(1 for candidate in candidates if candidate['found'])

        precision = n_retrieved_relevant / len(candidates)
        recall = n_retrieved_relevant / len(gt_coords)

        if recall > 1:
            recall = 1

        if draw_results:
            NBR_TEMPLATE = 1
            NBR_OF_CANDIDATES = min(len(candidates), 10)
            NBR_BLANK_SPACE = 1
            NBR_WORST = max(len(worst_candidates), 1)

            fig, axs = plt.subplots(1,
                                    NBR_TEMPLATE + NBR_OF_CANDIDATES + NBR_BLANK_SPACE + NBR_WORST,
                                    figsize=((NBR_TEMPLATE + NBR_OF_CANDIDATES + NBR_BLANK_SPACE + NBR_WORST) * 1.5, 3))

            fig.suptitle(
                'Page ' + template["page_name"]
                + " \"" + word + "\" : "
                + str(len(gt_words)) + " instance(s). "
                + str(sum(map(lambda x: x["found"], candidates))) + " found over the first " + str(
                    len(candidates)) + " candidates. "
                + str(round(ap * 100, 2)) + "% AP", fontsize=16)

            fig.tight_layout(pad=10.0)
            plt.axis('off')

            minx, miny, maxx, maxy = Polygon(template["coords"]).bounds

            y_offset_sim = -0.8 if widths.index(target_width) > 1 else -0.5
            y_offset_iou = 1.1 if widths.index(target_width) > 1 else 1.1
            y_offset_page_no = 1.5 if widths.index(target_width) > 1 else 1.5

            # Draw the template first
            axs[0].imshow(images[template["page_no"]][
                          int(miny):int(maxy),
                          int(minx):int(maxx)])
            axs[0].text(0.5, y_offset_page_no, "Page", size=12, ha="center", transform=axs[0].transAxes)
            axs[0].text(0.5, y_offset_sim, "Sim", size=12, ha="center", transform=axs[0].transAxes)
            axs[0].text(0.5, y_offset_iou, "IOU", size=12, ha="center", transform=axs[0].transAxes)
            axs[0].axis('off')

            for i in range(min(len(candidates), 10)):
                cdnt = candidates[i]
                page_no, x, y, sim, found, IOU = cdnt["page_no"], cdnt["x"], cdnt["y"], cdnt["sim"], \
                    cdnt["found"] if "found" in cdnt else False, cdnt["IOU"] if "IOU" in cdnt else 0

                to_draw = cv2.copyMakeBorder(
                    images[page_no][int(y):int(y + H), int(x):int(x + target_width)],
                    top=3,
                    bottom=3,
                    left=3,
                    right=3,
                    borderType=cv2.BORDER_CONSTANT,
                    value=[0, 255, 0] if found else [255, 0, 0]
                )

                axs[i + NBR_TEMPLATE].axis('off')
                axs[i + NBR_TEMPLATE].imshow(to_draw, interpolation='nearest', cmap='Greys_r')

                axs[i + NBR_TEMPLATE].text(0.5, y_offset_sim, str(round(sim, 3)), size=12, ha="center",
                                           transform=axs[i + 1].transAxes)

                axs[i + NBR_TEMPLATE].text(0.5, y_offset_iou, str(round(IOU, 3)), size=12, ha="center",
                                           transform=axs[i + 1].transAxes)

                # only write the page number for the choosen candidates (first row)
                axs[i + NBR_TEMPLATE].text(0.5, y_offset_page_no, str(page_no), size=12,
                                           ha="center",
                                           transform=axs[i + 1].transAxes)

            # Draw blank
            draw_blank(axs, NBR_TEMPLATE + NBR_OF_CANDIDATES, H, target_width, y_offset_sim)

            for i in range(len(worst_candidates)):
                worst_candidate = worst_candidates[i]
                page_no, x, y, sim = worst_candidate["page_no"], worst_candidate["x"], worst_candidate["y"], \
                    worst_candidate["sim"]

                to_draw = images[page_no][int(y):int(y + H),
                          int(x):int(x + target_width)]

                axs[NBR_TEMPLATE + NBR_OF_CANDIDATES + NBR_BLANK_SPACE + i].axis('off')
                try:

                    axs[NBR_TEMPLATE + NBR_OF_CANDIDATES + NBR_BLANK_SPACE + i].imshow(to_draw)
                except:
                    print("worst_candidates oups")

                axs[NBR_TEMPLATE + NBR_OF_CANDIDATES + NBR_BLANK_SPACE + i].text(0.5, y_offset_sim,
                                                                                str(round(sim, 3)), size=12,
                                                                                ha="center",
                                                                                transform=axs[
                                                                                NBR_TEMPLATE + NBR_OF_CANDIDATES + NBR_BLANK_SPACE + i].transAxes)

            results_folders = "/".join(['results', corpus_name])
            fig.savefig(results_folders + '/' + template["template_id"] + "_results_top10" + '.png')
            plt.draw()
            plt.clf()
            plt.close("all")

    return precision, recall, ap, roc_auc, len(word)

## ['only-from-corpus', 'only-from-non-corpus', 'intersection', 'all']
def split_train_test(images, templates, strategy, part_corpus=None):
    if part_corpus and strategy:

        idxs_corpus_images = part_corpus_to_idxs(part_corpus, len(images))
        idxs_query_images = list(set(list(range(0, len(images)))) - set(idxs_corpus_images))

        images_splitted = {"corpus": [], "query": []}
        templates_splitted = {"corpus": [], "query": []}

        if strategy == "only-from-corpus":
            for template in templates:
                if template["page_no"] in idxs_corpus_images:
                    templates_splitted["corpus"].append(template)
                    templates_splitted["query"].append(template)

            for ii in range(len(images)):
                if ii in idxs_corpus_images:
                    images_splitted["corpus"].append(images[ii])
                    images_splitted["query"].append(images[ii])

        elif strategy == 'only-from-non-corpus':
            for template in templates:
                if template["page_no"] in idxs_query_images:
                    templates_splitted["corpus"].append(template)
                    templates_splitted["query"].append(template)

            for ii in range(len(images)):
                if ii in idxs_query_images:
                    images_splitted["corpus"].append(images[ii])
                    images_splitted["query"].append(images[ii])

        elif strategy == "intersection":
            for template in templates:
                if template["page_no"] in idxs_corpus_images:
                    templates_splitted["corpus"].append(template)
                else:
                    templates_splitted["query"].append(template)

            for ii in range(len(images)):
                if ii in idxs_corpus_images:
                    images_splitted["corpus"].append(images[ii])
                else:
                    images_splitted["query"].append(images[ii])

        else:
            images_splitted = {"corpus": images, "query": images}
            templates_splitted = {"corpus": templates, "query": templates}
    else:
        images_splitted = {"corpus": images, "query": images}
        templates_splitted = {"corpus": templates, "query": templates}

    return images_splitted, templates_splitted

def filter_by_word_size(templates, template_text_limit):
    return list(filter(lambda x: len(x["word"]) > template_text_limit, templates))


from rest_framework import serializers
from django.db.models import Avg

from .models import Course, Review

class ReviewSerializer(serializers.ModelSerializer):
    class Meta:
        extra_kwargs = {
            'email': {'write_only': True}
        }
        model = Review
        fields = (
            'id',
            'name',
            'email',
            'text',
            'note',
            'created_at',
            'updated_at',
            'active'
        )

    def validate_note(self, value):
        if value in range(1, 6):
            return value

        return serializers.ValidationError('A Nota da curso deve estar entre 1 ~ 5')

class CourseSerializer(serializers.ModelSerializer):
    # Nested
    #reviews = ReviewSerializer(many=True, read_only=True)

    # HyperlinkedRelatedField
    # reviews = serializers.HyperlinkedRelatedField(
    #     many=True,
    #     read_only=True,
    #     view_name='review-detail'
    # )

    # Primary Key
    reviews = serializers.PrimaryKeyRelatedField(many=True, read_only=True)

    mean = serializers.SerializerMethodField()

    class Meta:
        model = Course
        fields = (
            'id',
            'title',
            'url',
            'created_at',
            'updated_at',
            'active',
            'reviews',
            'mean'
        )

    def get_mean(self, obj):
        mean = obj.reviews.aggregate(Avg('note')).get('review__avg')
        if mean is None:
            return 0

        return round(mean * 2) / 2


import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import os

def parse_args():

    import argparse

    parser = argparse.ArgumentParser(
        description = 'Create YOLO format from masks')
    parser.add_argument(
        '-dir', dest='directory',
        help='Relative path to the directory containing the images',
        default='imgs', type=str)

    args = parser.parse_args()
    return args

def main(args):
    annotations_path = args.directory

    if 'yolo_data' not in os.listdir(annotations_path):
        os.makedirs(os.path.join(annotations_path,'yolo_data'))

    render_results = os.listdir(annotations_path)
    annotations = [filename for filename in render_results if 'annotation' in filename]
    annotations.sort()

    PATHS = [os.path.join(annotations_path, filename) for filename in annotations]
    img_res = (1080,1920, 3)

    for PATH in PATHS:
        print(PATH)
        mask = np.array(Image.open(PATH).getdata())
        colors = np.unique(mask, axis=0)

        fig, ax = plt.subplots()

        bounding_boxes = []

        with open(os.path.join(annotations_path, 'yolo_data', PATH.split(os.sep)[-1].replace('_annotation.png', '.txt')), 'w') as f:
            for color in colors:
                if color[0:3].any():
                    x_min = (np.where(np.all(mask==color, axis=1))[0]%1920).min()
                    x_max = (np.where(np.all(mask==color, axis=1))[0]%1920).max()
                    y_min = (np.where(np.all(mask==color, axis=1))[0]//1920).min()
                    y_max = (np.where(np.all(mask==color, axis=1))[0]//1920).max()

                    width = x_max-x_min
                    height = y_max-y_min

                    x_center = x_min + width/2
                    y_center = y_min + height/2

                    yolo_format = [str(val) for val in [0,x_center/img_res[1],y_center/img_res[0],width/img_res[1],height/img_res[0]]]

                    bounding_boxes.append((x_min, x_max, y_min, y_max))
                    f.write(' '.join(yolo_format)+'\n')

        ax.imshow(np.reshape(np.array(Image.open(PATH.replace('_annotation', '')).getdata())[:,0:3],img_res))

        for bounding_box in bounding_boxes:
            rect = patches.Rectangle((bounding_box[0], bounding_box[2]), bounding_box[1]-bounding_box[0], bounding_box[3]-bounding_box[2], linewidth=1, edgecolor='r', facecolor='none')
            ax.add_patch(rect)

        plt.axis('off')
        plt.savefig(os.path.join(annotations_path, 'yolo_data', PATH.split(os.sep)[-1]))

if __name__ == "__main__":
    args = parse_args()
    main(args)


from timeit import main
import PyPDF2
import difflib
import sys

def pdfToText(pdf, txt):
    pdffileobj = open(pdf, "rb")

    # create reader variable that will read the pdffileobj
    pdfreader = PyPDF2.PdfFileReader(pdffileobj)

    # This will store the number of pages of this pdf file
    x = pdfreader.numPages

    # create a variable that will select the selected number of pages
    pageobj = pdfreader.getPage(x - 1)

    # (x+1) because python indentation starts with 0.
    # create text variable which will store all text datafrom pdf file
    text = pageobj.extractText()

    file = open(txt, "w+")
    file.write(text)

def compare_txt(_text_A, _text_B):
    with open(_text_A) as file_1:
        file_1_text = file_1.readlines()

    with open(_text_B) as file_2:
        file_2_text = file_2.readlines()

    # Find and print the diff:
    for line in difflib.unified_diff(file_1_text, file_2_text, fromfile="file1.txt", tofile="file2.txt", lineterm=""):
        print(line)

if __name__ == "__main__":
    # List of pdfs
    pdf = []
    # total arguments
    n = len(sys.argv)
    for i in range(1, n):
        pdf_file = sys.argv[i]
        pdf.append(pdf_file)

    # List of text file names. Just some dummy files GOOD FOR NOTHING
    txt = ["txt1.txt", "txt2.txt"]

    for i in range(2):
        pdfToText(pdf[i], txt[i])

    compare_txt(txt[0], txt[1])


from datetime import datetime
import http.client
import json
import sys

import tzlocal

def measure_workflow_execution_time(
    circle_ci_token: str,
    metrics_endpoint: str,
    workflow_id: str,
    has_run_e2e_tests: str,
):
    conn = http.client.HTTPSConnection("circleci.com")
    auth = f"Basic {circle_ci_token}"
    circle_ci_headers = {"authorization": auth}
    conn.request("GET", f"/api/v2/workflow/{workflow_id}", headers=circle_ci_headers)

    res = conn.getresponse()
    data = res.read()

    created_at = datetime.strptime(json.loads(data.decode("utf-8"))["created_at"], "%Y-%m-%dT%H:%M:%SZ")
    worfklow_execution_time = datetime.utcnow() - created_at
    conn = http.client.HTTPSConnection(metrics_endpoint)
    headers = {"Content-Type": "text/plain"}
    metric_type = "0442e902-b72c-45b7-8482-d2afe7842bf7" if has_run_e2e_tests == "True" else "2f6cabcf-51ee-44c5-b3f5-102854159dad"

    conn.request(
        "POST",
        "/push-metric",
        json.dumps(
            {
                "value": int(worfklow_execution_time.total_seconds()),
                "creationDate": datetime.now(tzlocal.get_localzone()).strftime("%Y-%m-%dT%H:%M:%SZ"),
                "project": 988,
                "metricType": metric_type,
            }
        ),
        headers,
    )

if __name__ == "__main__":
    measure_workflow_execution_time(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4])


__copyright__ = "Copyright 2020, 3Liz"
__license__ = "GPL version 3"
__email__ = "info@3liz.org"
__revision__ = "$Format:%H$"

from functools import partial

from qgis.PyQt import (
    QtWidgets,
)
from qgis.PyQt.QtWidgets import (
    QPushButton,
)
from qgis.core import (
    Qgis,
)

from webbrowser import open_new
from processing import execAlgorithmDialog
from lizsync.qgis_plugin_tools.tools.i18n import tr
from lizsync.qgis_plugin_tools.tools.resources import load_ui, plugin_name

FORM_CLASS = load_ui('lizsync_dockwidget_base.ui')

class LizsyncDockWidget(QtWidgets.QDockWidget, FORM_CLASS):

    def __init__(self, iface, parent=None):
        """Constructor."""
        super().__init__(parent)

        self.iface = iface
        self.setupUi(self)

        # Buttons directly linked to an algorithm
        self.algorithms = [
            'create_database_structure',
            'upgrade_database_structure',
            'initialize_central_database',

            'deploy_database_server_package',
            'package_central_database',
            'synchronize_database',

            'build_mobile_project',
            'send_projects_and_files_to_clone_ftp',

            'package_all',
            'deploy_all',
        ]
        for alg in self.algorithms:
            button = self.findChild(QPushButton, 'button_{0}'.format(alg))
            if not button:
                continue
            button.clicked.connect(partial(self.runAlgorithm, alg))

        # Buttons not linked to algs
        #
        # Help on database
        button = self.findChild(QPushButton, 'button_help_database')
        if button:
            button.clicked.connect(self.help_database)

    def runAlgorithm(self, name):

        if name not in self.algorithms:
            self.iface.messageBar().pushMessage(
                tr("Error"),
                tr("This algorithm cannot be found") + ' {}'.format(name),
                level=Qgis.Critical
            )
            return

        # Run alg
        param = {}
        alg_name = 'lizsync:{0}'.format(name)
        execAlgorithmDialog(alg_name, param)

    @staticmethod
    def help_database():
        """
        Display the help on database structure
        """
        name = plugin_name().lower()
        open_new(r'https://3liz.github.io/qgis-%s-plugin/' % name)


import pandas as pd
import time
from datetime import datetime
from kafka import KafkaProducer

# Read CSV file into a DataFrame
# Replace 'your_file.csv' with the actual file name
df = pd.read_csv('ecommerce.csv', low_memory=False)

# Kafka producer configuration
producer = KafkaProducer(bootstrap_servers='localhost:9094',
                         value_serializer=lambda v: str(v).encode('utf-8'))

# Iterate over rows and simulate real-time data flow
for index, row in df.iterrows():
    order_info = {
        'item_id': int(row['item_id']),
        'status': str(row['status']),
        'created_at': str(row['created_at']),
        'price': float(row['price']),
        'qty_ordered': int(row['qty_ordered']),
        'grand_total': float(row['grand_total']),
    }

    # Send data to Kafka topic
    producer.send('sales', value=order_info)

    # Simulate a delay to represent real-time processing
    time.sleep(10)


"""Steps implementation for following scenarios: verifyStage.feature."""

from behave import given, when, then
from features.src.stage import Stage
from features.src import pipeline
from pyshould import should

@given(u'I have verified a booster\'s pipeline has completed')
def given_pipeline_completed(context):
    """Precondition that the pipeline has been complete and verified."""
    pipeline.pipelineVerified | should.be_true.desc("Pipeline is complete and verified.")
    print('Attempting to use query for Pipeline deployed to Stage...')
    context.stage = Stage()

@when(u'I query a pipeline\'s stage endpoint')
def when_query_pipeline(context):
    """Query a pipeline's stage endpoint."""
    result = context.stage.runTest('testing stage endpoint')
    print('Result = {}'.format(result))
    context.result = result

@then(u'I should see the deployed app running on stage')
def then_app_running_stage(context):
    """Check that the app is deployed and running on stage."""
    result = context.result
    result | should.equal('Success').desc("Application is reachable in the Stage stage.")


import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.keras import Model
from tensorflow.keras.layers import Conv2D, Dense, MaxPool2D, Flatten, Dropout, GlobalAvgPool2D

class NiNBlock(Model):
    def __init__(self, filter_num, kernel_size, padding, strids):
        super(NiNBlock, self).__init__()
        self.layers_lst = []
        self.layers_lst.append(
            Conv2D(filters=filter_num, kernel_size=kernel_size, padding=padding, strides=strids, activation='relu')
        )
        for _ in range(2):
            self.layers_lst.append(Conv2D(filters=filter_num, kernel_size=1, activation='relu'))

    def call(self, inputs, training=None, mask=None):
        for layer in self.layers_lst:
            inputs = layer(inputs)
        return inputs

class NiN(Model):
    def __init__(self, label_num):
        super(NiN, self).__init__()
        self.layer_lst = []
        NiNBlock_info = [
            [96, 11, 4, 'valid'],
            [256, 5, 1, 'same'],
            [384, 3, 1, 'same']
            # , [10, 3, 1, 1]
        ]
        for (filter_num, kernel_size, strides, pading) in NiNBlock_info:
            self.layer_lst.append(NiNBlock(filter_num=filter_num, kernel_size=kernel_size, strids=strides, padding=pading))
            self.layer_lst.append(MaxPool2D(pool_size=3, strides=2))
        self.layer_lst.append(Dropout(0.5))
        self.layer_lst.append(NiNBlock(filter_num=label_num, kernel_size=3, strids=1, padding='same'))
        self.layer_lst.append(GlobalAvgPool2D())
        self.layer_lst.append(Flatten())

    def call(self, inputs, training=None, mask=None):
        for layer in self.layer_lst:
            inputs = layer(inputs)
        return inputs

if __name__ == "__main__":
    # config
    debug = True
    img_dimension = 28
    target_dimension = 224
    batch_size = 8
    epochs = 10
    label_num = 10
    if debug:
        train_num = 60
        test_num = 10
    else:
        train_num = -1
        test_num = -1

    (x_train, y_train), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data()
    x_train = x_train[:train_num, :, :]
    y_train = y_train[:train_num, ]
    x_test = x_test[:test_num, :, :]
    y_test = y_test[:test_num, ]

    x_train = (x_train.astype('float32')/225).reshape(x_train.shape[0], img_dimension, img_dimension, 1).astype('float32')
    x_test = (x_test.astype('float32')/225).reshape(x_test.shape[0], img_dimension, img_dimension, 1).astype('float32')

    x_train = tf.image.resize(x_train, [target_dimension, target_dimension])
    x_test = tf.image.resize(x_test, [target_dimension, target_dimension])

    model = NiN(label_num=10)
    model.compile(optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy'])

    sample_input = tf.ones(shape=[1, target_dimension, target_dimension, 1])
    print(sample_input.shape)
    for layer in model.layers:
        sample_input = layer(sample_input)
        print(sample_input.shape)
    # model(sample_input)
    # print(model.summary())
    history = model.fit(
        x=x_train
        , y=y_train
        , validation_data=(x_test, y_test)
        , epochs=epochs
        , batch_size=batch_size
    )
    print(model.summary())
    acc = history.history['accuracy']
    val_ac = history.history['val_accuracy']
    loss = history.history['loss']
    val_loss = history.history['val_loss']

    epochs_range = range(epochs)

    plt.figure(figsize=(10, 10))
    plt.subplot(1, 2, 1)
    plt.plot(epochs_range, acc, label='Train acc')
    plt.plot(epochs_range, val_ac, label='Val acc')
    plt.legend(loc='lower right')
    plt.title("Train & val accuracy")

    plt.subplot(1, 2, 2)
    plt.plot(epochs_range, loss, label='Train loss')
    plt.plot(epochs_range, val_loss, label='Val loss')
    plt.legend(loc='upper right')
    plt.title("Train & val loss")
    plt.show()


import sys,os
pj = os.path.join
curPath = os.path.abspath(os.path.dirname(__file__))
rootPath = os.path.split(curPath)[0]
sys.path.append(rootPath)
sys.path.append(curPath)
sys.path.append(pj(curPath,'FaceRecog'))
sys.path.append(pj(curPath,'Pytorch_Retinaface'))

import torch
import torch.nn as nn
from torchvision.models.quantization.utils import quantize_model
import torch_mlu
import torch_mlu.core.mlu_model as ct
import torch_mlu.core.mlu_quantize as mlu_quantize
import argparse
import cv2
import numpy as np
from sklearn.preprocessing import normalize
from FaceRecog.eval_utils.inference_api import Inference
from Pytorch_Retinaface.retina_infer import RetinaFaceDet

ct.set_core_number(4)
ct.set_core_version('MLU270')

"""
face bbox detection
"""

def fetch_cpu_data(x,use_half_input=False, to_numpy=True):
    if use_half_input:
        output = x.cpu().type(torch.FloatTensor)
    else:
        output = x.cpu()
    if to_numpy:
        return output.detach().numpy()
    else:
        return output

def resize(img_cv, dh = 540 , dw = 860 ):
    output_img = np.zeros((dh, dw, 3), np.uint8)
    dratio = dh / dw
    h, w = img_cv.shape[:2]
    ratio = h / w
    if ratio > dratio:
        # w should be filled
        _h = dh
        scale = _h / h
        _w = int(w * scale)
    else:
        # h should be filled
        _w = dw
        scale = _w / w
        _h = int(h * scale)

    scale_img_cv = cv2.resize(img_cv, dsize=(_w, _h))
    #filling the output_img with scale_img_cv where ratio of width and height is same as img_cv.
    output_img[:_h, :_w, :] = scale_img_cv[:, :, :]

    return {
        'output': output_img,
        'ratio': scale,#得到的检测框与原图上人脸框之间的比例
    }

def _normalize_retinaface(img_cv2,dst_size=[480,640],mlu=False):
    """
    :param img_cv2:
    :param dst_size:[width,height]
    :param mlu:
    :return:
    """
    dw,dh = dst_size
    out_data = resize(img_cv2,dh,dw)
    img_cv2 = out_data['output']
    ratio = out_data['ratio']
    img_data = np.asarray(img_cv2, dtype=np.float32)
    if mlu:
        return img_data,ratio #[0,255]
    else:
        mean = (104, 117, 123)
        img_data = img_data - mean
        img_data = img_data.astype(np.float32) #[0,1] normalized
    return img_data,ratio

def preprocess_retinaface(img_cv2, dst_size = [480,640] ,mlu=False):
    img_data, ratio = _normalize_retinaface(img_cv2,dst_size=dst_size,mlu=mlu)
    img_data = np.transpose(img_data, axes=[2, 0, 1])
    img_data = np.expand_dims(img_data, axis=0)
    img_t = torch.from_numpy(img_data)
    return img_t, ratio

class mlu_face_det_inference(object):
    def __init__(self ,weights,model_name='mobile0.25',use_mlu=True,use_jit=False):
        super(mlu_face_det_inference,self).__init__()

        self.use_mlu = use_mlu
        self.use_jit = use_jit
        loading = False if use_mlu else True
        infer = RetinaFaceDet(model_type=model_name,model_path=weights,use_cpu=True,loading=loading)
        model = infer.net
        if use_mlu:
            print('==using mlu quantization model==')
            model = mlu_quantize.quantize_dynamic_mlu(model)
            checkpoint = torch.load(weights, map_location='cpu')
            model.load_state_dict(checkpoint, strict=False)
            model.eval()
            model = model.to(ct.mlu_device())
            if use_jit:
                print('==jit==')
                randinput = torch.rand(1,3,640,480)*255
                randinput = randinput.to(ct.mlu_device())
                traced_model = torch.jit.trace(model, randinput, check_trace=False)
                self.model = traced_model
            else:
                self.model = model
        else:
            print('==using pytorch model==')
            model.eval()
            self.model = model

        self.infer = infer

    @torch.no_grad()
    def execute(self,img_cv2,dst_size=[480,640],threshold=0.8,topk=5000,keep_topk=750,nms_threshold=0.2):
        """
        :param dst_size: [width,height] all image will be scaled into that size for detection, but bbox will be returned in its original scale
        :param img_cv2: img_cv2 = cv2.imread() or [cv2.imread(c) for c in image_list]
        :return: detss = list of np.array, [ np.array(n,15)]
                    where len(detss) = len(img_cv2) if isinstance(img_cv2,list) else 1
                          n = detected faces in each image
                          15 :[x0,y0,x1,y1,score,landmarkx0,landmarky0,...,]
        """
        if isinstance(img_cv2,list):
            data = [preprocess_retinaface(c, dst_size ,mlu=self.use_mlu) for c in img_cv2]
            ratio = [ c[1] for c in data ]
            data = [c[0] for c in data]
            data = torch.cat(data, dim=0)
        else:
            data = preprocess_retinaface(img_cv2,dst_size=dst_size,mlu=self.use_mlu)
            ratio = [data[1]]
            data = data[0]

        if self.use_mlu:
            data = data.to(ct.mlu_device())

        locs,confs,landmss = self.model(data)

        if self.use_mlu:
            locs = fetch_cpu_data(locs,use_half_input=False,to_numpy=False)
            confs = fetch_cpu_data(confs,use_half_input=False,to_numpy=False)
            landmss = fetch_cpu_data(landmss,use_half_input=False,to_numpy=False)

        net_output = [locs,confs,landmss]
        dets = self.infer.execute_batch_mlu(net_output=net_output, batch_shape=data.shape,
                                            threshold=threshold,topk=topk,keep_topk=keep_topk,
                                            nms_threshold=nms_threshold)

        assert len(dets) == len(ratio), 'Err len(dets) != len(ratio)'

        detss = []
        for n,det in enumerate(dets):
            det = det/ratio[n]
            det[:,4] = det[:,4]*ratio[n]
            detss.append(det)
        return detss

"""
face feature extraction
"""
def _format(img_cv2, format_size=112):
    org_h, org_w = img_cv2.shape[0:2]
    rescale_ratio = format_size / max(org_h, org_w)
    h, w = int(org_h * rescale_ratio), int(org_w * rescale_ratio)
    img_rescaled = cv2.resize(img_cv2, (w, h))
    paste_pos = [int((format_size - w) / 2), int((format_size - h) / 2)]
    img_format = np.zeros((format_size, format_size, 3), dtype=np.uint8)
    img_format[paste_pos[1]:paste_pos[1] + h, paste_pos[0]:paste_pos[0] + w] = img_rescaled
    return img_format

def _normalize(img_cv2,mlu=False):
    img_cv2 = cv2.cvtColor(img_cv2, cv2.COLOR_BGR2RGB)
    # mean = [123.675, 116.28, 103.53]
    # std = [58.395, 57.12, 57.375]
    img_data = np.asarray(img_cv2, dtype=np.float32)
    if mlu:
        return img_data #[0,255]
    else:
        mean = 0.5
        std = 0.5
        img_data /= 255
        img_data = img_data - mean
        img_data = img_data / std
        img_data = img_data.astype(np.float32) #[0,1] normalized
    return img_data

def preprocess(img_cv2, mlu=False):
    img_format = _format(img_cv2)
    img_data = _normalize(img_format,mlu=mlu)
    img_data = np.transpose(img_data, axes=[2, 0, 1])
    img_data = np.expand_dims(img_data, axis=0)
    img_t = torch.from_numpy(img_data)
    return img_t

class mlu_face_rec_inference(object):
    def __init__(self ,weights,model_name='resnet101_irse_mx',use_mlu=True,use_jit=False):
        super(mlu_face_rec_inference,self).__init__()

        self.use_mlu = use_mlu
        self.use_jit = use_jit
        use_device = 'cpu'
        ckpt_fpath = None if use_mlu else weights
        infer = Inference(backbone_type=model_name,
                          ckpt_fpath=ckpt_fpath,
                          device=use_device)
        model = infer.model
        if use_mlu:
            print('==using mlu quantization model==')
            model = mlu_quantize.quantize_dynamic_mlu(model)
            checkpoint = torch.load(weights, map_location='cpu')
            model.load_state_dict(checkpoint, strict=False)
            model.eval()
            model = model.to(ct.mlu_device())
            if use_jit:
                print('==jit==')
                randinput = torch.rand(1,3,112,112)*255
                randinput = randinput.to(ct.mlu_device())
                traced_model = torch.jit.trace(model, randinput, check_trace=False)
                self.model = traced_model
            else:
                self.model = model
        else:
            print('==using pytorch model==')
            model.eval()
            self.model = model

    @torch.no_grad()
    def execute(self,img_cv2):
        """
        :param img_cv2: img_cv2 = cv2.imread() or [cv2.imread(c) for c in image_list]
        :return: unnormalized feature [N,512], N = len(img_cv2) if isinstance(img_cv2,list) else 1
        """
        if isinstance(img_cv2,list):
            data = [preprocess(c, mlu=self.use_mlu) for c in img_cv2]
            data = torch.cat(data, dim=0)
        else:
            data = preprocess(img_cv2,mlu=self.use_mlu)

        if self.use_mlu:
            data = data.to(ct.mlu_device())

        out = self.model(data)
        out = out.cpu().detach().numpy().reshape(-1, 512)
        return out

def crop_face_from_bbox(img_cv2,dets):
    scale = 1.3 #expand for better face feature extraction
    img_faces = []
    for ith, b in enumerate(dets):
        b = list(map(int, b))
        pwidth = int((b[2] - b[0]) * scale)
        pheight = int((b[3] - b[1]) * scale)
        pcx = int((b[2] + b[0]) / 2)
        pcy = int((b[3] + b[1]) / 2)

        img_face = cv2.getRectSubPix(img_cv2, (pwidth, pheight), (pcx, pcy))
        img_faces.append(img_face)
    return img_faces

if __name__ == "__main__":
    print('just a usage example')
    img_cv2 = cv2.imread('sally.jpg')
    # img_cv2 = np.random.random((640,480,3))*255
    # img_cv2 = img_cv2.astype(np.uint8)
    h,w,c = img_cv2.shape
    cpu_face_det_model = mlu_face_det_inference(weights='weights/face_det/mobilenet0.25_Final.pth',use_mlu=False,use_jit=False)
    cpu_detss = cpu_face_det_model.execute(img_cv2,dst_size=[w,h])
    if len(cpu_detss) > 0:
        print(cpu_detss[0].shape,cpu_detss[0])

    mlu_face_det_model = mlu_face_det_inference(weights='weights/face_det/retinaface_mlu_int8.pth', use_mlu=True,
                                                use_jit=True)
    mlu_detss = mlu_face_det_model.execute(img_cv2,dst_size=[w,h])
    if len(mlu_detss) > 0:
        print(mlu_detss[0].shape,mlu_detss[0])

    # exit(0)
    # img_cv2 = cv2.imread('test.jpg')
    # using detss[0] because only 1 image is used for test. (ith image from batch image)
    cpu_face_images = crop_face_from_bbox(img_cv2,dets=cpu_detss[0])
    mlu_face_images = crop_face_from_bbox(img_cv2,dets=mlu_detss[0])

    #in this test only one face can be extracted from the image
    cpu_face_image = cpu_face_images[0]
    mlu_face_image = mlu_face_images[0]

    mlu_face_model = mlu_face_rec_inference(weights='weights/face_rec/resnet101_mlu_int8.pth',use_mlu=True,use_jit=True)
    cpu_face_model = mlu_face_rec_inference(weights='weights/face_rec/r101irse_model_3173.pth',use_mlu=False,use_jit=False)

    mlu_face_feature = mlu_face_model.execute(mlu_face_image)
    cpu_face_feature = cpu_face_model.execute(cpu_face_image)

    mlu_face_feature = normalize(mlu_face_feature,axis=1)
    cpu_face_feature = normalize(cpu_face_feature,axis=1)

    print('cosine similarity =', mlu_face_feature@(cpu_face_feature.transpose()))


from datetime import datetime
from multiprocessing import Process, Pipe, Event
import os
from os import path
import pickle
from PIL import Image
from typing import Any, Dict, List, Tuple
# Project Modules
from parsing import imageops, opencv, tesseract
from utils.directories import get_temp_directory, get_assets_directory

FOLDER = path.join(get_assets_directory(), "headers")
ROWS = 17.2

class ScoreboardDatabase(object):
    """Shelf that stores the results of Scoreboard results"""

    def __init__(self):
        """Initialize the Shelf and open the file"""
        self._path = path.join(get_temp_directory(), "scoreboards.db")
        self._shelf: dict = self.read_database()

    def read_database(self) -> dict:
        """Open the pickle and read from it"""
        if not path.exists(self._path):
            self._shelf = dict()
            self.save_database()
        with open(self._path, "rb") as fi:
            return pickle.load(fi)

    def save_database(self):
        """Save the data to the pickle"""
        with open(self._path, "wb") as fo:
            pickle.dump(self._shelf, fo)

    def save_scoreboard(self, match: datetime, scoreboard: Dict[str, Any]):
        """Save a scoreboard in the database"""
        if match in self._shelf:
            scoreboard = self.merge_scoreboards(self._shelf[match], scoreboard)
        self._shelf[match] = scoreboard
        self.save_database()

    @staticmethod
    def merge_scoreboards(scoreboard1: Dict[str, Any], scoreboard2: Dict[str, Any]) -> Dict[str, Any]:
        """Merge two scoreboards to a single scoreboard"""
        allies1, allies2 = scoreboard1["allies"], scoreboard2["allies"]
        enemies1, enemies2 = scoreboard1["enemies"], scoreboard2["enemies"]
        allies1.extend(allies2)
        enemies1.extend(enemies2)
        allies = list(set(allies1))
        enemies = list(set(enemies1))
        scoreboard1["allies"], scoreboard1["enemies"] = allies, enemies
        return scoreboard1

    @staticmethod
    def get_scoreboard(match: datetime):
        """Return the scoreboard for a match given the datetime key"""
        with ScoreboardDatabase() as db:
            if match not in db._shelf:
                return None
            return db[match]

    def __enter__(self):
        """Context manager: Open shelf"""
        return self

    def __exit__(self, exception: Exception, value: int, traceback: str):
        """Context manager: Close shelf"""
        if exception is not None:
            print("[ScoreboardDatabase] Unhandled context error: {}, {}, {}".format(
                exception, value, traceback))
        self.save_database()

    def __getitem__(self, item: datetime):
        """Dict accessor for shelf"""
        if item not in self._shelf:
            return None
        return self._shelf[item]

class ScoreboardParser(Process):
    """
    Parse a Scoreboard in a separate Process and save its results

    Runs the results in a separate process to ensure that the main
    process is not influenced. Saves the data in the ScoreboardDatabase,
    with the match start time (single datetime) as key.
    """
    SCALES: Dict[float, Image.Image] = {
        float(file[:-4]): Image.open(path.join(FOLDER, file)) for file in os.listdir(FOLDER)}
    HEIGHT: int = 430
    WIDTH: int = 1190
    COLUMNS: List[str] = ["name", "kills", "assists", "deaths", "damage", "hit", "objectives"]
    WIDTHS: Dict[str, float] = {
        "name": 280 / WIDTH,
        "kills": 130 / WIDTH,
        "assists": 130 / WIDTH,
        "deaths": 130 / WIDTH,
        "damage": 130 / WIDTH,
        "hit": 130 / WIDTH,
        "objectives": 130 / WIDTH,
    }

    def __init__(self, match_start: datetime, screenshot: Image.Image):
        """Initialize a scoreboard results process"""
        Process.__init__(self)
        self._match: datetime = match_start
        self._screenshot: Image.Image = screenshot
        self._db: ScoreboardDatabase = None
        self._recv, self._send = Pipe(False)
        self._progress: float = 0.0
        self._done = Event()
        self._started = Event()

    def start(self):
        """Handler to start the Process"""
        if self._started.is_set():
            return
        self._started.set()
        Process.start(self)

    def run(self):
        """Parse the Scoreboard and save its data"""
        result = self.parse_scoreboard()
        with ScoreboardDatabase() as db:
            db.save_scoreboard(self._match, result)
        self._send.send(-1)
        self._done.set()

    def parse_scoreboard(self) -> Dict[str, Any]:
        """Parse the scoreboard"""
        print("[ScoreboardParser] Parsing scoreboard")
        scoreboard: Dict[str, Any] = {"allies": list(), "enemies": list()}
        scale, location = self.get_scale_location(self._screenshot)
        rows: List[List[Image.Image]] = self.split_scoreboard(self._screenshot, scale, location)
        todo, done = sum(len(row) for row in rows), 0
        for i, row in enumerate(rows):
            columns: List[(str, int)] = list()
            allied = self.is_allied(row[1])
            for name, column in zip(ScoreboardParser.COLUMNS, row):
                r = tesseract.perform_ocr_scoreboard(column, name, 650, 400, 20)
                columns.append(r)
                print("[ScoreboardParser] {}: {} -> {}".format(i, name, r), flush=True)
                done += 1
                self._send.send(done / todo)
            scoreboard["allies" if allied else "enemies"].append(columns)
        try:
            scoreboard["score"] = self.get_score(self._screenshot, scale, location)
        except SystemError:
            scoreboard["score"]: List[int, int, int] = [0, 0, 0]
        return scoreboard

    @staticmethod
    def get_score(screenshot: Image.Image, scale: float, location: Tuple[int, int]) -> Tuple[int, int, int]:
        """Parse the score on the scoreboard to (left, right, self)"""
        x, y = location
        y1, y2 = int(y - 40 * scale), int(y - 20 * scale)
        x1_l, x2_l, x1_r, x2_r = x + 50 * scale, x + 85 * scale, x + 180 * scale, 205 * scale
        left: Image.Image = screenshot.crop(tuple(map(int, (x1_l, y1, x2_l, y2))))
        right: Image.Image = screenshot.crop(tuple(map(int, (x1_r, y1, x2_r, y2))))
        score_l, score_r = map(lambda i: tesseract.perform_ocr_scoreboard(i, "score", 420, 360), (left, right))
        return score_l, score_r, list(map(ScoreboardParser.is_allied, (left, right))).index(True)

    @staticmethod
    def crop_scoreboard(image: Image.Image, scale: float, location: Tuple[int, int]) -> Image.Image:
        """Crop a screenshot of a scoreboard to only the scoreboard"""
        (w, h), (x, y) = ScoreboardParser.SCALES[scale].size, location
        box = (x, y + h + int(round(7 * 1.05 / scale)), x + w, int(y + h + ScoreboardParser.HEIGHT * scale))
        return image.crop(box)

    @staticmethod
    def get_scale_location(screenshot: Image.Image) -> Tuple[float, Tuple[int, int]]:
        """Determine the location and scale of a scoreboard"""
        ratios: Dict[float, float] = dict()
        locations: Dict[float, Tuple[int, int]] = dict()
        for scale, template in ScoreboardParser.SCALES.items():
            ratio, location = opencv.template_match(screenshot, template, 0, True)
            ratios[ratio] = scale
            locations[scale] = location
        scale: float = ratios[max(ratios.keys())]
        return scale, locations[scale]

    @staticmethod
    def is_scoreboard(image: Image.Image, scale: float):
        """Determine whether a screenshot is an image of a scoreboard"""
        return opencv.template_match(image, ScoreboardParser.SCALES[scale], 0, True)[0] > 90

    @staticmethod
    def is_allied(subsection: Image.Image) -> bool:
        """Determine whether a row is of an allied or enemy player"""
        dominant: Tuple[int, int, int] = imageops.get_dominant_color(subsection)
        return dominant[1] > dominant[0]  # Green more dominant than red

    @staticmethod
    def split_scoreboard(image: Image.Image, scale: float, header_loc: tuple) -> list:
        """Split a scoreboard into different elements"""
        columns, widths = ScoreboardParser.COLUMNS, ScoreboardParser.WIDTHS
        image = ScoreboardParser.crop_scoreboard(image, scale, header_loc)
        result = list()
        for i in range(16):
            row = (0, i * (image.height / ROWS), image.width, (i + 1) * (image.height / ROWS))
            row_img = image.crop(row)
            row_elems = list()
            for name in columns:
                start = sum([widths[col] * image.width for col in columns[:columns.index(name)]])
                end = start + widths[name] * image.width
                crop = (start, 0, end, row_img.height)
                column = row_img.crop(crop)
                column = column.resize((column.width * 3, column.height * 3), Image.LANCZOS)
                row_elems.append(column)
            result.append(row_elems)
        return result

    @property
    def string(self):
        """Return a formatted progress string"""
        while self._recv.poll():
            self._progress = self._recv.recv()
        if self._progress == -1:
            return "Scoreboard: Complete\n"
        return "Scoreboard: {:03.1f}%\n".format(self._progress * 100)

    def is_done(self):
        return self._done.is_set()


import json
from typing import Any, Dict, List, Optional, Union

from flet_core.border import Border, BorderSide
from flet_core.constrained_control import ConstrainedControl
from flet_core.control import Control, OptionalNumber
from flet_core.control_event import ControlEvent
from flet_core.event_handler import EventHandler
from flet_core.gesture_detector import TapEvent
from flet_core.gradients import Gradient
from flet_core.ref import Ref
from flet_core.text_style import TextStyle
from flet_core.types import (
    AnimationValue,
    BorderRadiusValue,
    MaterialState,
    OffsetValue,
    ResponsiveNumber,
    RotateValue,
    ScaleValue,
)

class DataColumnSortEvent(ControlEvent):
    def __init__(self, i, a) -> None:
        self.column_index: int = i
        self.ascending: bool = a

class DataColumn(Control):
    def __init__(
        self,
        label: Control,
        ref=None,
        visible: Optional[bool] = None,
        disabled: Optional[bool] = None,
        data: Any = None,
        # specific
        numeric: Optional[bool] = None,
        tooltip: Optional[str] = None,
        on_sort=None,
    ):
        Control.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data)

        self.__on_sort = EventHandler(
            lambda e: DataColumnSortEvent(**json.loads(e.data))
        )
        self._add_event_handler("sort", self.__on_sort.get_handler())

        self.label = label
        self.numeric = numeric
        self.tooltip = tooltip
        self.on_sort = on_sort

    def _get_control_name(self):
        return "c"

    def _get_children(self):
        children = []
        if self.__label:
            self.__label._set_attr_internal("n", "l")
            children.append(self.__label)
        return children

    # label
    @property
    def label(self):
        return self.__label

    @label.setter
    def label(self, value):
        self.__label = value

    # numeric
    @property
    def numeric(self) -> Optional[bool]:
        return self._get_attr("numeric", data_type="bool", def_value=False)

    @numeric.setter
    def numeric(self, value: Optional[bool]):
        self._set_attr("numeric", value)

    # tooltip
    @property
    def tooltip(self):
        return self._get_attr("tooltip")

    @tooltip.setter
    def tooltip(self, value):
        self._set_attr("tooltip", value)

    # on_sort
    @property
    def on_sort(self):
        return self.__on_sort

    @on_sort.setter
    def on_sort(self, handler):
        self.__on_sort.subscribe(handler)
        self._set_attr("onSort", True if handler is not None else None)

class DataCell(Control):
    def __init__(
        self,
        content: Control,
        ref=None,
        visible: Optional[bool] = None,
        disabled: Optional[bool] = None,
        data: Any = None,
        # specific
        on_double_tap=None,
        on_long_press=None,
        on_tap=None,
        on_tap_cancel=None,
        on_tap_down=None,
        placeholder: Optional[bool] = None,
        show_edit_icon: Optional[bool] = None,
    ):
        Control.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data)

        self.__on_tap_down = EventHandler(lambda e: TapEvent(**json.loads(e.data)))
        self._add_event_handler("tap_down", self.__on_tap_down.get_handler())

        self.content = content
        self.on_double_tap = on_double_tap
        self.on_long_press = on_long_press
        self.on_tap = on_tap
        self.on_tap_cancel = on_tap_cancel
        self.on_tap_down = on_tap_down
        self.placeholder = placeholder
        self.show_edit_icon = show_edit_icon

    def _get_control_name(self):
        return "c"

    def _get_children(self):
        return [self.__content]

    # content
    @property
    def content(self):
        return self.__content

    @content.setter
    def content(self, value):
        self.__content = value

    # placeholder
    @property
    def placeholder(self) -> Optional[bool]:
        return self._get_attr("placeholder", data_type="bool", def_value=False)

    @placeholder.setter
    def placeholder(self, value: Optional[bool]):
        self._set_attr("placeholder", value)

    # show_edit_icon
    @property
    def show_edit_icon(self) -> Optional[bool]:
        return self._get_attr("showEditIcon", data_type="bool", def_value=False)

    @show_edit_icon.setter
    def show_edit_icon(self, value: Optional[bool]):
        self._set_attr("showEditIcon", value)

    # on_double_tap
    @property
    def on_double_tap(self):
        return self._get_event_handler("double_tap")

    @on_double_tap.setter
    def on_double_tap(self, handler):
        self._add_event_handler("double_tap", handler)
        self._set_attr("onDoubleTap", True if handler is not None else None)

    # on_long_press
    @property
    def on_long_press(self):
        return self._get_event_handler("long_press")

    @on_long_press.setter
    def on_long_press(self, handler):
        self._add_event_handler("long_press", handler)
        self._set_attr("onLongPress", True if handler is not None else None)

    # on_tap
    @property
    def on_tap(self):
        return self._get_event_handler("tap")

    @on_tap.setter
    def on_tap(self, handler):
        self._add_event_handler("tap", handler)
        self._set_attr("onTap", True if handler is not None else None)

    # on_tap_cancel
    @property
    def on_tap_cancel(self):
        return self._get_event_handler("tap_cancel")

    @on_tap_cancel.setter
    def on_tap_cancel(self, handler):
        self._add_event_handler("tap_cancel", handler)
        self._set_attr("onTapCancel", True if handler is not None else None)

    # on_tap_down
    @property
    def on_tap_down(self):
        return self.__on_tap_down

    @on_tap_down.setter
    def on_tap_down(self, handler):
        self.__on_tap_down.subscribe(handler)
        self._set_attr("onTapDown", True if handler is not None else None)

class DataRow(Control):
    def __init__(
        self,
        cells: Optional[List[Control]] = None,
        ref=None,
        visible: Optional[bool] = None,
        disabled: Optional[bool] = None,
        data: Any = None,
        # specific
        color: Union[None, str, Dict[MaterialState, str]] = None,
        selected: Optional[bool] = None,
        on_long_press=None,
        on_select_changed=None,
    ):
        Control.__init__(self, ref=ref, visible=visible, disabled=disabled, data=data)

        self.cells = cells
        self.color = color
        self.selected = selected
        self.on_long_press = on_long_press
        self.on_select_changed = on_select_changed

    def _get_control_name(self):
        return "r"

    def _before_build_command(self):
        super()._before_build_command()
        self._set_attr_json("color", self.__color)

    def _get_children(self):
        return self.__cells

    # cells
    @property
    def cells(self):
        return self.__cells

    @cells.setter
    def cells(self, value):
        self.__cells = value if value is not None else []

    # color
    @property
    def color(self) -> Union[None, str, Dict[MaterialState, str]]:
        return self.__color

    @color.setter
    def color(self, value: Union[None, str, Dict[MaterialState, str]]):
        self.__color = value

    # selected
    @property
    def selected(self) -> Optional[bool]:
        return self._get_attr("selected", data_type="bool", def_value=False)

    @selected.setter
    def selected(self, value: Optional[bool]):
        self._set_attr("selected", value)

    # on_long_press
    @property
    def on_long_press(self):
        return self._get_event_handler("long_press")

    @on_long_press.setter
    def on_long_press(self, handler):
        self._add_event_handler("long_press", handler)
        self._set_attr("onLongPress", True if handler is not None else None)

    # on_select_changed
    @property
    def on_select_changed(self):
        return self._get_event_handler("select_changed")

    @on_select_changed.setter
    def on_select_changed(self, handler):
        self._add_event_handler("select_changed", handler)
        self._set_attr("onSelectChanged", True if handler is not None else None)

class DataTable(ConstrainedControl):
    def __init__(
        self,
        columns: Optional[List[DataColumn]] = None,
        rows: Optional[List[DataRow]] = None,
        ref: Optional[Ref] = None,
        key: Optional[str] = None,
        width: OptionalNumber = None,
        height: OptionalNumber = None,
        left: OptionalNumber = None,
        top: OptionalNumber = None,
        right: OptionalNumber = None,
        bottom: OptionalNumber = None,
        expand: Union[None, bool, int] = None,
        col: Optional[ResponsiveNumber] = None,
        opacity: OptionalNumber = None,
        rotate: RotateValue = None,
        scale: ScaleValue = None,
        offset: OffsetValue = None,
        aspect_ratio: OptionalNumber = None,
        animate_opacity: AnimationValue = None,
        animate_size: AnimationValue = None,
        animate_position: AnimationValue = None,
        animate_rotation: AnimationValue = None,
        animate_scale: AnimationValue = None,
        animate_offset: AnimationValue = None,
        on_animation_end=None,
        tooltip: Optional[str] = None,
        visible: Optional[bool] = None,
        disabled: Optional[bool] = None,
        data: Any = None,
        #
        # DataTable-specific
        #
        border: Optional[Border] = None,
        border_radius: BorderRadiusValue = None,
        horizontal_lines: Optional[BorderSide] = None,
        vertical_lines: Optional[BorderSide] = None,
        checkbox_horizontal_margin: OptionalNumber = None,
        column_spacing: OptionalNumber = None,
        data_row_color: Union[None, str, Dict[MaterialState, str]] = None,
        data_row_min_height: OptionalNumber = None,
        data_row_max_height: OptionalNumber = None,
        data_text_style: Optional[TextStyle] = None,
        bgcolor: Optional[str] = None,
        gradient: Optional[Gradient] = None,
        divider_thickness: OptionalNumber = None,
        heading_row_color: Union[None, str, Dict[MaterialState, str]] = None,
        heading_row_height: OptionalNumber = None,
        heading_text_style: Optional[TextStyle] = None,
        horizontal_margin: OptionalNumber = None,
        show_bottom_border: Optional[bool] = None,
        show_checkbox_column: Optional[bool] = None,
        sort_ascending: Optional[bool] = None,
        sort_column_index: Optional[int] = None,
        on_select_all=None,
    ):
        ConstrainedControl.__init__(
            self,
            ref=ref,
            key=key,
            width=width,
            height=height,
            left=left,
            top=top,
            right=right,
            bottom=bottom,
            expand=expand,
            col=col,
            opacity=opacity,
            rotate=rotate,
            scale=scale,
            offset=offset,
            aspect_ratio=aspect_ratio,
            animate_opacity=animate_opacity,
            animate_size=animate_size,
            animate_position=animate_position,
            animate_rotation=animate_rotation,
            animate_scale=animate_scale,
            animate_offset=animate_offset,
            on_animation_end=on_animation_end,
            tooltip=tooltip,
            visible=visible,
            disabled=disabled,
            data=data,
        )

        self.columns = columns
        self.rows = rows
        self.border = border
        self.border_radius = border_radius
        self.horizontal_lines = horizontal_lines
        self.vertical_lines = vertical_lines
        self.bgcolor = bgcolor
        self.gradient = gradient
        self.divider_thickness = divider_thickness
        self.checkbox_horizontal_margin = checkbox_horizontal_margin
        self.column_spacing = column_spacing
        self.data_row_color = data_row_color
        self.data_row_min_height = data_row_min_height
        self.data_row_max_height = data_row_max_height
        self.data_text_style = data_text_style
        self.heading_row_color = heading_row_color
        self.heading_row_height = heading_row_height
        self.heading_text_style = heading_text_style
        self.horizontal_margin = horizontal_margin
        self.show_bottom_border = show_bottom_border
        self.show_checkbox_column = show_checkbox_column
        self.sort_ascending = sort_ascending
        self.sort_column_index = sort_column_index
        self.on_select_all = on_select_all

    def _get_control_name(self):
        return "datatable"

    def _before_build_command(self):
        super()._before_build_command()
        self._set_attr_json("border", self.__border)
        self._set_attr_json("gradient", self.__gradient)
        self._set_attr_json("borderRadius", self.__border_radius)
        self._set_attr_json("horizontalLines", self.__horizontal_lines)
        self._set_attr_json("verticalLines", self.__vertical_lines)
        self._set_attr_json("dataRowColor", self.__data_row_color)
        self._set_attr_json("headingRowColor", self.__heading_row_color)
        self._set_attr_json("dataTextStyle", self.__data_text_style)
        self._set_attr_json("headingTextStyle", self.__heading_text_style)

    def _get_children(self):
        children = []
        children.extend(self.__columns)
        children.extend(self.__rows)
        return children

    # columns
    @property
    def columns(self):
        return self.__columns

    @columns.setter
    def columns(self, value: Optional[List[DataColumn]]):
        self.__columns = value if value is not None else []

    # rows
    @property
    def rows(self):
        return self.__rows

    @rows.setter
    def rows(self, value: Optional[List[DataRow]]):
        self.__rows = value if value is not None else []

    # border
    @property
    def border(self) -> Optional[Border]:
        return self.__border

    @border.setter
    def border(self, value: Optional[Border]):
        self.__border = value

    # border_radius
    @property
    def border_radius(self) -> BorderRadiusValue:
        return self.__border_radius

    @border_radius.setter
    def border_radius(self, value: BorderRadiusValue):
        self.__border_radius = value

    # horizontal_lines
    @property
    def horizontal_lines(self) -> Optional[BorderSide]:
        return self.__horizontal_lines

    @horizontal_lines.setter
    def horizontal_lines(self, value: Optional[BorderSide]):
        self.__horizontal_lines = value

    # vertical_lines
    @property
    def vertical_lines(self) -> Optional[BorderSide]:
        return self.__vertical_lines

    @vertical_lines.setter
    def vertical_lines(self, value: Optional[BorderSide]):
        self.__vertical_lines = value

    # checkbox_horizontal_margin
    @property
    def checkbox_horizontal_margin(self) -> OptionalNumber:
        return self._get_attr("checkboxHorizontalMargin")

    @checkbox_horizontal_margin.setter
    def checkbox_horizontal_margin(self, value: OptionalNumber):
        self._set_attr("checkboxHorizontalMargin", value)

    # column_spacing
    @property
    def column_spacing(self) -> OptionalNumber:
        return self._get_attr("columnSpacing")

    @column_spacing.setter
    def column_spacing(self, value: OptionalNumber):
        self._set_attr("columnSpacing", value)

    # divider_thickness
    @property
    def divider_thickness(self) -> OptionalNumber:
        return self._get_attr("dividerThickness", data_type="float", def_value=1.0)

    @divider_thickness.setter
    def divider_thickness(self, value: OptionalNumber):
        self._set_attr("dividerThickness", value)

    # horizontal_margin
    @property
    def horizontal_margin(self) -> OptionalNumber:
        return self._get_attr("horizontalMargin")

    @horizontal_margin.setter
    def horizontal_margin(self, value: OptionalNumber):
        self._set_attr("horizontalMargin", value)

    # data_row_color
    @property
    def data_row_color(self) -> Union[None, str, Dict[MaterialState, str]]:
        return self.__data_row_color

    @data_row_color.setter
    def data_row_color(self, value: Union[None, str, Dict[MaterialState, str]]):
        self.__data_row_color = value

    # data_row_min_height
    @property
    def data_row_min_height(self) -> OptionalNumber:
        return self._get_attr("dataRowMinHeight")

    @data_row_min_height.setter
    def data_row_min_height(self, value: OptionalNumber):
        self._set_attr("dataRowMinHeight", value)

    # data_row_max_height
    @property
    def data_row_max_height(self) -> OptionalNumber:
        return self._get_attr("dataRowMaxHeight")

    @data_row_max_height.setter
    def data_row_max_height(self, value: OptionalNumber):
        self._set_attr("dataRowMaxHeight", value)

    # data_text_style
    @property
    def data_text_style(self):
        return self.__data_text_style

    @data_text_style.setter
    def data_text_style(self, value: Optional[TextStyle]):
        self.__data_text_style = value

    # bgcolor
    @property
    def bgcolor(self):
        return self._get_attr("bgColor")

    @bgcolor.setter
    def bgcolor(self, value):
        self._set_attr("bgColor", value)

    # gradient
    @property
    def gradient(self) -> Optional[Gradient]:
        return self.__gradient

    @gradient.setter
    def gradient(self, value: Optional[Gradient]):
        self.__gradient = value

    # heading_row_color
    @property
    def heading_row_color(self) -> Union[None, str, Dict[MaterialState, str]]:
        return self.__heading_row_color

    @heading_row_color.setter
    def heading_row_color(self, value: Union[None, str, Dict[MaterialState, str]]):
        self.__heading_row_color = value

    # heading_row_height
    @property
    def heading_row_height(self) -> OptionalNumber:
        return self._get_attr("headingRowHeight")

    @heading_row_height.setter
    def heading_row_height(self, value: OptionalNumber):
        self._set_attr("headingRowHeight", value)

    # heading_text_style
    @property
    def heading_text_style(self):
        return self.__heading_text_style

    @heading_text_style.setter
    def heading_text_style(self, value: Optional[TextStyle]):
        self.__heading_text_style = value

    # show_bottom_border
    @property
    def show_bottom_border(self) -> Optional[bool]:
        return self._get_attr("showBottomBorder", data_type="bool", def_value=False)

    @show_bottom_border.setter
    def show_bottom_border(self, value: Optional[bool]):
        self._set_attr("showBottomBorder", value)

    # show_checkbox_column
    @property
    def show_checkbox_column(self) -> Optional[bool]:
        return self._get_attr("showCheckboxColumn", data_type="bool", def_value=False)

    @show_checkbox_column.setter
    def show_checkbox_column(self, value: Optional[bool]):
        self._set_attr("showCheckboxColumn", value)

    # sort_ascending
    @property
    def sort_ascending(self) -> Optional[bool]:
        return self._get_attr("sortAscending", data_type="bool", def_value=False)

    @sort_ascending.setter
    def sort_ascending(self, value: Optional[bool]):
        self._set_attr("sortAscending", value)

    # sort_column_index
    @property
    def sort_column_index(self) -> Optional[int]:
        return self._get_attr("sortColumnIndex")

    @sort_column_index.setter
    def sort_column_index(self, value: Optional[int]):
        self._set_attr("sortColumnIndex", value)

    # on_select_all
    @property
    def on_select_all(self):
        return self._get_event_handler("select_all")

    @on_select_all.setter
    def on_select_all(self, handler):
        self._add_event_handler("select_all", handler)
        self._set_attr("onSelectAll", True if handler is not None else None)

class Item(Control):
    def __init__(self, obj):
        Control.__init__(self)
        assert obj, "obj cannot be empty"
        self.obj = obj

    def _set_attr(self, name, value, dirty=True):
        if value is None:
            return

        orig_val = self._get_attr(name)
        if orig_val is not None:
            if isinstance(orig_val, bool):
                value = str(value).lower() == "true"
            elif isinstance(orig_val, float):
                value = float(str(value))

        self._set_attr_internal(name, value, dirty=False)
        if isinstance(self.obj, dict):
            self.obj[name] = value
        else:
            setattr(self.obj, name, value)

    def _fetch_attrs(self):
        # reflection
        obj = self.obj if isinstance(self.obj, dict) else vars(self.obj)

        for name, val in obj.items():
            data_type = (
                type(val).__name__ if isinstance(val, (bool, float)) else "string"
            )
            orig_val = self._get_attr(name, data_type=data_type)

            if val != orig_val:
                self._set_attr_internal(name, val, dirty=True)

    def _get_control_name(self):
        return "item"


# -*- coding:utf-8 -*-

from functools import partial
import httplib

from libcloud.loadbalancer import providers

from libcloud_rest.api.handlers import ServiceHandler, invoke_method,\
    invoke_extension_method, list_providers, provider_info
from libcloud_rest.utils import json

invoke_method = partial(invoke_method, providers)

lb_handler = ServiceHandler('/loadbalancer/')

lb_handler.add_handlers([
    ('/providers', partial(list_providers, providers)),
    ('/providers/<string:provider_name>',
        partial(provider_info, providers)),
    ('/<string:provider>/<string:method_name>',
        partial(invoke_extension_method, providers), ['POST']),
    ('/<string:provider>/algorithms',
        partial(invoke_method, 'list_supported_algorithms')),
    ('/<string:provider>/balancers',
        partial(invoke_method, 'list_balancers')),
    ('/<string:provider>/protocols',
        partial(invoke_method, 'list_protocols')),
    ('/<string:provider>/locations', partial(invoke_method, 'list_locations'))
])

@lb_handler.handler('/<string:provider>/balancers', methods=['POST'])
def create_balancer(request):
    """
    Invoke create_balancer method and patch response.

    @return: Response object with newly created balancer ID in Location.
    """
    response = invoke_method('create_balancer', request)
    balancer_id = json.loads(response.data)['id']
    response.autocorrect_location_header = False
    response.headers.add_header('Location', balancer_id)
    response.status_code = httplib.CREATED
    return response

@lb_handler.handler('/<string:provider>/balancers/<string:balancer_id>')
def get_balancer(request):
    data = json.dumps({'balancer_id': request.args['balancer_id']})
    return invoke_method('get_balancer', request, data=data)

@lb_handler.handler('/<string:provider>/balancers/<string:loadbalancer_id>',
                    methods=['PUT'])
def update_balancer(request):
    json_data = json.loads(request.data)
    json_data['loadbalancer_id'] = request.args['loadbalancer_id']
    return invoke_method('update_balancer', request,
                         data=json.dumps(json_data))

@lb_handler.handler('/<string:provider>/balancers/<string:loadbalancer_id>',
                    methods=['DELETE'])
def destroy_balancer(request):
    """
    Get balancer id from params and invoke destroy_balancer driver method.
    @return: Empty response body
    """
    json_data = {'loadbalancer_id': request.args['loadbalancer_id']}
    return invoke_method('destroy_balancer', request,
                         data=json.dumps(json_data),
                         status_code=httplib.ACCEPTED)

@lb_handler.handler('/<string:provider>/balancers/<string:lb_id>/members',
                    methods=['POST'])
def create_member(request):
    json_data = json.loads(request.data)
    json_data['loadbalancer_id'] = request.args['lb_id']
    return invoke_method('balancer_attach_member', request,
                         data=json.dumps(json_data))

@lb_handler.handler('/<string:provider>/balancers/<string:lb_id>/members')
def list_members(request):
    json_data = json.loads(request.data)
    json_data['loadbalancer_id'] = request.args['lb_id']
    return invoke_method('balancer_list_members', request,
                         data=json.dumps(json_data))

@lb_handler.handler(
    '/<string:provider>/balancers/<string:lb_id>/members/<string:member_id>',
    methods=['DELETE'])
def delete_member(request):
    """
    @return:This operation does not return a response body.
    """
    json_data = {'loadbalancer_id': request.args['lb_id'],
                 'member_id': request.args['member_id']}
    return invoke_method('balancer_detach_member', request,
                         data=json.dumps(json_data),
                         status_code=httplib.ACCEPTED)


#!/usr/bin/env python
# encoding: utf-8

import logging
from sanic import  Blueprint, response
from sanic.response import json, text, html, redirect
from sanic_openapi import doc
import ujson
from common.utils import jsonify, insert_sql, select_sql, update_sql
import datetime
from bs4 import  BeautifulSoup
import ujson
import pickle
import re
import hashlib
import xml.etree.ElementTree as ET
import datetime
import time
import redis

logger = logging.getLogger('sanic')
youdao_bp = Blueprint('youdao', url_prefix='words')
review_time_list = {
    '0': datetime.timedelta(minutes=5),
    '1': datetime.timedelta(hours=1),
    '2': datetime.timedelta(hours=3),
    '3': datetime.timedelta(hours=6),
    '4': datetime.timedelta(hours=12),
    '5': datetime.timedelta(days=1),
    '6': datetime.timedelta(days=2),
    '7': datetime.timedelta(days=3),
    '8': datetime.timedelta(days=5),
    '9': datetime.timedelta(days=10),
    '10': datetime.timedelta(days=20),
    '11': datetime.timedelta(days=30)
}

async def create_audio(data):
    timestamp = datetime.datetime.timestamp(datetime.datetime.now())
    #主要是懒,@todo写道config.py里面
    path = 'audio/'+str(timestamp*10000).split('.')[0]+'.wav'
    fs = open(path, 'wb')
    fs.write(data)
    fs.close()
    return path

@youdao_bp.route('/audios/<word:string>', methods=['GET'])
async def get_word_audio(request, word):
    records = None
    async with request.app.db.acquire() as cur:
        records = await cur.fetch("select * from word_audio where word = '%s'"%word)
    if records:
        audio_path = records[0]['audio_path']
        async def streaming_fn(response):
            fs = open(audio_path,'rb')
            data = fs.read()
            response.write(data)
        return response.stream(streaming_fn,content_type='audio/mpeg')
    else:
        cli = request.app.client.cli
        #主要是懒,@todo写道config.py里面
        url = "http://dict.youdao.com/dictvoice?audio=" + word
        async with cli.get(url) as res:
            if res.status == 200:
                data = await res.read()
                path = await create_audio(data)
                async with request.app.db as cur:
                     await cur.fetch("INSERT INTO word_audio (create_time,word,audio_path) VALUES (current_timestamp,'%s','%s')" %(word,path))
                async def streaming_fn(response):
                    response.write(data)
                return response.stream(streaming_fn,content_type='audio/mpeg')
    return json({},status = 400)

@youdao_bp.route('/translates/<word:string>', methods=['GET'])
async def get_word_translate(request, word):
    records = None
    async with request.app.db.acquire() as cur:
        records = await cur.fetch("select * from word_translate where word='%s'"%word)
    if records:
        res = ujson.loads(records[0]['translate'])
        return json(res)
    else:
        cli = request.app.client.cli
        #直接写url主要是懒
        #主要是懒,@todo写道config.py里面
        url = "http://dict.youdao.com/search?keyfrom=dict.top&q="+word
        async with cli.get(url) as res:
            if res.status == 200:
                data = await res.text()
                res = await parse_html(request,data,word)
                async with request.app.db as cur:
                    #return json(res)
                    json_data = ujson.dumps(res)
                    json_data = json_data.replace("'","''")
                    await cur.fetch( """INSERT INTO word_translate (create_time,word,translate) VALUES (current_timestamp,'%s','%s')"""%(word,json_data))
                    #await cur.fetch( """INSERT INTO word_translate (create_time,word,translate) VALUES (current_timestamp,'%s',)"""%(word,ujson.dumps(res)))
                    return json(res)
    return json({},status = 400)

@youdao_bp.route('/search/<word:string>', methods=['GET'])
async def get_word(request, word):
    res = await get_word_translate(request, word)
    audio = await get_word_audio(request, word)
    return res

@youdao_bp.route('/users',methods=['POST'])
async def add_user(request,fromu=None):
    data = {}
    try:
        if not fromu:
            data = ujson.loads(request.body)
        else:
            data = {"username":fromu}
    except:
        return json({},status = 400)
    if data.get('username') and len(data.get('username'))>5 :
        async with request.app.db.acquire() as cur:
            records = await cur.fetch("select * from users where username='%s'"%data.get('username'))
            if records:
                request['session']['user_id'] = records[0]['id']
                return json(jsonify(records)[0])
            else:
                sql = """insert into users (create_time,username,name,wechat_user) values
                (current_timestamp,'%s','%s','%s') returning *"""
                user_info = (data.get('username'),data.get('name') if data.get('name') else data.get('username'),data.get('wechat_user') if data.get('wechat_user') else 'NULL')
                records = await cur.fetch(sql%user_info)
                print(records)
                if records:
                    request['session']['user_id'] = records[0]['id']
                res = await add_wordlist(request,data={"name":"新手起步"})
                if res:
                    wordlist_id = ujson.loads(res.body)['id']
                    r = await add_wordlists_word(request,wordlist_id,data={"word":"hello"})
                    return(r.body)
                if records:
                    return json(jsonify(records)[0])
    return json({},status = 400)

@youdao_bp.route('/wordlists/wechat/<wechat_token>', methods=['GET'])
async def get_wechat_word_h5(request,wechat_token):
    res = await add_user(request,wechat_token)
    wordlist_id = -1
    if res:
        await update_wechat_user(request,wechat_user=wechat_token)
        async with request.app.db.acquire() as cur:
            record = await cur.fetch("select * from word_list where user_id=%s"%request['session']['user_id'])
            if record:
                wordlist_id = record[0]['id']
    return redirect('http://bitnotice.susnote.com/html/WordLists.html')

@youdao_bp.route('/users/wechat_user',methods=['POST'])
async def update_wechat_user(request,user_id=None,wechat_user=None):
    data = {}
    try:
        data = ujson.loads(request.body)
    except:
        pass
    if not wechat_user:
        wechat_user = data.get('wechat_user')
    if not user_id:
        user_id = data.get('user_id') if data.get('user_id') else request['session'].get('user_id')

    if wechat_user and user_id:
        async with request.app.db as cur:
            sql = """update users set wechat_user=%s where id=%s returning *"""%(wechat_user,user_id)
            records = cur.fetch(sql)
            return json({})
    return json({},status=400)

@youdao_bp.route('/wordlists/<wordlist_id:int>/words', methods=['GET'])
async def get_wordlists_word(request,wordlist_id):
    user_id = None
    if request['session'].get('user_id'):
        user_id = request['session'].get('user_id')
    if user_id:
        async with request.app.db.acquire() as cur:
            records = await cur.fetch("select * from word_list where id=%s and user_id=%s"%(wordlist_id,user_id))
            if records:
                return json(jsonify(records)[0]["words"])
    return json({},status=400)

@youdao_bp.route('/wordlists/<wordlist_id:int>/words', methods=['POST'])
async def add_wordlists_word(request,wordlist_id,data={}):
    user_id = None
    try:
        if not data:
            data = ujson.loads(request.body)
    except:
        return json({},status=400)
    if request['session'].get('user_id'):
        user_id = request['session'].get('user_id')
    if user_id and data.get('word'):
        async with request.app.db as cur:
            records = await cur.fetch("select * from word_list where id=%s and user_id=%s"%(wordlist_id,user_id))
            if records:
                words = records[0]['words']
                if data.get('word') not in words:
                    words.append(data.get('word'))
                    words = str(list(words)).replace('[','{').replace(']','}').replace("'",'"')
                    records = await cur.fetch("""update word_list set words='%s' where id=%s returning *"""%(words,wordlist_id))
                    r = await cur.fetch("""insert into user_words (create_time,word,words_level,user_id,review_time, word_list_id) values (current_timestamp,'%s',%s,%s,current_timestamp,%s) returning *"""%(data.get('word'),0,user_id,wordlist_id))
                    print(r)
                    if records:
                        return json(jsonify(records)[0])
    return json({})

@youdao_bp.route('/wordlists', methods=['GET'])
async def get_wordlists(request):
    wordlist_id = request.args.get('id', 0)
    user_id = None
    if request['session'].get('user_id'):
        user_id = request['session'].get('user_id')
    if user_id and wordlist_id ==0:
        async with request.app.db.acquire() as cur:
            records = await cur.fetch("select * from word_list where user_id=%s"%(user_id))
            return json(jsonify(records))
    elif user_id:
        async with request.app.db.acquire() as cur:
            records = await cur.fetch("select * from word_list where user_id=%s and id =%s"%(user_id,wordlist_id))
            return json(jsonify(records))

    return json({},status=400)

@youdao_bp.route('/wordlists', methods=['DELETE'])
async def delete_wordlists(request,data={}):
    try:
        if not data:
            data = ujson.loads(request.body)
    except:
        return json({},status=400)
    wordlist_name = data.get("name").replace("'","''") if data.get("name") else "未命名"
    user_id = data.get("user_id") if data.get("user_id") else request['session'].get('user_id')
    if user_id:
        async with request.app.db as cur:
            r = await cur.fetch("select * from word_list where name='%s' and user_id=%s"%(wordlist_name,user_id))
            if r:
                wordlist_id = r[0]['id']
                await cur.fetch("delete from user_words where word_list_id=%s"%wordlist_id)
                res = await cur.fetch("delete from word_list where name='%s' and user_id=%s returning *"%(wordlist_name,user_id))
                return json(jsonify(r))
    return json({},status=400)

@youdao_bp.route('/wordlists', methods=['POST'])
async def add_wordlist(request,data={}):
    try:
        if not data:
            data = ujson.loads(request.body)
    except:
        return json({},status=400)
    wordlist_name = data.get("name").replace("'","''") if data.get("name") else "未命名"
    user_id = data.get("user_id") if data.get("user_id") else request['session'].get('user_id')
    if user_id:
        async with request.app.db as cur:
            r = await cur.fetch("select * from word_list where name='%s' and user_id=%s"%(wordlist_name,user_id))
            if r:
                return json(jsonify(r)[0])
            else:
                records = await cur.fetch("insert into word_list (create_time,words,name,category,user_id) VALUES (current_timestamp,'{}','%s','private',%s) returning *"%(wordlist_name,user_id))
            if records:
                return json(jsonify(records)[0])
    return json({},status=400)

@youdao_bp.route('/wordlists/<wordlist_id:int>', methods=['DELETE'])
async def delete_wordlist(request,wordlist_id):
    user_id = request['session'].get('user_id')
    if user_id:
        async with request.app.db as cur:
            records = await cur.fetch("delete from word_list where user_id=%s and id=%s returning *"%(user_id,wordlist_id))
            if records:
                return json(jsonify(records)[0])
    return json({},status=400)

@youdao_bp.route('/wordlists/<wordlist_id:int>/review/h5', methods=['GET'])
async def get_word_h5(request,wordlist_id):
    env = request.app.env
    template = await env.get_template('WordListReview.html').render_async(wordlist_id=wordlist_id)
    return html(template)

@youdao_bp.route('/wordlists/<wordlist_id:int>/h5', methods=['GET'])
async def get_word_h5(request,wordlist_id):
    env = request.app.env
    template = await env.get_template('WordList.html').render_async(wordlist_id=wordlist_id)
    return html(template)

async def parse_html(request,html,word):
    """
    解析web版有道的网页
    :param html:网页内容
    :return:result
    """
    soup = BeautifulSoup(html, "lxml")
    root = soup.find(id='results-contents')

    # query 搜索的关键字
    keyword = root.find(class_='keyword')
    result = {}
    if not keyword:
        result['query'] = word
    else:
        result['query'] = str(keyword.string)

    # 基本解释
    basic = root.find(id='phrsListTab')
    if basic:
        trans = basic.find(class_='trans-container')
        if trans:
            result['basic'] = {}
            result['basic']['explains'] = [str(tran.string) for tran in trans.find_all('li')]
            # 中文
            if len(result['basic']['explains']) == 0:
                exp = trans.find(class_='wordGroup').stripped_strings
                result['basic']['explains'].append(' '.join(exp))

                # 音标
            phons = basic(class_='phonetic', limit=2)
            if len(phons) == 2:
                result['basic']['uk-phonetic'], result['basic']['us-phonetic'] = \
                    [str(p.string)[1:-1] for p in phons]
            elif len(phons) == 1:
                result['basic']['phonetic'] = str(phons[0].string)[1:-1]

    # 翻译
    if 'basic' not in result:
        result['translation'] = await get_translation(request, word)

    # 网络释义(短语)
    web = root.find(id='webPhrase')
    if web:
        result['web'] = [
            {
                'key': str(wordgroup.find(class_='search-js').string).strip(),
                'value': [v.strip() for v in str(wordgroup.find('span').next_sibling).split(';')]
            } for wordgroup in web.find_all(class_='wordGroup', limit=4)
        ]
    return result

async def get_translation(request, word):
        """
        通过web版有道翻译抓取翻译结果
        :param word:str 关键字
        :return:list 翻译结果
        """
        cli = request.app.client.cli
        url = 'http://fanyi.youdao.com/translate?keyfrom=dict.top&i='+word

        async with cli.get(url) as res:
            if res.status == 200:
                data = await res.text()
                pattern = re.compile(r'"translateResult":\[(\[.+\])\]')
                m = pattern.search(data)
                result = ujson.loads(m.group(1))

        return [item['tgt'] for item in result]

@youdao_bp.route('/wechat/login', methods=['GET','POST'])
async def wechat_auth(request):
    if request.method == 'GET':
        token = 'WXokChinaCoinJJ'
        data = request.args
        signature = data.get('signature','')
        timestamp = data.get('timestamp','')
        nonce = data.get('nonce','')
        echostr = data.get('echostr','')
        s = [timestamp,nonce,token]
        s.sort()
        s = ''.join(s)
        s = s.encode('utf-8')
        if (hashlib.sha1(s).hexdigest() == signature):
            return html(echostr)
    else:
        rec = request.body
        xml_rec = ET.fromstring(rec)
        tou = xml_rec.find('ToUserName').text
        fromu = xml_rec.find('FromUserName').text
        content = xml_rec.find('Content').text
        if content in ["背单词","生词本"]:
            content = "开始吧"%fromu
        xml_rep = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[%s]]></Content><FuncFlag>0</FuncFlag></xml>"

        response = html(xml_rep % (fromu,tou,str(int(time.time())), content))
        response.content_type = 'application/xml'
        return response

    return 'Hello weixin!'

@youdao_bp.route('/wordlists/<word_list_id:int>/review', methods=['GET'])
async def get_word_level(request,word_list_id):
    user_id = request['session'].get('user_id')
    words = []
    current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    async with request.app.db.acquire() as cur:
        records = await cur.fetch("""select word from user_words where word_list_id=%s and review_time< '%s'"""%(word_list_id,current_time))
        for record in jsonify(records):
            words.append(record['word'])
        return json(words)
    return json([])

@youdao_bp.route('/wordlists/<word_list_id:int>/review', methods=['PUT'])
async def update_word_level(request,word_list_id,words=None):
    user_id = request['session'].get('user_id')
    result = []
    if not words:
        words = ujson.loads(request.body)
    for word in words:
        word_text = word['word']
        async with request.app.db.acquire() as cur:
            record = await cur.fetch("""select words_level from user_words where word='%s' and user_id=%s and word_list_id=%s"""%(word_text,user_id,word_list_id))
            level = record[0]["words_level"]
        state = word['state']
        if state == 'remember':
            level = level+1 if level<=11 else level
            review_time = datetime.datetime.now() + review_time_list[str(level)]
            async with request.app.db.acquire() as cur:
                r = await cur.fetch("""update user_words set words_level = %s,review_time= '%s' where user_id=%s and word='%s' returning *"""%(level,review_time.strftime('%Y-%m-%d %H:%M:%S'),user_id,word_text))
        elif state == 'confuse':
            review_time = datetime.datetime.now()
            async with request.app.db.acquire() as cur:
                r = await cur.fetch("""update user_words set words_level = %s,review_time= '%s' where user_id=%s and word='%s' returning *"""%(level,review_time.strftime('%Y-%m-%d %H:%M:%S'),user_id,word_text))
        elif state == 'not remember':
            level = 0
            async with request.app.db.acquire() as cur:
                r = await cur.fetch("""update user_words set words_level = %s where user_id=%s and word='%s' returning *"""%(level,user_id,word_text))
        result.append(jsonify(r))
    return json(result)