import numpy as np
import sympy as sp
from Entity import Line, Point, DottedLine
from Geometry import Geometry
from Turret import Turret
from utils.coordination_calculator import *

class CameraTurret(Turret):
    def __init__(self, targets = None, q1=0, q2=0, pOffset=np.array([-10, 0, 0]).reshape(3, 1),
                 p12=np.array([0, 74, 60]).reshape(3, 1),
                 orig=np.array([100, 50, 0]).reshape(3, 1)):
        super().__init__(q1, q2, pOffset, p12, orig)
        self.targets = targets
        self.num_q = 2

        # Only need to run initSimCamera for simulations
        self.initSimCamera()

    def initSimCamera(self):
        self.focal_length = 100
        self.view_angle = 0.959931

    def getEntities(self):
        R01 = zRot(self.q1_given)
        R12 = yRot(self.q2_given)

        p_1 = self.orig + R01 @ self.p12
        p_2 = p_1 + R01 @ R12 @ self.pOffset

        # p_1alpha, p_1beta, p_1gamma are for visualization purposes only
        p_1alpha = self.orig + R01 @ np.array([0, 0, self.p12[2][0]]).reshape(3, 1)
        p_1beta = p_1alpha + R01 @ np.array([0, self.p12[1][0], 0]).reshape(3, 1)
        p_1gamma = p_1beta + R01 @ np.array([self.p12[0][0], 0, 0]).reshape(3, 1)

        #print(self.targetsInView())

        # Visualization of camera plane
        camera_plane_entities = self.cameraPlaneBounds(p_2)

        #target_p = self.orig if self.targets is None else self.targets[0].pos
        lens_pos = self.representTarget() + self.orig
        # target list can be changed to include only targets in view
        target_p = self.getTargetLinks(lens_pos, self.targets)
        target_rep = self.representTarget(target_p[0]) + self.orig

        return [Line(np.array([p_2[0][0], p_2[1][0], 0]).reshape(3, 1), p_2, "black", 2),
                Line(np.array([0, p_2[1][0], 0]).reshape(3, 1), np.array([p_2[0][0], p_2[1][0], 0]).reshape(3, 1),
                     "black", 2),
                Line(np.array([p_2[0][0], 0, 0]).reshape(3, 1), np.array([p_2[0][0], p_2[1][0], 0]).reshape(3, 1),
                     "black", 2),
                Line(p_1, p_2, "orange", 8),
                DottedLine(p_1, p_1 + 5*(p_2 - p_1), "black", 2, 10),
                Line(self.orig, p_1alpha, "orange", 8),
                Line(p_1alpha, p_1beta, "orange", 8),
                Line(p_1beta, p_1gamma, "orange", 8),
                DottedLine(self.orig, target_rep, "red", 2, 10),
                DottedLine(lens_pos, target_rep, "blue", 2, 10),
                Point(self.orig, 'black', 8),
                Point(p_1, 'black', 8),
                Point(p_2, 'black', 8),
                ] +  camera_plane_entities

    # Function only relevant for simulations, so take focal length and view angle as parameters
    def cameraPlaneBounds(self, p_2):
        # Typical webcam view angles between 55 (0.959931 rad) and 65 degrees (1.13446 rad)
        R01 = zRot(self.q1_given)
        R12 = yRot(self.q2_given)

        corner_list = []
        corner_list.append(p_2 + R01 @ R12 @ np.array([-self.focal_length,
                                                       self.focal_length * np.tan(self.view_angle/2),
                                                       self.focal_length * np.tan(self.view_angle/2)]).reshape(3, 1))
        corner_list.append(p_2 + R01 @ R12 @ np.array([-self.focal_length,
                                                       -self.focal_length * np.tan(self.view_angle / 2),
                                                       self.focal_length * np.tan(self.view_angle / 2)]).reshape(3, 1))
        corner_list.append(p_2 + R01 @ R12 @ np.array([-self.focal_length,
                                                      -self.focal_length * np.tan(self.view_angle / 2),
                                                       -self.focal_length * np.tan(self.view_angle / 2)]).reshape(3, 1))
        corner_list.append(p_2 + R01 @ R12 @ np.array([-self.focal_length,
                                                       self.focal_length * np.tan(self.view_angle / 2),
                                                       -self.focal_length * np.tan(self.view_angle / 2)]).reshape(3, 1))

        entities = []
        for i in range(len(corner_list)):
            entities.append(Line(corner_list[i], p_2, "yellow", 2))
            if i < len(corner_list) - 1:
                entities.append(Line(corner_list[i], corner_list[i+1], "yellow", 2))
            else:
                entities.append(Line(corner_list[i], corner_list[0], "yellow", 2))

        return entities

    def targetsInView(self):
        # Check all given targets and return a list of targets that are in view
        # Compute getTargetLinks
        # Compute angle between x/y and z axes and check if they lie within view range
        R01 = zRot(self.q1_given)
        R12 = yRot(self.q2_given)

        p_1 = self.orig + R01 @ self.p12
        p_2 = p_1 + R01 @ R12 @ self.pOffset

        t_links = self.getTargetLinks(p_2, self.targets)
        in_view = dict()

        for t_link, target in zip(t_links, self.targets):
            tvec = zRot(np.pi) @ yRot(np.pi/2) @ t_link
            if tvec[2][0] < 0:
                continue
            if abs(np.arctan2(tvec[0][0], tvec[2][0])) < self.view_angle/2 and \
                    abs(np.arctan2(tvec[1][0], tvec[2][0])) < self.view_angle/2:
                in_view[target.getId()] = target

        return in_view

    def getTargetLinks(self, lens_pos, targets):
        t_links = []
        for target in targets:
            # Simulate tvecs coming from Aruco by representing distances between
            # the camera lens and each target in the camera lens' frame
            c_t = target.pos - lens_pos

            # (c_t)c = R20(c_t)o
            R01 = zRot(self.q1_given)
            R12 = yRot(self.q2_given)

            R02 = R01 @ R12
            R20 = R02.transpose()
            c_tc = R20 @ c_t

            # Set axes relative to Aruco camera axis
            # Produces a 'tvec', a pinhole camera translation vector given by Aruco
            #c_tc = zRot(np.pi) @ yRot(np.pi/2) @ c_tc

            # TODO Rotate axes back to camera frame.
            # Rotates tvec to camera frame. Final offset sent to POE

            t_links.append(c_tc)

        return t_links

    def representTarget(self, distance=np.array([0, 0, 0]).reshape(3, 1)):
        # Define POE parameters
        # h1 = [0, 0, 1]T or ez
        # h2 = [1, 0, 0]T or ex
        # p01 = [0, 0, 0]T
        # p12 = self.p12, default case [0, 0, 75]T
        # p2T = self.pOffset + {target vector}
        p01 = np.array([0, 0, 0]).reshape(3, 1)
        p2T = self.pOffset + distance

        # Define rotation matrices
        # e^(h1xq1) = Rz(q1)
        # e^(h2xq2) = Rx(q2)
        R01 = zRot(self.q1_given)
        R12 = yRot(self.q2_given)

        # Define HTMS
        # T01 = |R01    p01|
        #       |0      1  |
        T01 = np.identity(4)
        T01[0:3, 0:3] = R01
        T01[0:3, 3:4] = p01

        # T12 = |R12    p12|
        #       |0      1  |
        T12 = np.identity(4)
        T12[0:3, 0:3] = R12
        T12[0:3, 3:4] = self.p12

        # T2T = |I      p2T|
        #       |0      1  |
        T2T = np.identity(4)
        T2T[0:3, 3:4] = p2T

        target = T01 @ T12 @ T2T

        return target[0:3, 3:4]

    # Returns a path matrix representing the entire sweep
    # sinusoidal sweep
    # in position space: y = 100*sin(pi*z/4)
    # in configuration space: q1 = a*sin(b*(q2-c))+d
    # (0, 0) is camera turret at zero configuration
    # increasing t_elapse decreases speed of camera
    def sweepPath(self, t_elapse, time_step = 0.1, q1_range = (-np.pi, np.pi), q2_range = (-np.pi, np.pi)):
        r1 = getRange(q1_range)
        r2 = getRange(q2_range)
        a = r1
        b = 2.5 * np.pi / r2
        c = q2_range[1] - r2
        d = q1_range[1] - r1

        # Find number of time steps and allocate path matrix at size
        num_steps = np.ceil(t_elapse / time_step).astype('int')
        period = 2 * r2 / num_steps
        t = q2_range[0]   # t initialized to pi because of zero configuration offest from yz-plane
        q_steps = np.zeros([self.num_q+1, num_steps+1])

        for i in range(num_steps+1):
            # parametrize path to q2 = t, q1 = a*sin(b*(t-c))+d with zero_config offset
            q_step = np.array([(a*np.sin(b*(t-c))+d), t]).reshape(2, 1)
            q_steps[0, i:i+1] = (i * time_step)
            q_steps[1:3,i:i+1] = q_step
            t += period

        return q_steps

    def inverseKin(self, targetPos):
        # MATLAB code from Robotics class used as reference

        # Solve for both q1 and q2 using subproblem 2
        # P0T = R01 @ P12 + R01 @ R12 @ (P23 + P3T)     --- use this
        # R01^T @ P0T = P12 + R12 @ (P23 + P3T)
        # k1 = -ez, p1 = p0T - p12 = p2T_target, k2 = ey, p2 = p2T

        R01T = zRot_s(-self.q1)
        R12 = yRot_s(self.q2)

        # self.pOffset = p2T where T is at end effector of turret
        p2T_f = targetPos - self.p12
        p2T_f = p2T_f / np.linalg.norm(p2T_f)
        p2T_f = p2T_f * np.linalg.norm(self.pOffset)
        print(p2T_f / np.linalg.norm(p2T_f))

        #  0 -1  0
        #  0  0  1
        # -1  0  0

        pk1 = -p2T_f[2][0]
        pk2 = -self.pOffset[1][0]
        a = np.array([pk1, pk2]).reshape(2, 1)
        cond = np.linalg.norm(p2T_f)**2 - np.linalg.norm(a)**2
        v1 = np.array([cond**0.5, pk2, -pk1]).reshape(3, 1)

        pp11 = p2T_f - np.array([0, 0, -pk1]).reshape(3, 1)
        pp21 = v1 - np.array([0, 0, -pk1]).reshape(3, 1)
        pp12 = self.pOffset - np.array([0, pk2, 0]).reshape(3, 1)
        pp22 = v1 - np.array([0, pk2, 0]).reshape(3, 1)

        numer1 = (pp11 / np.linalg.norm(pp11)) - (pp21 / np.linalg.norm(pp21))
        denom1 = (pp11 / np.linalg.norm(pp11)) + (pp21 / np.linalg.norm(pp21))
        numer2 = (pp12 / np.linalg.norm(pp12)) - (pp22 / np.linalg.norm(pp22))
        denom2 = (pp12 / np.linalg.norm(pp12)) + (pp22 / np.linalg.norm(pp22))

        q1_sol = np.arctan2(np.linalg.norm(numer1), np.linalg.norm(denom1))
        q2_sol = np.arctan2(np.linalg.norm(numer2), np.linalg.norm(denom2))
        #print(q1_sol, q2_sol)

        return np.array([q1_sol, q2_sol]).reshape(2, 1)

# Helper function to get range of an angle
# r = (min, max)
def getRange(r):
    return (r[1] - r[0]) / 2.0

# for testing
if __name__ == "__main__":
    ct = CameraTurret()
    print(ct.sweepPath(10))


import numpy as np

class SGD():

    def __init__(self, X, y, beta=None, epochs=100, size_batch=1, method="OLS", lmbda=0.0):

        self.X = X
        self.y = y
        self.epochs = epochs

        N,P = X.shape
        self.N = N
        self.P = P

        self.size_batch = size_batch
        self.num_batches = int(N / size_batch)

        if beta is None:
            self.beta = np.random.randn(P, 1).ravel()
        else:
            self.beta = beta

        self.method = method

        # ridge
        self.lmbda = lmbda

    def learning_rate_decay(self, t, t0, t1):
        """Calculates the learning rate decay.

        Args:
            t (int): (epoch number) * (number of batches) + (batch number)
            t0 (int): t0 value
            t1 (int): t1 value
        """
        return t0 / (t + t1)

    def gradient(self, batch_indices, beta):
        """Calculating the gradient of the MSE cost function for OLS or Ridge regression.

        Args:
            batch_indices (ndarray): 1D array of indices to use from dataset
            beta (ndarray): 1D array of beta parameters
        """

        X_b = self.X[batch_indices]
        y_b = self.y[batch_indices]

        if self.method == "Ridge":
            return 2 * (X_b.T @ ((X_b @ beta) - y_b) + self.lmbda * beta)
        else:
            return (2/self.size_batch) * X_b.T @ ((X_b @ beta) - y_b)

    def fit(self, learning_rate=0.001):
        """Estimates the beta paramaters using standard SGD.
        Shuffles the dataset for each epoch.

        Args:
            learning_rate (float): learning rate, default=0.001
        """

        indices = np.arange(self.N)
        shuffler = np.random.default_rng()

        beta = self.beta
        for epoch in range(self.epochs):

            shuffler.shuffle(indices)
            for i in range(self.num_batches):
                batch_indices = np.random.choice(indices, self.size_batch, replace=True)
                beta = beta - learning_rate * self.gradient(batch_indices, beta)

        return beta.ravel()

    def fit_with_decay(self, t0=5, t1=50):
        """Estimates the beta paramaters using SGD with decaying learning rate.
        Shuffles the dataset for each epoch.

        Args:
            t0 (float): t0 value, default=5
            t0 (float): t0 value, default=50
        """

        indices = np.arange(self.N)
        shuffler = np.random.default_rng()

        beta = self.beta
        for epoch in range(self.epochs):

            shuffler.shuffle(indices)
            for i in range(self.num_batches):
                batch_indices = np.random.choice(indices, self.size_batch, replace=True)
                learning_rate = self.learning_rate_decay(epoch * self.num_batches + i, t0, t1)

                beta = beta - learning_rate * self.gradient(batch_indices, beta)

        return beta.ravel()

    def RMSprop(self, learning_rate=0.001, rho=0.9, eps=1e-8):
        """Estimates the beta paramaters using the RMSprop variant of SGD.
        Shuffles the dataset for each epoch.

        Source: http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf

        Args:
            learning_rate (float): learning rate, default=0.001
            rho (float): rho value, default=0.9
            eps (float): epsilon value, default=1e-8
        """

        indices = np.arange(self.N)
        shuffler = np.random.default_rng()

        gradient_squared = np.zeros(len(self.beta))
        beta = self.beta
        for epoch in range(self.epochs):

            shuffler.shuffle(indices)
            for i in range(self.num_batches):
                batch_indices = np.random.choice(indices, self.size_batch, replace=True)

                gradient = self.gradient(batch_indices, beta)
                gradient_squared = (rho * gradient_squared) + ((1 - rho) * gradient * gradient)

                beta = beta - learning_rate * gradient / np.sqrt(gradient_squared + eps)

        return beta.ravel()

    def ADAM(self, learning_rate=0.001, rho_1=0.9, rho_2=0.99, eps=1e-8):
        """Estimates the beta paramaters using the ADAM variant of SGD.
        Shuffles the dataset for each epoch.

        Source: https://arxiv.org/pdf/1412.6980v9.pdf

        Args:
            learning_rate (float): learning rate, default=0.001
            rho_1 (float): rho value, default=0.9
            rho_2 (float): rho value, default=0.99
            eps (float): epsilon value, default=1e-8
        """

        indices = np.arange(self.N)
        shuffler = np.random.default_rng()

        m = np.zeros(len(self.beta))
        s = np.zeros(len(self.beta))
        m_hat = np.zeros(len(self.beta))
        s_hat = np.zeros(len(self.beta))

        beta = self.beta
        for epoch in range(self.epochs):

            shuffler.shuffle(indices)
            for i in range(1, self.num_batches + 1):
                batch_indices = np.random.choice(indices, self.size_batch, replace=True)

                gradient = self.gradient(batch_indices, beta)
                m = (rho_1 * m) + ((1 - rho_1) * gradient)
                s = (rho_2 * s) + ((1 - rho_2) * gradient * gradient)

                m_hat = m / (1 - rho_1**i)
                s_hat = s / (1 - rho_2**i)

                beta = beta - learning_rate * (m_hat / (np.sqrt(s_hat) + eps))

        return beta.ravel()


#!/usr/bin/env python
#-*- coding: utf-8 -*-

from urllib.request import urlretrieve

url = "http://imgnews.naver.net/image/413/2017/07/13/0000052580_001_20170713163927829.jpg"
savename = "heungmin.jpg"

# 다운로드
urlretrieve(url, savename)

print("파일이 저장되었습니다.")


# rocket
import pygame

ROCKET_SIZE = (90, 90)

class Rocket:
    def __init__(self):
        rocket = pygame.image.load('resources/rocket.png').convert()
        self.rocket = pygame.transform.scale(rocket, ROCKET_SIZE)

    def draw(self, x, y, surface):
        surface.blit(self.rocket, (x, y))

if __name__ == '__main__':
    rrrr = Rocket()
    rrrr.draw(1, 2, 4)


# -*- coding: utf-8 -*-
# @Author  : llc
# @Time    : 2020/5/4 15:52
import importlib
import os
import re
import traceback

from flask_cors import CORS
from flask_openapi3 import Info
from flask_openapi3 import OpenAPI
from werkzeug.exceptions import HTTPException
from werkzeug.middleware.proxy_fix import ProxyFix

def init_exception(app: OpenAPI):
    from app.utils.exceptions import BaseAPIException, UnknownException

    @app.errorhandler(Exception)
    def handler(e):
        """处理全局异常"""
        if isinstance(e, BaseAPIException):
            return e
        elif isinstance(e, HTTPException):
            code = e.code
            message = e.description
            return BaseAPIException(code, message)
        else:
            print(traceback.format_exc())
            return UnknownException()

def auto_register_api(app: OpenAPI):
    """自动注册蓝图API
    自动寻找api文件夹中的APIBlueprint并完成注册
    """
    here = os.path.dirname(__file__)
    api_dir = os.path.join(here, "api")
    for root, dirs, files in os.walk(api_dir):
        for file in files:
            if file == "__init__.py":
                continue
            if not file.endswith(".py"):
                continue
            api_file = os.path.join(root, file)
            rule = re.split(r"src|.py", api_file)[1]
            api_route = ".".join(rule.split(os.sep)).strip(".")
            try:
                api = importlib.import_module(api_route)
                app.register_api(api.api)
            except AttributeError:
                print(f"模块 {api_route} 中没有api变量")
            except:
                traceback.print_exc()
                print(f"模块 {api_route} 自动注册错误")

def register_apis(app: OpenAPI):
    """注册API蓝图"""
    # from app.api.user import api as user_api
    # from app.api.admin import api as admin_api
    # from app.api.book import api as book_api
    # from app.api.file import api as file_api
    # from app.api.job import api as job_api
    # app.register_api(user_api)
    # app.register_api(admin_api)
    # app.register_api(book_api)
    # app.register_api(file_api)
    # app.register_api(job_api)
    auto_register_api(app)

def init_jwt(app: OpenAPI):
    """初始化JWT"""
    from app.utils.jwt_tools import jwt_manager
    jwt_manager.init_app(app)

def init_db(app: OpenAPI):
    """初始化数据库"""
    from app.model import db
    db.init_app(app)

def init_rq2(app: OpenAPI):
    """初始化rq2"""
    from app.rq import rq2
    rq2.init_app(app)

def create_app():
    from . import config
    # 创建Flask实例
    app = OpenAPI(
        __name__,
        info=Info(title=config.APP_NAME, version=config.APP_VERSION),
        security_schemes={
            "basic": {"type": "http", "scheme": "basic"},
            "jwt": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"}

        },
        doc_expansion="none",
    )
    # json 正确返回中文
    app.json.ensure_ascii = False
    # 使用真实IP
    app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)
    # 全局配置项
    app.config.from_object(config)
    # 初始化全局异常
    init_exception(app)
    # 跨域支持
    CORS(app)
    # 初始化JWT
    init_jwt(app)
    # 初始化数据库
    init_db(app)
    # 初始化rq2
    init_rq2(app)
    # 注册API蓝图
    register_apis(app)
    return app


from flask.views import MethodView
from flask import request, jsonify, abort
from datetime import datetime
from jsonschema import Draft4Validator
from jsonschema.exceptions import best_match
import uuid

from gift_card.models import GiftCard
from gift_card.schema import create_gift_cart_schema
from gift_card.templates import gift_card_obj, gift_card_objs
from store.models import Store
from store.decorators import token_required
from customer.models import Customer
from utils import paginated_results

CUSTOMER_NOT_FOUND = "CUSTOMER_NOT_FOUND"
MISSING_PARAMETERS = "MISSING_PARAMETERS"
GIFT_CARD_NOT_FOUND = "GIFT_CARD_NOT_FOUND"

class GiftCardAPI(MethodView):

    def __init__(self):
        self.PER_PAGE = 10
        if (request.method != 'GET' and request.method != 'DELETE') and not request.json:
            abort(400)

    @token_required
    def get(self, gift_card_id=None):
        """
        returns a gift_card object of list of gift_card objects
        """
        store = Store.objects.filter(app_id=request.headers.get('APP-ID'), deleted_at=None).first()

        if gift_card_id:
            gift_card = GiftCard.objects.filter(gift_card_id=gift_card_id).first()

            if gift_card is None:
                return jsonify({}), 404

            if gift_card.recipient_customer.store_id != store:
                return jsonify({}), 404

            response = {
                "result": "ok",
                "gift_card": gift_card_obj(gift_card)
            }
            return jsonify(response), 200
        else:
            gift_cards = GiftCard.objects()

            if "giftercustomerid" not in request.args and "recipientcustomerid" not in request.args:
                return jsonify({"error": MISSING_PARAMETERS}), 400

            if "giftercustomerid" in request.args:
                gifter_customer = Customer.objects.filter(customer_id=request.args['giftercustomerid'], store_id=store
                                                          , deleted_at=None).first()
                if gifter_customer is None:
                    return jsonify({"error": CUSTOMER_NOT_FOUND}), 404
                gift_cards = gift_cards.filter(gifter_customer=gifter_customer)

            if "recipientcustomerid" in request.args:
                recipient_customer = Customer.objects.filter(customer_id=request.args['recipientcustomerid']
                                                             , store_id=store, deleted_at=None).first()

                if recipient_customer is None:
                    return jsonify({"error": CUSTOMER_NOT_FOUND}), 404
                gift_cards = gift_cards.filter(recipient_customer=recipient_customer)

            if "active" in request.args:
                if request.args['active'].lower() == 'true':
                    gift_cards = gift_cards.filter(current_balance_in_cents__gt=0)

            return paginated_results(objects=gift_cards, collection_name='gift_card', request=request
                                     , per_page=self.PER_PAGE, serialization_func=gift_card_objs), 200

    @token_required
    def post(self):
        """
        creates a new gift card.
        """
        error = best_match(Draft4Validator(create_gift_cart_schema).iter_errors(request.json))
        if error:
            return jsonify({"error": error.message}), 400

        store = Store.objects.filter(app_id=request.headers.get('APP-ID'), deleted_at=None).first()

        gifter_customer = Customer.objects.filter(customer_id=request.json.get("gifter_customer_id")
                                           , store_id=store, deleted_at=None).first()
        if gifter_customer is None:
            return jsonify({"error": CUSTOMER_NOT_FOUND}), 404

        recipient_customer = Customer.objects.filter(customer_id=request.json.get("recipient_customer_id")
                                                     , store_id=store, deleted_at=None).first()
        if recipient_customer is None:
            return jsonify({"error": CUSTOMER_NOT_FOUND}), 404

        gift_card = GiftCard(
            gift_card_id=str(uuid.uuid4().int),
            gifter_customer=gifter_customer,
            recipient_customer=recipient_customer,
            original_balance_in_cents=request.json.get("original_amount"),
            current_balance_in_cents=request.json.get("original_amount")
        ).save()

        response = {
            "result": "ok",
            "gift_card": gift_card_obj(gift_card)
        }
        return jsonify(response), 201

    @token_required
    def delete(self, gift_card_id):
        """
        voids a gift_card
        """
        store = Store.objects.filter(app_id=request.headers.get('APP-ID'), deleted_at=None).first()
        gift_card = GiftCard.objects.filter(gift_card_id=gift_card_id).first()

        if gift_card is None:
            return jsonify({"error": GIFT_CARD_NOT_FOUND}), 404

        if gift_card.recipient_customer.store_id != store:
            return jsonify({"error": CUSTOMER_NOT_FOUND}), 404

        gift_card.void()

        return jsonify({}), 204

class CustomerGiftCardAPI(MethodView):

    def __init__(self):
        self.PER_PAGE = 10
        if (request.method != 'GET' and request.method != 'DELETE') and not request.json:
            abort(400)

    @token_required
    def get(self, customer_id):
        """
        returns a list of customer gift cards
        """
        store = Store.objects.filter(app_id=request.headers.get('APP-ID'), deleted_at=None).first()

        customer = Customer.objects.filter(customer_id=customer_id, store_id=store, deleted_at=None).first()

        if customer is None:
            return jsonify({"error": CUSTOMER_NOT_FOUND}), 404

        gift_cards = GiftCard.objects.filter(recipient_customer=customer)

        if "active" in request.args:
            if request.args['active'].lower() == 'true':
                gift_cards = gift_cards.filter(current_balance_in_cents__gt=0)

        return paginated_results(objects=gift_cards, collection_name='gift_card', request=request
                                 , per_page=self.PER_PAGE, serialization_func=gift_card_objs), 200


import os
import json
import jsonlines
from tqdm import tqdm
import random
import numpy as np

from transformers.tokenization_bert import BasicTokenizer

PAD_LABEL_ID = -100

def basic_tokenize(string):
    return BasicTokenizer().tokenize(string)

def get_vocab(paths):
    if os.path.isfile(paths['vocab_path']):
        vocab_path = paths['vocab_path']
        g = open(vocab_path, 'r', encoding='utf-8')
        voc = [s.strip() for s in g.readlines()]
    else:
        vocab = {}
        g = open(paths['vocab_path'], 'w', encoding='utf-8')
        with open(paths['gold_pair_train_path'], 'r', encoding='utf-8') as f:
            json_data = json.load(f)
            # json_data = jsonlines.Reader(f)
            train_data = []
            for item in json_data['train']:
                train_data.append(item)
            for story_index, story in enumerate(tqdm(train_data)):
                history = basic_tokenize(story['beforegold'])
                persona = basic_tokenize(story['persona'])
                gold = basic_tokenize(story['gold'])
                ori_beforegold = basic_tokenize(story['ori_beforegold'])
                ori_persona = basic_tokenize(story['ori_persona'])
                # ori_gold = basic_tokenize(story['ori_gold'])

                all = history + persona + gold + ori_beforegold + ori_persona
                for word in all:
                    if word in vocab:
                        vocab[word] += 1
                    else:
                        vocab[word] = 1

            voc = [v[0] for v in sorted(vocab.items(), key=lambda item: item[1], reverse=True)]

        for x in voc:
            g.write(x + "\n")

    return voc

def get_exp_vocab(paths):
    if os.path.isfile(paths['vocab_path']):
        g = open(paths['vocab_path'], 'r', encoding='utf-8')
        voc = [s.strip() for s in g.readlines()]
    else:
        vocab = {}
        g = open(paths['vocab_path'], 'w', encoding='utf-8')
        with open(paths['gold_pair_train_path'], 'r', encoding='utf-8') as f:
            json_data = json.load(f)
            # json_data = jsonlines.Reader(f)
            train_data = []
            for item in json_data['train']:
                train_data.append(item)
            for story_index, story in enumerate(tqdm(train_data)):
                history = basic_tokenize(story['beforegold'])
                persona = basic_tokenize(story['persona'])
                gold = basic_tokenize(story['gold'])

                temp_persona = []
                temp_persona.append(story['exp_personality']['xattr'])
                temp_persona.append(story['exp_personality']['xeffect'])
                temp_persona.append(story['exp_personality']['xintent'])
                temp_persona.append(story['exp_personality']['xneed'])
                temp_persona.append(story['exp_personality']['xreact'])
                temp_persona.append(story['exp_personality']['xwant'])
                temp_persona = list(filter('<none>'.__ne__, temp_persona))

                exp_persona = basic_tokenize(" ".join(temp_persona))
                all = history + persona + gold + exp_persona

                for word in all:
                    if word in vocab:
                        vocab[word] += 1
                    else:
                        vocab[word] = 1

            voc = [v[0] for v in sorted(vocab.items(), key=lambda item: item[1], reverse=True)]

        for x in voc:
            g.write(x + "\n")

    return voc

def get_aug_skeletons(s, mask_rate, replace_rate, vocab):
    """ This function is used for generating augmented skeletons

    Args:
        s: list. The raw skeleton
        mask_rate: The rate of background words to be masked
        replace_rate: The rate of background words to be replaced with random words.
        vocab: The vocab for getting random words.
    """
    # a for mask, b for replace, c for shuffle
    # ending string을 3가지 카테고리(랜덤 마스킹, 랜덤 단어 대치, 랜덤 순서 변경)로 augment 시켜서 리스트를 리턴
    a = s.copy()
    b = s.copy()
    c = s.copy()

    # 랜덤으로 토큰 __ 처리
    for i in range(len(s)):
        if s[i] != "__":
            rand = random.random()
            if rand < mask_rate:
                a[i] = "__"

    # 랜덤으로 토큰에 다른 토큰으로 대채함(보카 사전에 있는 단어 중에서 골라옴)
    for i in range(len(s)):
        if s[i] != "__":
            rand = random.random()
            if rand < replace_rate:
                rand_word_id = np.random.randint(0, len(vocab)-1)
                b[i] = vocab[rand_word_id]

    # 블랭크 뺀 토큰들만 다시 담아서 셔플해버림
    no_blank_word_id_in_sen = []
    no_blank_word_in_sen = []
    for i in range(len(s)):
        if s[i] != "__":
            no_blank_word_id_in_sen.append(i)
            no_blank_word_in_sen.append(s[i])
    random.shuffle(no_blank_word_in_sen)
    j = 0
    for i in range(len(s)):
        if s[i] != "__":
            c[i] = no_blank_word_in_sen[j]
            j += 1

    str_mask = " ".join([s for s in a]).strip()
    str_replace = " ".join([s for s in b]).strip()
    str_shuffle = " ".join([s for s in c]).strip()

    return [str_mask, str_replace, str_shuffle]

def bottom_up_dp_lcs(str_a, str_b, do_merge, mask_rate, replace_rate, vocab, mode):
    """Get LCS skeletons using the bottom up DP algorithm

    Args:
        str_a, : String
        do_merge: bool. Whether merge the consecutive blanks into one blank
        mask_rate: float. The rate of background words to be masked
        replace_rate: float. The rate of background words to be replaced with the random word
        vocab: list. The vocab for getting random words.
        mode: string. "train", "dev" or "test"
    """

    """ LCS 로직 수정
    <기존> : str_a와 str_b. 두 개의 스트링의 토큰들을 각각 끝 단어부터 서로 비교하면서
    공통된 단어(background word)는 살리고, 서로 다른 단어(Unique word)는 블랭크 처리

    example.
    str_a ( persona ) : i have played since i was 4 years old .
    str_b ( gold )    : never . i have been playing since i was four. it pays more than lifiting weights .

    lcs_a ( persona after process) : i have __ since i was __ __ __ .
    lcs_b ( gold after process)    : __ __ i have __ __ since i was __ __ __ __ __ __ __ .

    * merge = True --> 이어지는 / consecutive blank는 하나의 blank로 합침.
      데이터 증강 전에 이 작업을 해주고, 증강을 시킴

    lcs_a ( persona after process) : i have __ since i was __ .
    lcs_b ( gold after process)    : __ i have __ since i was __ .

    <수정> : str_a와 str_b. 두 개의 스트링의 토큰들을 각각 끝 단어부터 서로 비교하면서
    서로 다른 단어(Unique word)는 살리고, 서로 같은 단어(background words)는 personal words로 취급하여 블랭크 처리

    lcs_a ( persona after process ) :  __ __ played __ __ __ 4 years old __
    lcs_b ( gold after process )    : never . __ __ been playing __ __ __ four . it pays more than lifting weights __

    * merge = True
    lcs_a ( persona after process ) :  __ played __ 4 years old __
    lcs_b ( gold after process )    : never . __ been playing __ four . it pays more than lifting weights __

    * Aug

    lcs_a : [__ played __ 4 years old __],
            [__ played __ 4 __ old __ ] : 랜덤으로 blank 추가된 데이터
            [__ played __ 4 months old __] : 단어 사전에서 뽑아온 신규 단어를 랜덤으로 교체
            [__ 4 __ played years old __] : 랜덤으로 단어 순서를 바꿈

    이렇게 기존 데이터에 3가지 타입의 데이터를 더 붙여서 어그맨팅함
    우리는 lcs_b만을 훈련 과정에 사용한다
    """
    str_a = basic_tokenize(str_a)
    str_b = basic_tokenize(str_b)
    str_a.insert(0, "_str_")
    str_b.insert(0, "_str_")
    if len(str_a) == 0 or len(str_b) == 0:
        return 0
    # Persona sentence 길이만큼 0으로 리스트를 채우고, 그 리스트를 Gold sentence 길이만큼 반복해서 복사한 리스트를 채운다
    dp = [[0 for _ in range(len(str_b) + 1)] for _ in range(len(str_a) + 1)]
    for i in range(1, len(str_a) + 1):
        for j in range(1, len(str_b) + 1):
            if str_a[i - 1] == str_b[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max([dp[i - 1][j], dp[i][j - 1]])

    i, j = len(str_a), len(str_b)
    LCS_a = ""
    LCS_b = ""
    a_unique = []
    b_unique = []

    # str_a와 str_b의 문장들의 가장 끝부분부터 차례대로 검사하면서, 공통 단어를 체크한다. 공통 단어가 아닌 경우를 unique 리스트에 추가한다
    while i > 0 and j > 0:
        # str_a와 str_b의 마지막 토큰들을 서로 비교하여 같을 경우에 아래 조건문 진입
        if str_a[i - 1] == str_b[j - 1] and dp[i][j] == dp[i - 1][j - 1] + 1:
            # LCS_a = str_a[i - 1] + " " + LCS_a
            # LCS_b = str_a[i - 1] + " " + LCS_b
            # merge가 True일 경우 __이 맨 앞에 있을 경우는 중복 블랭크를 없애야 하기 때문에 if 문으로 구별
            if LCS_a.startswith(" __ ") and do_merge:
                LCS_a = LCS_a
            else:
                LCS_a = " __ " + LCS_a
            if LCS_b.startswith(" __ ") and do_merge:
                LCS_b = LCS_b
            else:
                LCS_b = " __ " + LCS_b

            # LCS_a = " __ " + LCS_a
            # LCS_b = " __ " + LCS_b
            i, j = i - 1, j - 1
            continue
        # str_a와 str_b의 마지막 토큰들을 서로 비교하여 다를 경우에 아래 조건문 진입
        if dp[i][j] == dp[i - 1][j]:  # lcs_a를 채워나가는 조건문
            i, j = i - 1, j
            if LCS_a.startswith(" __ ") and do_merge:
                # LCS_a = LCS_a
                LCS_a = str_a[i] + " " + LCS_a
            else:
                # LCS_a = " __ " + LCS_a
                LCS_a = str_a[i] + " " + LCS_a
            a_unique.append((i, str_a[i]))
            continue
        if dp[i][j] == dp[i][j - 1]:  # lcs_b를 채워나가는 조건문
            i, j = i, j - 1
            if LCS_b.startswith(" __ ") and do_merge:
                # LCS_b = LCS_b
                LCS_b = str_b[j] + " " + LCS_b
            else:
                # LCS_b = " __ " + LCS_b
                LCS_b = str_b[j] + " " + LCS_b
            b_unique.append((j, str_b[j]))
            continue
    a_unique = a_unique[::-1]
    b_unique = b_unique[::-1]

    LCS_a = LCS_a[4:]  # _str_ 이후의 글자들
    LCS_b = LCS_b[4:]

    if not do_merge:
        lcsas = [LCS_a]
        lcsbs = [LCS_b]
    else:
        if mode == "train_aug":
            split_LCS_a = LCS_a.split()
            split_LCS_b = LCS_b.split()
            lcsas = [LCS_a] + get_aug_skeletons(split_LCS_a, mask_rate, replace_rate, vocab)
            lcsbs = [LCS_b] + get_aug_skeletons(split_LCS_b, mask_rate, replace_rate, vocab)
        else:
            lcsas = [LCS_a]
            lcsbs = [LCS_b]

    return lcsas, lcsbs, a_unique, b_unique, str_a[1:], str_b[1:]

def get_train_skeletons(paths, mode, do_merge, mask_rate, replace_rate, exp):

    if exp == True:
        vocab = get_exp_vocab(paths)
    else:
        vocab = get_vocab(paths)

    with open(paths['gold_pair_train_path'], 'r', encoding='utf-8') as f:
        json_data = json.load(f)
        train_data = []
        for item in json_data['train']:
            train_data.append(item)

    skeletons = []

    with open(paths['train_skeletons_path'], 'w', encoding="utf-8") as f:
        for story_index, story in enumerate(tqdm(train_data)):
            history = story['ori_beforegold']
            upg_persona = story['upg_persona']
            gold = story['gold']
            if exp:
                # atomic의 9가지 관계 중 others 관계 3가지를 제외한 6가지 관계만 관리
                # 6가지 관계 중 NLI 필터를 거쳐서 남은 1등 페르소나(빔서치 내림차순)를 사용
                # None은 NLI 필터를 거쳐서 살아남은 녀석이 없는 것임.
                temp_persona = []
                temp_persona.append(story['exp_personality']['xattr'])
                temp_persona.append(story['exp_personality']['xeffect'])
                temp_persona.append(story['exp_personality']['xintent'])
                temp_persona.append(story['exp_personality']['xneed'])
                temp_persona.append(story['exp_personality']['xreact'])
                temp_persona.append(story['exp_personality']['xwant'])

                exp_persona = list(filter('<none>'.__ne__, temp_persona))

                #"xattr": "<none>", "xeffect": "<none>", "xintent": "<none>", "xneed": "i need to drive to the diner . .", "xreact": "<none>", "xwant": "<none>"

            skeleton_persona, skeleton_gold, \
            persona_unique_word, gold_unique_word, \
            basic_toked_persona, basic_toked_gold = bottom_up_dp_lcs(upg_persona, gold, do_merge, mask_rate, replace_rate,
                                                                     vocab, mode)

            skeleton = {}
            skeleton['history'] = story['ori_beforegold']
            skeleton['gold'] = gold
            skeleton['ori_gold'] = story['ori_gold']
            skeleton['persona'] = upg_persona
            skeleton['ori_persona'] = story['ori_persona']
            skeleton['exp_personality'] = story['exp_personality']
            skeleton['used_exp'] = story['used_exp']
            skeleton['raw_skeleton_gold'] = skeleton_gold
            # skeleton['raw_skeleton_gold_unique'] = gold_unique_word
            # skeleton['raw_skeleton_persona_unique'] = persona_unique_word
            basic_toked_history = basic_tokenize(history)

            if exp:
                if not exp_persona:
                    skeleton['exp_persona'] = '[PAD]'
                    basic_toked_exp_persona = basic_tokenize(exp_persona)
                else:
                    skeleton['exp_persona'] = exp_persona
                    basic_toked_exp_persona = basic_tokenize(" ".join(exp_persona))

                sen_pre_1 = ["<premise>"] + basic_toked_history + ["<raw>"] + basic_toked_persona + [
                    "<cf>"] + basic_toked_exp_persona
                sen_after_1 = ["<raw>"] + basic_toked_gold

            else:
                sen_pre_1 = ["<premise>"] + basic_toked_history + ["<raw>"] + basic_toked_persona
                sen_after_1 = ["<raw>"] + basic_toked_gold

            # sen_pre_1은 sketch 레이블에는 관여되지 않는 컨택스트들이라서 -100으로 패딩 처리
            # gold 자리의 레이블은 토큰 길이만큼 0으로 우선 채우고, 정답이 아닌 위치의 레이블은 1로 변경
            len_pre = len(sen_pre_1)
            label_pre = [PAD_LABEL_ID for i in range(len(sen_pre_1))]
            label_gold = [0 for i in range(len(basic_toked_gold))]

            # 우리가 맞춰야 할 자리는 페르소나 문장과 골드 문장의 공통된 단어 토큰
            # 그래서 unique_words의 레이블은 1로 변환. 정답 위치의 레이블은 0으로 두고
            # for x in persona_unique_word:
            #     label_gold[x[0]] = 1
            for x in gold_unique_word:
                label_gold[x[0] - 1] = 1

            label_gold = [PAD_LABEL_ID] + label_gold
            label_raw = [label_pre, label_gold]
            ex_raw = [sen_pre_1, sen_after_1]

            assert len(label_raw[1]) == len(ex_raw[1])

            # skeleton['label_raw'] = label_raw
            # skeleton['ex_raw'] = ex_raw
            skeletons.append(skeleton)

        json.dump(skeletons, f)

def get_train_skeletons_edit(paths):
    '''
    triple_5_ForLCS 데이터를 가지고 스켈레톤을 우선 만든다 ( skeleton = LCS(전처리반영된페르소나, 골드)
    '''
    skeleton_path = paths['train_skeletons_path']
    triple_path = paths['gold_pair_train_path']
    result_path = paths['train_skeletons_edit_path']

    with open(skeleton_path, 'r', encoding='utf-8') as f:
        skeleton_item = json.load(f)
    with open(triple_path, 'r', encoding='utf-8') as t:
        triple_item = json.load(t)

    with open(result_path, 'w', encoding='utf-8') as w:
        result = []
        # triple_persona = []
        # for i, data in enumerate(triple_item):
        #     triple_persona.append(data['originP'])

        for i, data in enumerate(skeleton_item):
            edit_triple = {}
            edit_triple['history'] = data['history']
            edit_triple['persona'] = data['ori_persona']
            edit_triple['gold'] = data['gold']
            edit_triple['raw_skeleton_gold'] = data['raw_skeleton_gold']
            result.append(edit_triple)
            #print("check")
        json.dump(result, w, separators=(',', ': '))

def get_dev_skeletons(paths, mode, do_merge, exp):
    if mode == "dev":
        pair_path = paths['gold_pair_dev_path']
        skeletons_path = paths['dev_skeletons_path']
    elif mode == "test":
        pair_path = paths['gold_pair_test_path']
        skeletons_path = paths['test_skeletons_path']

    with open(pair_path, 'r', encoding='utf-8') as f:
        json_data = json.load(f)
        train_data = []
        for item in json_data['valid']:
            train_data.append(item)

    skeletons = []

    with open(skeletons_path, 'w', encoding="utf-8") as f:
        for story_index, story in enumerate(tqdm(train_data)):
            history = story['beforegold']
            upg_persona = story['upg_persona']
            gold = story['gold']
            if exp:
                # atomic의 9가지 관계 중 others 관계 3가지를 제외한 6가지 관계만 관리
                # 6가지 관계 중 NLI 필터를 거쳐서 남은 1등 페르소나(빔서치 내림차순)를 사용
                # None은 NLI 필터를 거쳐서 살아남은 녀석이 없는 것임.
                temp_persona = []
                temp_persona.append(story['exp_personality']['xattr'])
                temp_persona.append(story['exp_personality']['xeffect'])
                temp_persona.append(story['exp_personality']['xintent'])
                temp_persona.append(story['exp_personality']['xneed'])
                temp_persona.append(story['exp_personality']['xreact'])
                temp_persona.append(story['exp_personality']['xwant'])

                exp_persona = list(filter('<none>'.__ne__, temp_persona))

            skeleton_persona, skeleton_gold, \
            persona_unique_word, gold_unique_word, \
            basic_toked_persona, basic_toked_gold = bottom_up_dp_lcs(upg_persona, gold, do_merge, 0, 0, [], mode)

            skeleton = {}
            skeleton['history'] = story['ori_beforegold']
            skeleton['gold'] = gold
            skeleton['ori_gold'] = story['ori_gold']
            skeleton['persona'] = upg_persona
            skeleton['ori_persona'] = story['ori_persona']
            skeleton['exp_personality'] = story['exp_personality']
            skeleton['used_exp'] = story['used_exp']
            skeleton['raw_skeleton_gold'] = skeleton_gold
            # skeleton['raw_skeleton_gold_unique'] = gold_unique_word
            # skeleton['raw_skeleton_persona_unique'] = persona_unique_word
            basic_toked_history = basic_tokenize(history)

            if exp:
                if not exp_persona:
                    skeleton['exp_persona'] = '[PAD]'
                    basic_toked_exp_persona = basic_tokenize(exp_persona)
                else:
                    skeleton['exp_persona'] = exp_persona
                    basic_toked_exp_persona = basic_tokenize(" ".join(exp_persona))

                sen_pre_1 = ["<premise>"] + basic_toked_history + ["<raw>"] + basic_toked_persona + [
                    "<cf>"] + basic_toked_exp_persona
                sen_after_1 = ["<raw>"] + basic_toked_gold

            else:
                sen_pre_1 = ["<premise>"] + basic_toked_history + ["<raw>"] + basic_toked_persona
                sen_after_1 = ["<raw>"] + basic_toked_gold

            #
            #
            # if exp:
            #     skeleton['exp_persona'] = exp_persona
            #     basic_toked_exp_persona = basic_tokenize(exp_persona)
            #
            # skeleton['raw_skeleton_gold'] = skeleton_gold
            # skeleton['raw_skeleton_gold_unique'] = gold_unique_word
            # # skeleton['raw_skeleton_persona_unique'] = persona_unique_word
            #
            # basic_toked_history = basic_tokenize(history)
            #
            # if exp:
            #     sen_pre_1 = ["<premise>"] + basic_toked_history + ["<raw>"] + basic_toked_persona + [
            #         "<cf>"] + basic_toked_exp_persona
            #     sen_after_1 = ["<raw>"] + basic_toked_gold
            # else:
            #     sen_pre_1 = ["<premise>"] + basic_toked_history + ["<raw>"] + basic_toked_persona
            #     sen_after_1 = ["<raw>"] + basic_toked_gold

            # sen_pre_1은 sketch 레이블에는 관여되지 않는 컨택스트들이라서 -100으로 패딩 처리
            # gold 자리의 레이블은 토큰 길이만큼 0으로 우선 채우고, 정답이 아닌 위치의 레이블은 1로 변경
            len_pre = len(sen_pre_1)
            label_pre = [PAD_LABEL_ID for i in range(len(sen_pre_1))]
            label_gold = [0 for i in range(len(basic_toked_gold))]

            # 우리가 맞춰야 할 자리는 페르소나 문장과 골드 문장의 공통된 단어 토큰
            # 그래서 unique_words의 레이블은 1로 변환. 정답 위치의 레이블은 0으로 두고
            # for x in persona_unique_word:
            #     label_gold[x[0]] = 1
            for x in gold_unique_word:
                label_gold[x[0] - 1] = 1

            label_gold = [PAD_LABEL_ID] + label_gold
            label_raw = [label_pre, label_gold]
            ex_raw = [sen_pre_1, sen_after_1]

            assert len(label_raw[1]) == len(ex_raw[1])

            # skeleton['label_raw'] = label_raw
            # skeleton['ex_raw'] = ex_raw
            skeletons.append(skeleton)

        json.dump(skeletons, f)

if __name__ == '__main__':
    paths = {}
    # 최종 데이터 #

    do_merge = True
    exp = False
    paths['vocab_path'] = "dataForFinal/vocab.txt"
    paths['gold_pair_train_path'] = "trimdata/edited_triple_train.json"
    paths['train_skeletons_path'] = "dataForFinal/train_skeletons_aug.json"
    paths['train_skeletons_edit_path'] = "dataForFinal/train_skeletons_aug_edit.json"
    get_train_skeletons(paths, "train_aug", do_merge, 0.2, 0.2, exp)
    # get_train_skeletons_edit(paths)

    #dev
    do_merge = False
    exp = False
    paths['gold_pair_dev_path'] = "trimdata/edited_triple_train.json"
    paths['dev_skeletons_path'] = "dataForFinal/dev_skeletons.json"
    get_dev_skeletons(paths, "dev", do_merge, exp)

    # # --- 원규 데이터  ----#

    # train
    # do_merge = True
    # exp = True
    # paths['vocab_path'] = "dataForWG/vocab_exp.txt"
    # paths['gold_pair_train_path'] = "dataForWG/gold_pair_train_exp_atomic_filter.json"  # read
    # paths['train_skeletons_path'] = "dataForWG/train_skeletons_atomic_filter_aug.json"  # write
    # get_train_skeletons(paths, "train_aug", do_merge, 0.2, 0.2, exp)

    #dev
    # do_merge = False
    # exp = True
    # paths['gold_pair_dev_path'] = "dataForWG/gold_pair_dev_exp_atomic_filter.json"
    # paths['dev_skeletons_path'] = "dataForWG/dev_skeletons_atomic_filter_TTTT.json"
    # get_dev_skeletons(paths, "dev", do_merge, exp)
    # ----- 원규 데이터 용도 끝 -------

    # do_merge = False
    # exp = True
    # paths['vocab_path'] = "data/vocab_exp_none.txt"
    # paths['gold_pair_dev_path'] = "dataForWG/gold_pair_dev.json"
    # paths['dev_skeletons_path'] = "dataForWG/dev_skeletons.json"
    # get_dev_skeletons(paths, "dev", do_merge, exp)

    # do_merge = False
    # exp = True
    # paths['vocab_path'] = "data/vocab_exp_none.txt"
    # paths['gold_pair_dev_path'] = "data/gold_pair_dev_exp_none.json"
    # paths['dev_skeletons_path'] = "data/dev_skeletons_exp_none.json"
    # get_dev_skeletons(paths, "dev", do_merge, exp)

    # #test_exp
    # do_merge = False
    # exp = True
    # paths['gold_pair_test_path'] = "data/gold_pair_test_exp_none.json"
    # paths['test_skeletons_path'] = "data/test_skeletons_exp_none.json"
    # get_dev_skeletons(paths, "test", do_merge, exp)

    # test
    # do_merge = False
    # exp = False
    # paths['gold_pair_test_path'] = "data/gold_pair_test_wo_exp2.json"
    # paths['test_skeletons_path'] = "data/test_skeletons_wo_exp2.json"
    #
    # get_dev_skeletons(paths, "test", do_merge, exp)

    # # --- YES merge  YES Aug  TRAINING ----#
    # do_merge = True
    # paths['vocab_path'] = "data/vocab.txt"
    # paths['gold_pair_train_path'] = "data/gold_pair_train.json"
    # paths['train_skeletons_path'] = "data/train_skeletons.json"
    # #
    # get_train_skeletons(paths, "train", do_merge, 0.2, 0.2)


# coding=utf-8
from Tkinter import *
import Sublimator
import matplotlib
import numpy as np
import datetime

matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import pyplot as plt
MAX_NUM_PLOTDATA = 100

class CalibrationDialog:

    '''
        Dialog for Calibrating the lightbarrier for the current light conditions.
    '''

    def __init__(self, parent,sublimator):
        self.sublimator = sublimator
        top = self.top = Toplevel(parent)
        self.parent = parent
        Label(
            top, text="Calibration in progress.\n Lightsensor will not work if canceled.").pack()
        self.timelabel = Label(top, text="Time: 0:0")
        self.timelabel.pack()
        self.startTime = datetime.datetime.utcnow()
        self.calibrationRunning = True
        self.finishbutton = Button(top, text="Cancel Calibration", command=self.finish,foreground="red")
        self.finishbutton.pack(pady=5)

        top.wait_visibility()
        top.protocol('WM_DELETE_WINDOW', self.finish)
        # start calibration thread
        self.sublimator.calib_start()
        self.updateTime()

    def finish(self):
        # stopping calibr. thread
        self.sublimator.calib_stop()
        self.calibrationRunning = False
        self.top.destroy()

    def updateTime(self):
        if self.calibrationRunning:
            self.passedTime = datetime.datetime.utcnow() - self.startTime
            self.timelabel.config(
                text="Calibration-Time: {}:{}".format(self.passedTime.seconds / 60, self.passedTime.seconds % 60))
            if self.sublimator.hardware.activeConfiguration():
                self.parent.after(1000, self.updateTime)
            else:
                self.sublimator.lightcalibrationFinished = True
                self.finishbutton.config(text="Finish", foreground="black")
                self.timelabel.config(
                    text="Calibration finished!")

class Gui(Frame):

    '''
    Main Window for controlling the underlying program.
    '''

    def __init__(self, sublimator, master=None):
        self.sublimator = sublimator
        self.coolinglist = []
        self.heatinglist = []
        self.timelist = []
        self.seplist = []
        self.log = []

        self.progend = True
        self.sequences = self.sublimator.sequences
        self.testsequence = [x.name for x in self.sequences]
        self.variable = StringVar(master)
        self.variable.set(self.sequences[0].name)
        self.runner = 0
        self.oldlen = 0

        Frame.__init__(self, master)

        self.grid()
        self.buttoncontainer = Frame(master=self)
        self.buttoncontainer.grid(column=0, row=0)

        self.infoContainer()
        self.showText()
        self.showTextline(event=None)
        self.createDropdown()
        self.createStartButton()
        self.createCalibrationButton()
        self.showDiagram()
        self.saveDiagram()

    def infoContainer(self):
        '''
            Erstellt Container fuer die Programminformationen und setzt eine Scrollbar ein.
            '''
        self.containercan = Canvas(self, borderwidth=0, width=30)
        self.infocontainer = Frame(self.containercan)
        self.vsb = Scrollbar(self, orient="vertical", command=self.containercan.yview)
        self.containercan.configure(yscrollcommand=self.vsb.set)
        self.containercan.grid(column=0, row=2, sticky=N + S + E)
        self.vsb.grid(column=1, row=2, sticky=N + S)
        interior_id = self.containercan.create_window(
            (0, 0), window=self.infocontainer, anchor="nw")

        def _configure_infocontainer(event):
            # update the scrollbars to match the size of the inner frame
            size = (self.infocontainer.winfo_reqwidth(), self.infocontainer.winfo_reqheight())
            self.containercan.config(scrollregion="0 0 %s %s" % size)
            if self.infocontainer.winfo_reqwidth() != self.containercan.winfo_width():
                # update the self.containercan's width to fit the inner frame
                self.containercan.config(width=self.infocontainer.winfo_reqwidth())

        def _configure_containercan(event):
            if self.infocontainer.winfo_reqwidth() != self.containercan.winfo_width():
                # update the inner frame's width to fill the self.containercan
                self.containercan.itemconfigure(interior_id, width=self.containercan.winfo_width())

        self.infocontainer.bind('<Configure>', _configure_infocontainer)
        self.containercan.bind('<Configure>', _configure_containercan)

    def showText(self):
        '''
            Erstellt das Textfenster fuer die Log informationen.
            '''
        self.scrollbar = Scrollbar(self)
        self.textField = Text(self, height=10, width=90)
        self.scrollbar.grid(column=3, row=0, sticky=N + S)
        self.textField.grid(column=2, row=0)
        self.scrollbar.config(command=self.textField.yview)
        self.textField.config(
            yscrollcommand=self.scrollbar.set, state=DISABLED)

    def createDropdown(self):
        '''
        Erstellt das Dropdownmenue fuer die Auswahl der Programme.
        '''
        self.dropdown = apply(
            OptionMenu, (self.buttoncontainer, self.variable) + tuple(self.testsequence))
        self.variable.trace("w", self.showTextline)
        self.dropdown.grid(column=0, row=0, sticky=E + W + N + S)

    def createStartButton(self):
        '''
            Erstellt den Startbutton
            '''
        self.startButton = Button(self.buttoncontainer)
        self.startButton["text"] = "Start"
        self.startButton.bind("<Button-1>", self.startButton_Click)
        self.startButton.grid(column=1, row=0, sticky=E + W + N + S)

    def createCalibrationButton(self):
        '''
        Erstellt den Kalibrierungsbutton
        '''
        self.calibrationButton = Button(self.buttoncontainer)
        self.calibrationButton["text"] = "Lichtschranke kalibrieren"
        self.calibrationButton.bind("<Button-1>", self.calibrateButton_Click)
        self.calibrationButton.grid(column=0, row=2, sticky=E + W + N + S)

    def saveDiagram(self):
        self.checkvariable = IntVar()
        self.saveCheckbox = Checkbutton(
            self.buttoncontainer, text="save Diagram", variable=self.checkvariable)
        self.saveCheckbox.grid(column=0, row=1, sticky=E + W + N + S, columnspan=2)

    def startButton_Click(self, event):
        if not self.sublimator.running:
            self.sublimator.start(self.sequences[self.runner])
            self.plotData = [(0, 0, 0, 0)] * MAX_NUM_PLOTDATA
            self.startButton.config(text="Stop")
            self.saveCheckbox.configure(state="disabled")
        else:
            self.sublimator.stop()
            self.startButton.config(text="Start")
            self.saveCheckbox.configure(state="normal")

    def calibrateButton_Click(self, event):
        dialog = CalibrationDialog(root,self.sublimator)
        dialog.top.transient(self.master)
        dialog.top.grab_set()
        self.wait_window(dialog.top)
        self.calibTime = dialog.passedTime
        dialog.top.grab_release()
        self.sublimator.logger.info("Light-Calibration was running for {} minutes and {} seconds.".format(
            self.calibTime.seconds / 60, self.calibTime.seconds % 60))

    def showTextline(self, event, *args):
        '''
            sorgt fuer das fuellen des Informationscontainers bei Auswahl von Programm.
            '''
        self.runner = self.testsequence.index(self.variable.get())
        phases = Entry(self.infocontainer)
        phases.insert(END, "Phases of Program")
        phases.grid(column=0, row=0, sticky=N)
        phases.config(state=DISABLED)
        t = 0
        for heat in self.heatinglist:
            heat.destroy()
        self.heatinglist[:] = []
        for time_entry in self.timelist:
            time_entry.destroy()
        self.timelist[:] = []
        for cool in self.coolinglist:
            cool.destroy()
        self.coolinglist[:] = []
        for sep in self.seplist:
            sep.destroy()
        self.seplist[:] = []

        for i in range(len(self.sequences[self.runner].programs)):
            tlineheat = Entry(self.infocontainer)
            tlinecool = Entry(self.infocontainer)
            tlinetime = Entry(self.infocontainer)
            tlinefree = Entry(self.infocontainer)

            tlinefree.insert(END, "Phase " + str(i + 1))
            tlinefree.grid(column=0, row=t, sticky=N)
            tlinefree.config(state=(DISABLED), disabledbackground="white")
            self.seplist.append(tlinefree)

            tlineheat.config(state=NORMAL)
            tlineheat.insert(
                END, "Heating: " + str(self.sequences[self.runner].programs[i].targetHeatingTemp) + " Celsius")
            tlineheat.grid(column=0, row=t + 1, sticky=N)
            tlineheat.config(state=DISABLED)
            self.heatinglist.append(tlineheat)

            tlinecool.config(state=NORMAL)
            tlinecool.insert(
                END, "Cooling: " + str(self.sequences[self.runner].programs[i].targetCoolingTemp) + " Celsius")
            tlinecool.grid(column=0, row=t + 2, sticky=N)
            tlinecool.config(state=DISABLED)
            self.coolinglist.append(tlinecool)

            tlinetime.config(state=NORMAL)
            tlinetime.insert(
                END, "time: " + str(self.sequences[self.runner].programs[i].time) + " Sekunden")
            tlinetime.grid(column=0, row=t + 3, sticky=N)
            tlinetime.config(state=DISABLED)
            self.timelist.append(tlinetime)

            t += 4

    def button02_Click(self, event):

        if not self.sublimator.running:
            self.sublimator.start(self.sequences[self.runner - 1])
            self.plotData = [(0, 0, 0, 0)] * MAX_NUM_PLOTDATA
            self.button02.config(text="Stop")

            if self.scrollbar.get()[1] == 1.0:
                self.textField.yview(END)

        else:
            self.sublimator.stop()
            self.button02.config(text="Start")

    def showDiagram(self):
        self.plotData = [(0, 0, 0, 0, 0)] * MAX_NUM_PLOTDATA
        self.fig, self.ax = plt.subplots()
        self.ax2 = self.ax.twinx()

        # Lightbarrier Axis
        self.lightax = self.ax.twinx()
        self.fig.subplots_adjust(right=0.75)
        # Move lightbarrier axis to the right
        self.lightax.spines['right'].set_position(('axes', 1.2))
        self.lightax.set_frame_on(True)
        self.lightax.patch.set_visible(False)

        # Initalize plot lines with dummy data
        self.line1, = self.ax.plot(
            [x[0] for x in self.plotData], 'r--')  # Target Heat
        self.line2, = self.ax.plot(
            [x[1] for x in self.plotData], 'r-')  # Heat
        self.line3, = self.ax2.plot(
            [x[2] for x in self.plotData], 'b--')  # Target Cooling
        self.line4, = self.ax2.plot(
            [x[3] for x in self.plotData], 'b-')  # Cooling
        self.line5, = self.lightax.plot(
            [x[4] for x in self.plotData], 'k:')  # Light

        # Set parameter for the look of the figure
        self.ax.set_xlabel("Time")
        self.ax.set_ylim([0, 180])
        self.ax.set_ylabel(u"Heating-element in °C")
        self.ax.yaxis.label.set_color("red")
        self.ax.tick_params(axis="y", colors="red")
        self.ax2.set_ylim([0, 30])
        self.ax2.set_ylabel(u"Cooling-element in °C")
        self.ax2.yaxis.label.set_color("blue")
        self.ax2.tick_params(axis="y", colors="blue")
        self.lightax.set_ylim([0, 1])
        self.lightax.set_ylabel(u"LED-Brightness")
        self.lightax.yaxis.label.set_color("black")
        self.lightax.tick_params(axis="y", colors="black")

        # Show figure in tkinter widget
        self.canvas = FigureCanvasTkAgg(self.fig, self)
        self.canvas.show()
        self.canvas.get_tk_widget().grid()
        self.canvas._tkcanvas.grid(column=2, row=1, rowspan=100, sticky=W + S)

    def updatePlot(self):
        if self.sublimator.running:
            self.progend = False
            self.ax.set_title("")
            data = self.sublimator.datalog[:]
            if self.sublimator.progindex == 0:

                self.seplist[self.sublimator.progindex].configure(disabledbackground="cyan")

            else:
                self.seplist[self.sublimator.progindex].configure(disabledbackground="cyan")
                self.seplist[self.sublimator.progindex - 1].configure(disabledbackground="white")

            self.plotData = data
            if len(data) > MAX_NUM_PLOTDATA:
                self.plotData = data
                del self.plotData[0: len(data) - MAX_NUM_PLOTDATA]

            self.line1.set_xdata(np.arange(len(self.plotData)))
            self.line1.set_ydata([x[0] for x in self.plotData])

            self.line2.set_xdata(np.arange(len(self.plotData)))
            self.line2.set_ydata([x[1] for x in self.plotData])

            self.line3.set_xdata(np.arange(len(self.plotData)))
            self.line3.set_ydata([x[2] for x in self.plotData])

            self.line4.set_xdata(np.arange(len(self.plotData)))
            self.line4.set_ydata([x[3] for x in self.plotData])

            self.line5.set_xdata(np.arange(len(self.plotData)))
            self.line5.set_ydata([x[4] for x in self.plotData])

            # self.ax.plot()
            # self.ax2.plot()
            self.fig.canvas.draw()

        self.after(1000, self.updatePlot)

        if not self.sublimator.running:
            if self.progend is False and self.checkvariable.get() is 1:
                # Kompletter Plot wird am Ende gezeichnet wenn Checkbox aktiv
                self.saveEndPlot()

                self.progend = True

            for sep in self.seplist:
                sep.configure(disabledbackground="white")

            self.startButton.config(text="Start")
            self.saveCheckbox.configure(state="normal")

    def saveEndPlot(self):
        name = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M") + "_" + self.sequences[
            self.runner].name
        figurefile = "./figs/" + name + ".png"
        self.ax.lines.pop(0)
        self.ax2.lines.pop(0)
        self.lightax.pop(0)

        self.line1, = self.ax.plot(
            [x[0] for x in self.sublimator.datalog], 'r--')  # Target Heat
        self.line2, = self.ax.plot(
            [x[1] for x in self.sublimator.datalog], 'r-')  # Heat
        self.line3, = self.ax2.plot(
            [x[2] for x in self.sublimator.datalog], 'b--')  # Target Cooling
        self.line4, = self.ax2.plot(
            [x[3] for x in self.sublimator.datalog], 'b-')  # Cooling
        self.line5, = self.lightax.plot(
            [x[4] for x in self.sublimator.datalog], 'k:')  # Light

        # self.line1.set_xdata(np.arange(len(self.sublimator.datalog)))
        # self.line1.set_ydata([x[0] for x in self.sublimator.datalog])
        # self.line2.set_xdata(np.arange(len(self.sublimator.datalog)))
        # self.line2.set_ydata([x[1] for x in self.sublimator.datalog])
        # self.line3.set_xdata(np.arange(len(self.sublimator.datalog)))
        # self.line3.set_ydata([x[2] for x in self.sublimator.datalog])
        # self.line4.set_xdata(np.arange(len(self.sublimator.datalog)))
        # self.line4.set_ydata([x[3] for x in self.sublimator.datalog])
        # self.line5.set_xdata(np.arange(len(self.sublimator.datalog)))
        # self.line5.set_ydata([x[4] for x in self.sublimator.datalog])

        self.ax.set_title(name)
        self.fig.savefig(figurefile)
        self.sublimator.logger.info(
            "Figure with collected data was created: {}".format(figurefile))
        self.showDiagram()

    def updateConsole(self):
            '''
            Updatet die Konsolenausgabe.
            Verhindert Probleme mit Multithrading und Tkinter...
            '''

            self.newlen = len(self.sublimator.log_capture_string.getvalue())
            # Update der Konsole wenn neue Daten vorliegen
            if 0 < self.newlen != self.oldlen:
                self.oldlen = self.newlen
                # Inhalt komplett loeschen
                self.textField.configure(state=NORMAL)
                self.textField.delete(1.0, END)
                self.log = self.sublimator.log_capture_string.getvalue()
                # Neuen Inhalt einfuegen
                self.textField.insert(END, self.log)
                self.textField.configure(state=DISABLED)
                self.textField.yview(END)  # Setzt Scrollbar ans Ende
            # Update nach einer Sekunde, ruft Methode neu auf
            self.after(1000, self.updateConsole)

def _quit():
    '''
    Sorgt dafür das alle Threads in Tkinter und matplotlib richtig geschlossen werden
    :return:
    '''
    root.quit()
    root.destroy()

if __name__ == '__main__':
    sublimator = Sublimator.Sublimator()
    root = Tk()
    root.protocol("WM_DELETE_WINDOW", _quit)
    myapp = Gui(sublimator, master=root)

    myapp.master.title("Sublimator")
    myapp.master.minsize(860, 560)
    myapp.after(300, myapp.updatePlot)
    myapp.after(1000, myapp.updateConsole)
    myapp.mainloop()


# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def addTwoNumbers(self, l1, l2):  # O(N1 + N2) and O(1)
    def _rev_helper(node):  # reverse a linked list
        if not node: return
        prev, curr = None, node
        while curr:
            next_n = curr.next
            curr.next = prev
            prev = curr
            curr = next_n
        return prev

    l3 = _rev_helper(l1)
    l4 = _rev_helper(l2)
    l5 = dummy = ListNode(-1)
    carry = 0
    while l3 or l4:  # important to do with or. With "and" will be hard to propagate further
        val3 = l3.val if l3 else 0
        val4 = l4.val if l4 else 0
        value = (val3 + val4 + carry) % 10
        carry = (val3 + val4 + carry) // 10
        l5.next = ListNode(value)
        l5 = l5.next
        l3 = l3.next if l3 else None
        l4 = l4.next if l4 else None
    if carry:
        l5.next = ListNode(1)
    return _rev_helper(dummy.next)

def addTwoNumbers_no_reverse(self, l1, l2):  # O(N1 + N2) and O(1)
    """
    Follow-up w/o reversing lists
    """
    n1 = n2 = 0
    while l1:
        n1 *= 10
        n1 += l1.val
        l1 = l1.next
    while l2:
        n2 *= 10
        n2 += l2.val
        l2 = l2.next
    n3 = n1 + n2

    ##turn n3 to linked list
    prev = ListNode(-1)
    head = prev
    for i in str(n3):
        n = ListNode(i)
        head.next = n
        head = n
    return prev.next

def addTwoNumbers_deque(self, l1, l2):
    q1 = collections.deque()
    q2 = collections.deque()
    while l1 or l2:
        if l1 and l2:
            q1.append(l1.val)
            q2.append(l2.val)
            l1 = l1.next
            l2 = l2.next
        elif l1 and not l2:
            q1.append(l1.val)
            q2.appendleft(0)
            l1 = l1.next
        else:
            q1.appendleft(0)
            q2.append(l2.val)
            l2 = l2.next
    carry = 0
    cur = None
    while q1 and q1:
        n1, n2 = q1.pop(), q2.pop()
        if n1 + n2 + carry >= 10:
            cur = ListNode(n1 + n2 + carry - 10, cur)
            carry = 1
        else:
            cur = ListNode(n1 + n2 + carry, cur)
            carry = 0
    if carry == 1:
        return ListNode(1, cur)
    else:
        return cur