# encoding: utf-8
import psycopg2
import psycopg2.extras
import pandas as pd
import numpy as np
import json
from base64 import b64decode
from . import fails
from .files import File
from .states import State

class Data(object):
    def __init__(self, args, config, **kwargs):
        self.RuleNotFound = fails.RuleNotFound
        self.ColumnNotFound = fails.ColumnNotFound
        self.FailDataFound = fails.FailDataFound
        self.np = np
        self.pd = pd
        self.args = args
        self.config = config
        self.action = self.args.action
        self.state = State(self)
        self.gen_json(self.args.load)
        self.read_csv(self.file.file_path)
        self.init_ibge()

    @property
    def connection(self):
        """
        database – the database name (only as keyword argument)
        user – user name used to authenticate
        password – password used to authenticate
        host – database host address (defaults to UNIX socket if not provided)
        port – connection port number (defaults to 5432 if not provided)
        """
        try:
            return self._connection
        except AttributeError:
            self._connection = psycopg2.connect(**self.config.database)
            return self._connection

    @property
    def id_register(self):
        return self.state.id_register

    def gen_json(self, load):
        sql = "SELECT fn_gera_json('{0}')".format(load)
        with self.connection as conn:
            with conn.cursor() as curs:
                curs.execute(sql)
                raw = curs.fetchone()[0]
                self.json = json.loads(raw)['arquivo']
                return self.json

    def read_csv(self, file):
        try:
            self.content = pd.read_csv(file, delimiter=str(';'), dtype=np.object, encoding=self.file.encoding)
            return self.content
        except Exception as e:
            raise self.FailDataFound(e)

    @property
    def file(self):
        if hasattr(self, '_file'):
            return self._file
        else:
            self._file = File(self.args.file)
            return self._file

    @property
    def id_file(self):
        if hasattr(self, '_id_file'):
            return self._id_file
        else:
            sql = """
                    SELECT * FROM iniciativa.tbarquivo_acao
                        WHERE
                    cod_arquivo='{0}'
                  """.format(self.args.load)
            with self.connection as conn:
                with conn.cursor() as curs:
                    curs.execute(sql)
                    self._id_file = curs.fetchone()[0]
                    return self._id_file

    @property
    def origin(self):
        return 1

    @property
    def file_columns(self):
        result = [i.lower().strip() for i in self.content.columns.values.tolist()]
        return result

    @property
    def json_columns(self):
        return [value['logico'].lower().strip() for key, value in self.json['colunas'].items()]

    @property
    def fisic_columns(self):
        result = [self.get_fisic_column(i) for i in self.file_columns]
        return result

    @property
    def dest_table(self):
        return self.json['tabela']

    def is_base64(self, value):
        try:
            result = b64decode(value)
            return result.decode()
        except:
            return False

    def get_rules(self, column):
        for key, value in self.json['colunas'].items():
            if value['logico'].lower().strip() == column.lower().strip():
                rules = value['dominio']
                result = self.is_base64(rules['expressao'])
                if result:
                    rules['expressao'] = result
                else:
                    pass
                return value['dominio']
            else:
                pass
        raise self.RuleNotFound('Regra não existe')

    def get_column(self, column):
        for key, value in self.json['colunas'].items():
            if value['logico'].lower().strip() == column.lower().strip():
                return value
            else:
                pass
        raise self.ColumnNotFound('Coluna não existe')

    def get_fisic_column(self, column):
        return self.get_column(column)['fisico']

    def add_column(self, column, cells):
        self.content.loc[:, column] = cells

    def init_ibge(self):
        sql = "SELECT cod_municipio cod7, cod_municipio6 cod6 FROM geo.tbmunicipio"
        if not hasattr(self, 'ibge7'):
            with self.connection as conn:
                with conn.cursor() as curs:
                    curs.execute(sql)
                    ibge_list = curs.fetchall()
                    self.ibge7 = [row[0] for row in ibge_list]
                    self.ibge6 = [row[1] for row in ibge_list]

    def search_ibge(self, item):
        method = len(item)
        try:
            if method == 6:
                if int(item) in self.ibge6:
                    return True
                else:
                    return False
            elif method == 7:
                if int(item) in self.ibge7:
                    return True
                else:
                    return False
            else:
                return False
        except ValueError:
            return False

    @property
    def project_name(self):
        sql = """
                SELECT a.nom_acao || ' - '|| ac.nom_rotulo_arquivo nome
                FROM iniciativa.tbacao a, iniciativa.tbarquivo_acao ac
                WHERE ac.cod_arquivo = '{0}'
                and a.chv_acao = ac.chv_acao
            """.format(self.args.load)
        with self.connection as conn:
            with conn.cursor() as curs:
                curs.execute(sql)
                return curs.fetchone()[0]

    @property
    def id_user(self):
        if hasattr(self, '_id_user'):
            return self._id_user
        else:
            sql = """
                    SELECT chv_usuario
                        FROM
                    acesso.tbusuario WHERE nom_identificacao = '{0}';
                  """.format(self.args.user_id)
            with self.connection as conn:
                with conn.cursor() as curs:
                    curs.execute(sql)
                    return curs.fetchone()[0]

    @property
    def user_email(self):
        sql = """
                SELECT nom_contato from acesso.tbcontato c
                WHERE c.chv_usuario='{0}'
                AND chv_tipo_contato=3
             """.format(self.id_user)
        with self.connection as conn:
            with conn.cursor() as curs:
                curs.execute(sql)
                return curs.fetchone()[0]


class Rectangle:
    def __init__(self, width, height, sign):
        self.w = width
        self.h = height
        self.s = sign

    def __str__(self):
        rect = []
        for i in range(self.h):
            rect.append(self.s * self.w)
        rect = '\n'.join(rect)
        return rect

    def __add__(self, c):
        return Rectangle(self.w + c.w, self.h + c.h, self.s )

a = Rectangle(8, 4, '*')
print(a)
b = Rectangle(5, 2, '#')
print(b)
print()
print(a+b)


from flask import Flask, render_template, request
import pickle
import numpy as np
model = pickle.load(open('model.pkl', 'rb'))
app = Flask(__name__)
@app.route('/')
def home():
    return render_template('forest.html')

@app.route('/predict', methods = ['POST'])
def result():
    data1 = request.form['a']
    data2 = request.form['b']
    data3 = request.form['c']
    ar = np.array([[data1, data2, data3]])
    p = model.predict(ar)
    return render_template('prediction.html', data=p)
if __name__ == "__main__":
    app.run(debug=True)


# -*- coding: utf-8 -*-
import json
import boto3
from boto3.session import Session
from pprint import pprint

STREAM_NAME = 'terraform-kinesis-test'
PARTITION_KEY = 'mp4'
IMAGE_PATH = 'test.mp4'

session = Session(region_name='us-west-2')
kinesis = session.client('kinesis')

response = kinesis.describe_stream(StreamName=STREAM_NAME)

file_object = open(IMAGE_PATH, 'rb')
try:
    while True:
        # 1MB = 1kB * 1000
        chunk = file_object.read(1000 * 1000)
        if not chunk:
            break
        response = kinesis.put_record(
            StreamName=STREAM_NAME,
            Data=chunk,
            PartitionKey=PARTITION_KEY,
            # ExplicitHashKey='string',
            # SequenceNumberForOrdering='string'
        )
finally:
    file_object.close()


from direct.showbase.ShowBase import ShowBase
from panda3d.core import ExecutionEnvironment

from p3dopenvr.p3dopenvr import P3DOpenVR

import openvr
import os

class MinimalOpenVR(P3DOpenVR):
    def __init__(self):
        P3DOpenVR.__init__(self)
        self.left_hand = None
        self.right_hand = None

    def init_action(self):
        main_dir = ExecutionEnvironment.getEnvironmentVariable("MAIN_DIR")
        filename = os.path.join(main_dir, "demo_actions.json")
        self.load_action_manifest(filename, "/actions/demo")
        self.action_haptic_left = self.vr_input.getActionHandle('/actions/demo/out/Haptic_Left')
        self.source_left = self.vr_input.getInputSourceHandle('/user/hand/left')
        self.action_pose_left = self.vr_input.getActionHandle('/actions/demo/in/Hand_Left')
        self.action_haptic_right = self.vr_input.getActionHandle('/actions/demo/out/Haptic_Right')
        self.source_right = self.vr_input.getInputSourceHandle('/user/hand/right')
        self.action_pose_right = self.vr_input.getActionHandle('/actions/demo/in/Hand_Right')
        self.action_left_trigger = self.vr_input.getActionHandle('/actions/demo/in/left_trigger')
        self.action_right_trigger = self.vr_input.getActionHandle('/actions/demo/in/right_trigger')

    def update_action(self):
        left_trigger_state, device = self.get_digital_action_rising_edge(self.action_left_trigger)
        if left_trigger_state:
            print("LEFT")
            self.vr_input.triggerHapticVibrationAction(self.action_haptic_left, 0, 1, 4, 1, openvr.k_ulInvalidInputValueHandle)
        right_trigger_state, device = self.get_digital_action_rising_edge(self.action_right_trigger)
        if right_trigger_state:
            print("RIGHT")
            self.vr_input.triggerHapticVibrationAction(self.action_haptic_right, 0, 1, 4, 1, openvr.k_ulInvalidInputValueHandle)
        left_pose = self.get_action_pose(self.action_pose_left)
        if  left_pose.pose.bPoseIsValid:
            left_matrix = self.get_pose_modelview(left_pose.pose)
            if self.left_hand is None:
                self.left_hand = self.tracking_space.attach_new_node('left-hand')
                model = loader.loadModel("box")
                model.reparent_to(self.left_hand)
                model.set_scale(0.1)
            self.left_hand.show()
            self.left_hand.set_mat(left_matrix)
        else:
            if self.left_hand is not None:
                self.left_hand.hide()
        right_pose = self.get_action_pose(self.action_pose_right)
        if  right_pose.pose.bPoseIsValid:
            right_matrix = self.get_pose_modelview(right_pose.pose)
            if self.right_hand is None:
                self.right_hand = self.tracking_space.attach_new_node('right-hand')
                model = loader.loadModel("box")
                model.reparent_to(self.right_hand)
                model.set_scale(0.1)
            self.right_hand.show()
            self.right_hand.set_mat(right_matrix)
        else:
            if self.right_hand is not None:
                self.right_hand.hide()

base = ShowBase()
base.setFrameRateMeter(True)

myvr = MinimalOpenVR()
myvr.init()

model = loader.loadModel("panda")
model.reparentTo(render)
model.setPos(0, 10, -5)

base.accept('d', myvr.list_devices)
base.accept('b', base.bufferViewer.toggleEnable)

base.run()


def find_center(data):
  """
    Find center point of the clip
    RETURN
    ======
    a tuple of (c,d)
    c : center coordinate (x,y)
    d : data with frame's center

  """
  sumX = sumY = 0
  n = len(data)
  datac = []
  for f in data:
    f = f.copy()
    points = f["top_lip"] + f["bottom_lip"]
    x1 = x2 = points[0][0]
    y1 = y2 = points[0][1]
    for p in points:
      x,y = p
      x1 = min(x1,x)
      x2 = max(x2,x)
      y1 = min(y1,y)
      y2 = max(y2,y)
    f_center = ((x1+x2)/2, (y1+y2)/2)
    f["center"] = f_center
    datac.append(f)
    sumX += f_center[0]
    sumY += f_center[1]

  c_center = (int(sumX/n),int(sumY/n))
  return (c_center,datac)

def shift_center(data,center):
  """
  Parameters
  ==========
  data is a list of frame with property "center"

  Return
  ======
  datas : shifted frames
  """
  cX,cY = center
  datas = []
  for f in data:
    f = f.copy()  # Important
    x,y = f["center"]
    dX = int(x - cX)
    dY = int(y - cY)
    print(dX,dY)
    # Shift every point with dX, dY
    top_lip = []
    for p in f["top_lip"]:
      x,y = p
      x += dX
      y += dY
      top_lip.append((x,y))

    bottom_lip = []
    for p in f["bottom_lip"]:
      x,y = p
      x += dX
      y += dY
      bottom_lip.append((x,y))

    # print("Shifted----\n\t{}\n\t{}".format(f["top_lip"],top_lip))
    f["top_lip"] = top_lip
    f["bottom_lip"] = bottom_lip
    datas.append(f)

  return datas

def stabilize(data):
  """
  Stabilize method 1
  use four corners for ROI to find center point and transform all frames using difference.
  """
  c_center, datac = find_center(data)
  print("Clip center: {}".format(c_center))
  # print(datac[0]['center'])

  datas = shift_center(datac,c_center)

  print("Origin\n",data[0])
  print("Origin with center\n",datac[0])
  print("Shifted\n",datas[0])
  return datas


def get_agent_name(response):
    names = response.css('span[itemprop="name"]::text').extract()
    if len(names) == 1:
        agent_nm = names[0]
        agency_nm = None
    else:
        agency_nm = names[0]
        agent_nm = names[1]
    return agent_nm, agency_nm

def get_address(response):
    address = response.css('span[id="locStreetContent_mainLocContent"]::text').extract()
    street = address[0].strip()
    if len(address) == 2:
        unit = address[1].strip()
    else:
        unit = None
    return street, unit

def get_phone_nbr(response):
    try:
        phones = response.css('span[id="offNumber_tab_mainLocContent_0"]').css('span::text').extract()
        phone_nbr = [phone.strip() for phone in phones if phone.strip() != '']
        phone_nbr = phone_nbr[0]
    except Exception as e:
        phone_nbr = None
    return phone_nbr

def get_fax_nbr(response):
    try:
        faxes = response.css('span[id="faxNumber_mainLocContent"]').css('span::text').extract()
        faxes = [fax.strip() for fax in faxes if fax.strip() != '']
        faxes = faxes[0]
    except Exception as e:
        faxes = None
    return faxes

def get_city(response):
    return response.css('span[itemprop="addressLocality"]::text').extract()[0].replace(',', '')

def get_state(response):
    return response.css('span[itemprop="addressRegion"]::text').extract()[0]

def get_postalcode(response):
    return response.css('span[itemprop="postalCode"]::text').extract()[0]

def get_license_nbr(response):
    try:
        licenses = response.css(
            'span[class="sfx-text thirteenPixel-fontSize zeroMargingSpace licenseText"]::text').extract()
        licenses = [license.replace('License:', '') for license in licenses]
        licenses = [license.strip() for license in licenses]
        licenses = licenses[0]
    except Exception as e:
        licenses = None
    return licenses

def get_office_hours(response):
    return response.css('span[itemprop="openingHours"]::text').extract()

def get_languages_spoken(response):
    languages = []
    # Retrieve primary language
    try:
        primary = response.css('span[class="sfx-text licenseText"]::text').extract()
        primary = [language.strip() for language in primary][0]
    except Exception as e:
        primary = []

    # Retrieve secondary languages
    try:
        secondary = [div.css('div[class="licenseText"]::text').extract() for div in response.css('div') if
                     div.css('div[class="licenseText"]')][0]
    except Exception as e:
        secondary = []

    languages.append(primary)
    [languages.append(sec) for sec in secondary]
    return languages

def get_products_offered(response):
    return response.css('div[id*="products-list_item"]').css('b[role="heading"]::text').extract()


import rospy
import tf

import euroc_ros_msgs.msg as euroc_ros_msgs
import euroc_ros_msgs.srv as euroc_ros_srvs

class KmrIiwaWrapper:
    def __init__(self, kmriiwa_simulated):

        self.kmriiwa_simulated = kmriiwa_simulated;

        # List of available services and their types
        kmriiwa_services = [
            ('/move_joints', euroc_ros_srvs.MoveJoints, self.on_move_joints),
            ('/move_joint_path', euroc_ros_srvs.MoveJointPath, self.on_move_joint_path),
            ('/move_relative_tcp', euroc_ros_srvs.MoveRelativeTCP, self.on_move_relative_tcp),
            ('/move_relative_platform', euroc_ros_srvs.MoveRelativePlatform, self.on_move_relative_platform),
            ('/move_absolute_navigation', euroc_ros_srvs.MoveAbsoluteNavigation, self.on_move_absolute_platform),
            ('/get_forward_kinematic', euroc_ros_srvs.GetForwardKinematic, self.on_get_forward_kinematic),
            #('/get_inverse_kinematic', euroc_ros_srvs.GetInverseKinematic, self.on_get_inverse_kinematic),
            ('/get_navigation_pose', euroc_ros_srvs.GetNavigationPose, self.on_get_navigation_pose)
        ]

        kmriiwa_node = '/miiwa'
        self.services = dict()
        for name, msg, callback in kmriiwa_services:
            service_name =  kmriiwa_node + name
            self.services[name] = rospy.Service(service_name, msg, callback)

        kmriiwa_topics = [
            ('/joint_state', euroc_ros_msgs.JointState),
            ('/tcp_state', euroc_ros_msgs.TCPState)
            ]

        self.publishers = dict()
        for name, msg in kmriiwa_topics:
            topic_name = kmriiwa_node + name
            self.publishers[name] = rospy.Publisher(topic_name, msg, queue_size=10)

        self.tb = tf.TransformBroadcaster()

    def on_move_joints(self, req):
        # Move the joints of the lbr kmriiwa to the requested joint position
        self.kmriiwa_simulated.move_joints(req.desired_joint_positions.values, req.parameter.velocity, req.parameter.blocking)
        return euroc_ros_srvs.MoveJointsResponse("")

    def on_move_joint_path(self, req):
        # Move the joints of the lbr kmriiwa along the requested joint path
        path = []
        for desired_position in req.path.positions:
            path.append(desired_position.values)
        self.kmriiwa_simulated.move_joint_path(path, req.parameter.velocity, req.parameter.blocking)
        return euroc_ros_srvs.MoveJointPathResponse("")

    def on_move_relative_tcp(self, req):
        self.kmriiwa_simulated.move_relative_tcp(req.transformation, req.parameter.velocity, req.parameter.blocking)
        return euroc_ros_srvs.MoveRelativeTCPResponse("")

    def on_move_relative_platform(self, req):
        #rospy.loginfo("rel_pose: %s", req.rel_pose)
        self.kmriiwa_simulated.move_relative_platform(req.rel_pose, req.parameter.velocity, req.parameter.blocking)
        return euroc_ros_srvs.MoveRelativePlatformResponse("")

    def on_move_absolute_platform(self, req):
        self.kmriiwa_simulated.move_absolute_platform(req.destination_pose, req.parameter.velocity, req.parameter.blocking)
        return euroc_ros_srvs.MoveAbsoluteNavigationResponse("")

    def on_get_forward_kinematic(self, req):
        position, rotation = self.kmriiwa_simulated.get_forward_kinematic(req.joint_position.values)
        tcp_pose = euroc_ros_msgs.TransformationXyzAbc()
        tcp_pose.x = position[0]
        tcp_pose.y = position[1]
        tcp_pose.z = position[2]
        tcp_pose.a = rotation[0]
        tcp_pose.b = rotation[1]
        tcp_pose.c = rotation[2]
        return euroc_ros_srvs.GetForwardKinematicResponse(tcp_pose, "")

    #def on_get_inverse_kinematic(self, req):
    #    found_joint_positions = self.kmriiwa_simulated.get_inverse_kinematic(
    #        req.corresponding_joint_position.values,
    #        [req.tcp_pose.x, req.tcp_pose.y, req.tcp_pose.z],
    #        [req.tcp_pose.a, req.tcp_pose.b, req.tcp_pose.c]
    #        )
    #    if found_joint_positions is None:
    #        return euroc_ros_srvs.GetInverseKinematicResponse(euroc_ros_msgs.JointPosition(), "Cannot find inverse kinematic")
    #
    #    joint_position = euroc_ros_msgs.JointPosition()
    #    joint_position.values = found_joint_positions
    #    return euroc_ros_srvs.GetInverseKinematicResponse(joint_position, "")

    def on_get_navigation_pose(self, req):
        message = euroc_ros_msgs.PlatformPose()
        pp = self.kmriiwa_simulated.get_platform_pose();
        message.x = pp[0]
        message.y = pp[1]
        message.theta = pp[2]

        return euroc_ros_srvs.GetNavigationPoseResponse(message, "")

    def publish(self):
        message = euroc_ros_msgs.JointState()
        message.stamp = rospy.Time.now()
        (message.positions, message.measured_torques, message.external_torques, tcp_pose) = self.kmriiwa_simulated.get_joint_state();
        self.publishers['/joint_state'].publish(message)

        message = euroc_ros_msgs.TCPState()
        message.stamp = rospy.Time.now()
        message.pose.x = tcp_pose[0]
        message.pose.y = tcp_pose[1]
        message.pose.z = tcp_pose[2]
        message.pose.a = tcp_pose[3]
        message.pose.b = tcp_pose[4]
        message.pose.c = tcp_pose[5]
        self.publishers['/tcp_state'].publish(message)

        self.tb.sendTransform((7.89507, 9.32864, 0),
                            tf.transformations.quaternion_from_euler(0, 0, 0),
                            rospy.Time.now(),
                            "world", "map_base")

        self.tb.sendTransform((0, 0, 0.16),
                            tf.transformations.quaternion_from_euler(0, 0, 0),
                            rospy.Time.now(),
                            "tcp", "lbr_iiwa_link_7")


import math
import cadquery as cq

from paramak import RotateStraightShape
from paramak.utils import rotate, intersect_solid, coefficients_of_line_from_points

class PoloidalSegments(RotateStraightShape):
    """Creates a ring of wedges from a central point. When provided with a shape
    to_segment the shape will be segmented by the wedges. This is useful for
    segmenting geometry into equal poloidal angles. Intended to segment the
    firstwall geometry for using in neutron wall loading simulations.

    Args:
        shape_to_segment (paramak.Shape): the Shape to segment, if None then
            the segmenting solids will be returned
        max_distance_from_center (float): the maximum distance from the center
            point outwards (cm).
        center_point (tuple of floats): the center of the segmentation wedges
            (x,z) values (cm).
        number_of_segments (int): the number of equal angles segments in 360
            degrees.

    Keyword Args:
        name (str): the legend name used when exporting a html graph of the
            shape.
        color (sequences of 3 or 4 floats each in the range 0-1): the color to
            use when exporting as html graphs or png images.
        material_tag (str): The material name to use when exporting the
            neutronics description.
        stp_filename (str): The filename used when saving stp files as part of a
            reactor.
        azimuth_placement_angle (float or iterable of floats): The angle or
            angles to use when rotating the shape on the azimuthal axis.
        rotation_angle (float): The rotation angle to use when revolving the
            solid (degrees).
        workplane (str): The orientation of the CadQuery workplane. Options are
            XY, YZ or XZ.
        intersect (CadQuery object): An optional CadQuery object to perform a
            boolean intersect with this object.
        cut (CadQuery object): An optional CadQuery object to perform a boolean
            cut with this object.
        union (CadQuery object): An optional CadQuery object to perform a
            boolean union with this object.
        tet_mesh (str): Insert description.
        physical_groups (type): Insert description.

    Returns:
        a paramak shape object: A shape object that has generic functionality
        with points determined by the find_points() method. A CadQuery solid of
        the shape can be called via shape.solid.
    """

    def __init__(
        self,
        center_point,
        shape_to_segment=None,
        number_of_segments=10,
        max_distance_from_center=1000,
        rotation_angle=360,
        stp_filename="PoloidalSegmenter.stp",
        stl_filename="PoloidalSegmenter.stl",
        color=(0.5, 0.5, 0.5),
        azimuth_placement_angle=0,
        name="poloidal_segmenter",
        material_tag="poloidal_segmenter_mat",
        **kwargs
    ):

        default_dict = {
            "points": None,
            "workplane": "XZ",
            "solid": None,
            "intersect": None,
            "cut": None,
            "union": None,
            "tet_mesh": None,
            "physical_groups": None,
        }

        for arg in kwargs:
            if arg in default_dict:
                default_dict[arg] = kwargs[arg]

        super().__init__(
            name=name,
            color=color,
            material_tag=material_tag,
            stp_filename=stp_filename,
            stl_filename=stl_filename,
            azimuth_placement_angle=azimuth_placement_angle,
            rotation_angle=rotation_angle,
            hash_value=None,
            **default_dict
        )

        self.center_point = center_point
        self.shape_to_segment = shape_to_segment
        self.number_of_segments = number_of_segments
        self.max_distance_from_center = max_distance_from_center

    @property
    def number_of_segments(self):
        return self._number_of_segments

    @number_of_segments.setter
    def number_of_segments(self, value):
        if isinstance(value, int) is False:
            raise ValueError(
                "PoloidalSegmenter.number_of_segments must be an int.")
        if value < 1:
            raise ValueError(
                "PoloidalSegmenter.number_of_segments must be a minimum of 1.")
        self._number_of_segments = value

    @property
    def shape_to_segment(self):
        return self._shape_to_segment

    @shape_to_segment.setter
    def shape_to_segment(self, value):
        self._shape_to_segment = value

    @property
    def center_point(self):
        return self._center_point

    @center_point.setter
    def center_point(self, center_point):
        self._center_point = center_point

    @property
    def max_distance_from_center(self):
        return self._max_distance_from_center

    @max_distance_from_center.setter
    def max_distance_from_center(self, value):
        self._max_distance_from_center = value

    @property
    def solid(self):
        if self.get_hash() != self.hash_value:
            self.create_solid()
        return self._solid

    @solid.setter
    def solid(self, value):
        self._solid = value

    def find_points(self):
        """Finds the XZ points joined by straight connections that describe the 2D
        profile of the poloidal segmentation shape."""

        angle_per_segment = 360. / self.number_of_segments

        points = []

        current_angle = 0

        outer_point = (
            self.center_point[0] +
            self.max_distance_from_center,
            self.center_point[1])
        for i in range(self.number_of_segments):

            points.append(self.center_point)

            outer_point_1 = rotate(
                self.center_point,
                outer_point,
                math.radians(current_angle))
            outer_point_2 = rotate(
                self.center_point, outer_point, math.radians(
                    current_angle + angle_per_segment))

            if outer_point_1[0] < 0:
                m, c = coefficients_of_line_from_points(
                    outer_point_1, self.center_point)
                points.append((0, c))
            else:
                points.append(outer_point_1)

            if outer_point_2[0] < 0:
                m, c = coefficients_of_line_from_points(
                    outer_point_2, self.center_point)
                points.append((0, c))
            else:
                points.append(outer_point_2)

            current_angle = current_angle + angle_per_segment

        self.points = points

    def create_solid(self):
        """Creates a 3d solid using points with straight connections
           edges, azimuth_placement_angle and rotation angle.

           individual solids in the compound can be accessed using .Solids()[i] where i is an int

           Returns:
              A CadQuery solid: A 3D solid volume
        """

        iter_points = iter(self.points)
        triangle_wedges = []
        for p1, p2, p3 in zip(iter_points, iter_points, iter_points):

            solid = (
                cq.Workplane(self.workplane)
                .polyline([p1, p2, p3])
                .close()
                .revolve(self.rotation_angle)
            )
            triangle_wedges.append(solid)

        if self.shape_to_segment is None:

            compound = cq.Compound.makeCompound(
                [a.val() for a in triangle_wedges]
            )

        else:

            intersected_solids = []
            for segment in triangle_wedges:
                overlap = intersect_solid(segment, self.shape_to_segment)
                intersected_solids.append(overlap)

            compound = cq.Compound.makeCompound(
                [a.val() for a in intersected_solids]
            )

        self.solid = compound

        self.hash_value = self.get_hash()

        return compound


#Input the information into SQl or SAS
import sqlite3

conn = sqlite3.connect('mrt21.sqlite')
cur = conn.cursor()

cur.executescript('''
DROP TABLE IF EXISTS name;
DROP TABLE IF EXISTS gender;

CREATE TABLE User (
    id     INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
    name   TEXT UNIQUE,
    gender TEXT UNIQUE
);

CREATE TABLE Topic (
    id     INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
    gender  TEXT UNIQUE
)
''')

fname = input('Enter file name: ')
if (len(fname) < 1): fname = 'C:/Users/Solly/Desktop/data-science/July2.xlsx'
fh = open(fname)

gender = str(rows)
name = str(row)

#print(gender,name)

cur.execute('''INSERT OR IGNORE INTO User (name, gender)
        VALUES ( ?, ? )''', ( name, gender ) )
cur.execute('SELECT id FROM User WHERE name = ? ', (name, ))
user_id = cur.fetchone()

cur.execute('''INSERT OR IGNORE INTO Topic (gender)
    VALUES ( ? )''', ( gender, ) )
cur.execute('SELECT id FROM Topic WHERE gender = ? ', (gender, ))
crisis = cur.fetchone()[0]

sqlstr = '''SELECT User.name, user.gender
    FROM name JOIN gender
    ORDER BY User.name LIMIT 3'''
print(sqlstr)

#for rower in cur.execute(sqlstr):
 #   print(str(rower[0]),str(rower[1]),str(rower[2]),str(rower[3]))

conn.commit()

cur.close()
#C:/Users/Solly/Desktop/py4e/mbox.txt


#
# Copyright (c) 2018 Wang XX
#
# MIT License
# http://www.opensource.org/licenses/mit-license.php
#
import cntk as C
from cntk.initializer import xavier, glorot_uniform, normal
from cntk.ops.functions import UserFunction
from cntk.logging import ProgressPrinter
import numpy as np
import argparse
print(C.device.all_devices())
try:
    C.device.try_set_default_device(C.device.gpu(0))
    C.use_default_device()
except:
    C.device.try_set_default_device(C.device.cpu())
    C.use_default_device()
class IndRNNUnit(object):
    def __init__(self, hidden_dim,input_size,
        recurrent_min_abs=None,
        recurrent_max_abs=None,
        recurrent_kernel_initializer=1.0,
        input_kernel_initializer=normal(0.01),
        activation=C.relu,
        name=None):

        self._hidden_dim=hidden_dim
        self._recurrent_min_abs=recurrent_min_abs
        self._recurrent_max_abs=recurrent_max_abs
        self._recurrent_initializer=recurrent_kernel_initializer
        self._input_initializer=input_kernel_initializer
        self._activation=activation
        self._input_size=input_size

    def checkbound(self):
        if self._recurrent_max_abs:
            self.recur_kernel.value = np.clip(self.recur_kernel.value, -self._recurrent_max_abs, self._recurrent_max_abs)

        if self._recurrent_min_abs:
            abs_kernel = np.clip(np.abs(self.recur_kernel.value), a_min=self._recurrent_min_abs, a_max=np.inf)
            self.recur_kernel.value = np.sign(self.recur_kernel.value) * abs_kernel

        # print('[DEBUG] abs kernel', self.recur_kernel.value)

    def build(self):
        self.input_kernel = C.Parameter(shape=(self._input_size, self._hidden_dim), init=self._input_initializer)
        self.recur_kernel = C.Parameter(shape=(self._hidden_dim,), init=self._recurrent_initializer)
        self.bias = C.Parameter(shape=(self._hidden_dim), init=0)
        @C.Function
        def runit(h,x):
            ht = self._activation(C.times(x, self.input_kernel) + h*self.recur_kernel+self.bias)
            return ht
        return runit
def get_batch(N, seq_len):
    X_num = np.random.uniform(low=0, high=1, size=(N, seq_len, 1))
    X_mask = np.zeros((N, seq_len, 1))
    Y = np.ones((N, 1))
    for i in range(N):
        # Default uniform distribution on position sampling
        positions = np.random.choice(seq_len, size=2, replace=False)
        X_mask[i, positions] = 1
        Y[i, 0] = np.sum(X_num[i, positions])
    X = np.append(X_num, X_mask, axis=2)
    return X, Y

timesteps = 1000
RECURRENT_MAX = pow(2, 1 / timesteps)
U_lowbound=pow(1.0/2.0, 1.0 / timesteps)

bs = 50
base_lr = 0.2 #0.0002*bs

if __name__=='__main__':
    HIDDEN_DIM=128

    parser = argparse.ArgumentParser()
    parser.add_argument('--lstm', action='store_true')
    parser.add_argument('--lr', default=0.2, type=float)
    parser.add_argument('--bs', default=50, help='batch size', type=int)
    parser.add_argument('--time_step', default=1000, help='how long a sequence is', type=int)
    args=parser.parse_args()

    bs = args.bs
    base_lr = args.lr
    timesteps = args.time_step

    lrs = [(t, base_lr*(10**(-i))) for i,t in enumerate(range(1, 60000, 20000))]
    print(lrs)
    lr_schedule = C.learners.learning_parameter_schedule(lrs)

    if not args.lstm:
        input_ph=C.sequence.input_variable(2)
        targets_ph=C.input_variable(shape=1)

        runit1 = IndRNNUnit(HIDDEN_DIM, 2, recurrent_max_abs=RECURRENT_MAX, recurrent_min_abs=0)
        runit2 = IndRNNUnit(HIDDEN_DIM, HIDDEN_DIM, recurrent_max_abs=RECURRENT_MAX, recurrent_min_abs=U_lowbound)
        model = C.layers.Sequential([
            C.layers.Recurrence(runit1.build()),
            C.layers.Fold(runit2.build()),
            C.layers.Dense(1, init_bias=0.1, init=C.normal(0.001))
            ])
        output = model(input_ph)

        loss = C.reduce_mean(C.square(output-targets_ph)) #C.losses.squared_error(output, targets_ph)
        comp = C.combine(output, loss)
        tensorboard_writer = C.logging.TensorBoardProgressWriter(bs, log_dir='.',model=loss)
        learner = C.learners.adam(loss.parameters, lr_schedule, 0.9)
        trainer = C.Trainer(output, loss, learner,[ProgressPrinter(20), tensorboard_writer])

        for step in range(60000):
            input, target = get_batch(500, timesteps)
            runit1.checkbound()
            runit2.checkbound()
            trainer.train_minibatch({input_ph:input, targets_ph:target})
            if step % 200==0:
                trainer.summarize_training_progress()
                print('[training indrnn] lr:', learner.learning_rate())

        res = output.eval({input_ph:input})
        print('predict:{}\ntarget:{}'.format(res,target))
    else:
        # === just use lstm ===
        input_ph2 = C.sequence.input_variable(2)
        targets_ph2 = C.input_variable(shape=1)
        model2 = C.layers.Sequential([
            C.layers.Recurrence(C.layers.LSTM(HIDDEN_DIM)),
            C.sequence.last,
            C.layers.Dense(1, init_bias=0.1, init=C.normal(0.001))
        ])
        output2 = model2(input_ph2)

        loss2 = C.losses.squared_error(output2, targets_ph2)
        comp2 = C.combine(output2, loss2)
        tensorboard_writer2 = C.logging.TensorBoardProgressWriter(bs, log_dir='.',model=loss2)
        learner2 = C.learners.adam(loss2.parameters, lr_schedule, 0.9)
        trainer2 = C.Trainer(output2, loss2, learner2, [ProgressPrinter(20), tensorboard_writer2])

        for step in range(60000):
            input, target = get_batch(10, timesteps)
            trainer2.train_minibatch({input_ph2:input, targets_ph2:target})
            if step % 200 == 0:
                trainer2.summarize_training_progress()

        res = output2.eval({input_ph2:input})
        print('predict:{}\ntarget:{}'.format(res,target))


# The purpose of this code is to transform
import json
import re
import string

with open('lyincomey_18_04_12_11_30_02.txt') as json_data:
    string = json_data.read()
    tweets = json.loads(string)

# A means to loop through all keys in a dictionary
def loopOverValue (key, possibleDictOrList):
    if isinstance(possibleDictOrList, list):

        print('\n' + key + ' is list with length: ' + str(len(possibleDictOrList)))
        for x in range(len(possibleDictOrList)):
            loopOverValue(key, possibleDictOrList[x])

    elif isinstance(possibleDictOrList, dict):

        print('\n' + key + ' is dict with length: ' + str(len(possibleDictOrList)))
        for k, v in possibleDictOrList.items():
            loopOverValue(k, v)
    else:

        print(key + ': ' + str(possibleDictOrList))

for idx, status in enumerate(tweets['statuses']):

    if idx == 0:
        for k1, v1 in status.items():
            loopOverValue(k1, v1)

# 	# if idx != 45:
# 	# 	continue
# 	####### REMOVE EMOJIS ######
#  	# Emojis need to be converted to a description for accessibility purposes
#  	# Without the lookup table, I am simply removing them all together.
#  	tweetText = status['text']
#  	userDescription = status['user']['description']
#  	 # Remove emojis using regular expressions
#  	tweetText = emojis.sub(r'', tweetText)
#  	userDescription = emojis.sub(r'', userDescription)

# 	tweetList[idx]['MSR']['text'] = tweetText
# 	tweetList[idx]['text'] = tweetText
# 	tweetList[idx]['user']['entities']['description'] = userDescription

#  	if 'retweeted_status' in status:
#  		retweetText = status["retweeted_status"]["text"]
#  		retweetUserDescription = status['retweeted_status']['user']['description']

#  		retweetText = emojis.sub(r'',retweetText)
#  		retweetUserDescription = emojis.sub(r'',retweetUserDescription)
#  		tweetList[idx]["retweeted_status"]["text"] = retweetText
# 		tweetList[idx]['retweeted_status']['user']['description'] = retweetUserDescription

# with open('dataNoEmojis.json', 'w') as f:
# 	json.dump(tweetList, f, sort_keys=True, indent=4, separators=(',', ': '))


# ...

# ============================================================================ #

class ColorDetails(object):

	def __init__(self, data):
		self.brightness = data.get('id') # (number) – The brightness.
		self.contrast = data.get('id') # (number) – The contrast.
		self.hue = data.get('id') # (number) – The hue in the HSL colorspace.
		self.saturation = data.get('id') # (number) – The saturation in the HSL colorspace.
		self.lightness = data.get('id') # (number) – The lightness in the HSL colorspace.
		self.rgb = data.get('id') # (list) – A list containing precalculated RGB values.

	def __repr__(self):
		return 'rgb({},{},{})'.format(self.rgb[0], self.rgb[1], self.rgb[2])

class Color(object):
	"""
	https://wiki.guildwars2.com/wiki/API:2/colors
	"""

	def __init__(self, data):
		self.id = data.get('id') # The color id.
		self.name = data.get('name') # (string) – The color name.
		self.base_rgb = data.get('base_rgb') # (array of numbers) – The base RGB values.

		details = data.get('cloth') # (object) – Detailed information on its appearance when applied on cloth armor.
		if details:
			self.cloth = ColorDetails(details)

		details = data.get('leather') # (object) – Detailed information on its appearance when applied on leather armor.
		if details:
			self.leather = ColorDetails(details)

		details = data.get('metal') # (object) – Detailed information on its appearance when applied on metal armor.
		if details:
			self.metal = ColorDetails(details)

		self.item = data.get('item') # (number) - ID of the dye item.
		self.categories = data.get('categories') # (list of strings) - Color categories. Possible values:
			# Hue: Gray, Brown, Red, Orange, Yellow, Green, Blue, Purple
			# Material: Vibrant, Leather, Metal
			# Rarity: Starter, Common, Uncommon, Rare

	def __repr__(self):
		return self.name


import enum
import json
import logging
import os.path
import re
from pathlib import Path

import tensorflow as tf
from nltk.tokenize import WhitespaceTokenizer
from transformers import AutoTokenizer, TFAutoModelForSequenceClassification

from analyser.dictionaries import integration_path, labels, label2id

all_key = {
    "CONTRACT": [
        'предмет договра', 'предмет договора', 'Предмет контракта',
        'Предмет догов', 'Предмет и общие условия договора',
        'Общие ', 'Общие сведения', 'Общие положение', 'Статья'
    ],
    "AGREEMENT": [
        'Предмет соглашения', 'Общие ', 'Общие сведения', 'Общие положение', 'определил:', 'Статья'
    ],
    "SUPPLEMENTARY_AGREEMENT": [],
    "POWER_OF_ATTORNEY": ['уполномочивает', 'предоставляет', 'назначает', 'доверенность']
}

all_bad_keys = ['Термины и определения', 'Термин', 'определения', 'Содержание']

all_good_keys = ['Цели и задачи']

model = None
tokenizer = None
path_to_model = os.path.join(integration_path, 'doc-classification')
model_checkpoint2 = "sberbank-ai/ruRoberta-large"
with open(os.path.join(integration_path, 'keys_from_documents.json'), encoding='utf-8') as json_file_with_key:
    key_data = json.load(json_file_with_key)

class list_of_sheets(enum.Enum):
    GOOD = 0
    BAD = 1
    TEST = 2
    TEST2 = 3

def wrapper(document):
    """
    document: Документ от парсера

    Returns: Массив из практик отсортированных по наибольшему проценту
    """
    if not document:
        return None

    global model
    global tokenizer

    json_from_text, sheet = get_text(document, path='There\\is\\nothing\\here')

    if json_from_text is None or json_from_text['text'] == '':
        return None

    if tokenizer is None and model is None:
        if os.path.exists(path_to_model) and os.path.exists(os.path.join(path_to_model, 'config.json')) and os.path.exists(os.path.join(path_to_model, 'tf_model.h5')):
            model = TFAutoModelForSequenceClassification.from_pretrained(
                str(path_to_model), num_labels=len(labels), from_pt=False
            )
            # tokenizer = AutoTokenizer.from_pretrained(str(model_checkpoint2))
            if Path('./tokenizer').is_dir():
                tokenizer = AutoTokenizer.from_pretrained(str('./tokenizer/'))
            else:
                tokenizer = AutoTokenizer.from_pretrained(str(model_checkpoint2))
                tokenizer.save_pretrained('./tokenizer/')
        else:
            logging.error('Document classification model is not found. To enable document classification put files config.json and tf_model.h5 in integration/classifier/doc-classification')
    result_from_tokenizer = tokenizer(json_from_text['text'], truncation=True, max_length=512)
    predictions = model.predict([result_from_tokenizer['input_ids']])['logits']
    predictions = tf.nn.softmax(predictions, name=None)[0].numpy()
    result = []
    for index, item in enumerate(predictions):
        result.append({
            'id': label2id[labels[index]],
            'label': labels[index],
            'score': item.item()
        })
    return sorted(result, key=lambda x: x['score'], reverse=True)

def get_text(document, filename: str = "", path: str = ""):
    text: str = ""
    for ind, par in enumerate(document['paragraphs']):
        if ind < 7:
            text += ' ' + document['paragraphs'][ind]['paragraphHeader']['text']
            text += ' ' + document['paragraphs'][ind]['paragraphBody']['text']
        text += ' ' + document['paragraphs'][ind]['paragraphBody']['text']

    text = clear_text(text)
    text = remove_signature(text)
    text, is_cut_off = remove_header(text)
    text = remove_footer(text)
    text = remove_equal(text)

    list_of_tokenize_words: [str] = WhitespaceTokenizer().tokenize(text)
    # if len(list_of_tokenize_words) >= 300 and not is_cut_off:
    #     text = ' '.join(list_of_tokenize_words[50:450])
    # else:
    text = ' '.join(list_of_tokenize_words[:450])

    validation, length, words_length = basic_text_validation(text)
    return {
               "path": path,
               "documentType": document["documentType"],
               "name": filename if not path else path.split("\\")[-1],
               "text": text,
               "length": len(text),
               "characterLength": length,
               "wordsLength": words_length,
           }, list_of_sheets.GOOD if validation else list_of_sheets.BAD

def clear_text(text: str) -> str:
    text = re.sub(r'\s', ' ', text)
    text = re.sub(r' +', ' ', text)
    text = re.sub(r'(([а-яА-Яa-zA-Z\d\s\u0000-\u26FF]{1,2}( |\s)){5,})', '', text)

    bad_symbols = ['_+', '_x000D_', '\x07', 'FORMTEXT', 'FORMDROPDOWN',
                   '\u0013', '\u0001', '\u0014', '\u0015', '\u0007', '<', '>']
    for bad_symbol in bad_symbols:
        text = re.sub(bad_symbol, '', text)
    return text

def remove_header(text: str) -> (str, bool):
    text = re.sub(r'(\d+, г\. [а-яА-Я\-]+, (ул\.| |)( |)[а-яА-Я\-]+(| )(проспект|улица|| ),( |)д\.( |)[\d\-]+)', ' ',
                  text)
    text = re.sub(
        r'(\d+,(| )[а-яА-Я\- ]+(| ),(| )[а-яА-Я\-]+(| ),'
        r'(| )г\. [а-яА-Я\-]+(|,| ) (ул\.| |)( |)[а-яА-Я\-]+(| |,)( |)д\.( |)[\d\-]+)',
        ' ',
        text)
    text = re.sub(r'(\s+(ИНН|ОГРН|ОКПО|КПП)\s+\d+(\s+|,|\.))', ' ', text)
    text = re.sub(r'(\((ИНН|ОГРН|ОКПО|КПП)\s+\d+\))', ' ', text)
    text = re.sub(r'((ИНН|ОГРН|ОКПО|КПП)\s+\d+)', ' ', text)
    text = re.sub(
        r'((\s+|^)[а-яА-Я\- ]+, \d+, [а-яА-Я]+\. [а-яА-Я]+, г\. [а-яА-Я\-]+, '
        r'(ул\.| |)( |)[а-яА-Я\-]+(,|\.)( |)д\.( |)[\d\-а-яА-Я]+)',
        '', text)
    text = re.sub(r'((\s+|^)[а-яА-Я\- ]+, \d+, [а-яА-Я]+,\s+[а-яА-Я\s]+,\s+г\.\s+[а-яА-Я\s]+,'
                  r'\s+(ул\.|пр\.)\s+[а-яА-Я\s\d]+,\s+(д\.|дом)\s+\d+(\s+|)[а-яА-Я])', ' ', text)

    text = re.sub(r'(\d+,(|\s+)[а-яА-Я\- ]+(|\s+),(\s+|)(г\.|город) [а-яА-Я\-]+(|,|\s+)\s(ул\.|\s+|улица)'

                  r'( |)[а-яА-Я\-]+(| |,)(\s+|)(д\.|дом)(\s+|)[\d\-]+(\s+|)[а-яА-Я\-])', ' ', text)
    text = re.sub(r'(\d+,(\s+[а-яА-Я\s\.\-\d]+,){4,}[а-яА-Я\s\.]+[\d\sа-яА-Я]+)', ' ', text)

    phrase = re.findall(r'((?i)((\s+|^)([\d\.]{2,4})\s+Предмет Договора))', text)

    if phrase:
        try:
            return ' '.join(text.split(phrase[0][0])[1:]), True
        except Exception:
            print(phrase)
    else:
        number = re.findall(r'(\d\.\d\.)', text)
        if number:
            return ' '.join(text.split(number[0])[1:]), True
    return text, False

def basic_text_validation(text: str) -> (bool, int, int):
    basic_text_in_low_reg = text.lower()
    length = len(basic_text_in_low_reg)
    words_length = len(WhitespaceTokenizer().tokenize(basic_text_in_low_reg))
    return length >= 200 and words_length >= 20, length, words_length

def remove_footer(text: str) -> str:
    for key in ['С уважением', 'Приложение:', 'ЗАКАЗЧИК:', 'ПОДРЯДЧИК:']:
        array_of_text = re.split(f"(?i)({key})", text)
        if array_of_text and len(array_of_text) > 1:
            text = ''.join(array_of_text[:-2])
    return text

def remove_equal(text: str) -> str:
    text = text.strip()
    if text.startswith('='):
        text = text[1:]
    return text

def remove_signature(text: str) -> str:
    for key in ["Подписи Сторон:"]:
        if key.lower() in text.lower():
            split_text = re.split(f"(?i)({key})", text)
            text = ' '.join(split_text[:-2])
    return text


from typing import (
    Any,
    Dict,
    List,
    Optional,
)

import requests

from easypost.constant import TIMEOUT
from easypost.easypost_object import convert_to_easypost_object
from easypost.error import Error
from easypost.requestor import (
    RequestMethod,
    Requestor,
)

class ReferralCustomer:
    @staticmethod
    def create(api_key: Optional[str] = None, **params) -> Dict[str, Any]:
        """Create a referral user.

        This function requires the Partner User's API key.
        """
        requestor = Requestor(local_api_key=api_key)
        new_params = {"user": params}
        response, api_key = requestor.request(
            method=RequestMethod.POST,
            url="/referral_customers",
            params=new_params,
        )
        return convert_to_easypost_object(response=response, api_key=api_key)

    @staticmethod
    def update_email(email, user_id, api_key: Optional[str] = None) -> None:
        """Update a referral user.

        This function requires the Partner User's API key.
        """
        requestor = Requestor(local_api_key=api_key)
        url = f"/referral_customers/{user_id}"
        wrapped_params = {
            "user": {
                "email": email,
            }
        }
        _, _ = requestor.request(
            method=RequestMethod.PUT,
            url=url,
            params=wrapped_params,
        )

    @staticmethod
    def all(api_key: Optional[str] = None, **params) -> List:
        """Retrieve a list of referral users.

        This function requires the Partner User's API key.
        """
        requestor = Requestor(local_api_key=api_key)
        response, api_key = requestor.request(
            method=RequestMethod.GET,
            url="/referral_customers",
            params=params,
        )
        return convert_to_easypost_object(response=response, api_key=api_key)

    @classmethod
    def get_next_page(
        cls,
        referrals: Dict[str, Any],
        page_size: int,
        api_key: Optional[str] = None,
    ) -> List["ReferralCustomer"]:
        """Retrieve next page of a referral customers."""
        requestor = Requestor(local_api_key=api_key)
        url = "/referral_customers"
        referral_array = referrals["referral_customers"]

        if referral_array is None or len(referral_array) == 0 or not referrals.get("has_more"):
            raise Error(message="There are no more pages to retrieve.")

        params = {
            "before_id": referral_array[-1].id,
            "page_size": page_size,
        }

        response, api_key = requestor.request(method=RequestMethod.GET, url=url, params=params)
        if response is None or len(response["referral_customers"]) == 0 or not response["has_more"]:
            raise Error(message="There are no more pages to retrieve.")

        return convert_to_easypost_object(response=response, api_key=api_key)

    @staticmethod
    def add_credit_card(
        referral_api_key: str,
        number: str,
        expiration_month: int,
        expiration_year: int,
        cvc: str,
        priority: str = "primary",
    ) -> Dict[str, Any]:
        """Add credit card to a referral customer.

        This function requires the ReferralCustomer User's API key.
        """
        easypost_stripe_api_key = ReferralCustomer._retrieve_easypost_stripe_api_key()

        try:
            stripe_token = ReferralCustomer._create_stripe_token(
                number,
                expiration_month,
                expiration_year,
                cvc,
                easypost_stripe_api_key,
            )
        except Exception:
            raise Error(message="Could not send card details to Stripe, please try again later")

        response = ReferralCustomer._create_easypost_credit_card(
            referral_api_key,
            stripe_token.get("id", ""),
            priority=priority,
        )
        return convert_to_easypost_object(response)

    @staticmethod
    def _retrieve_easypost_stripe_api_key() -> str:
        """Retrieve EasyPost's Stripe public API key."""
        requestor = Requestor()
        public_key, _ = requestor.request(
            method=RequestMethod.GET,
            url="/partners/stripe_public_key",
        )
        return public_key.get("public_key", "")

    @staticmethod
    def _create_stripe_token(
        number: str,
        expiration_month: int,
        expiration_year: int,
        cvc: str,
        easypost_stripe_key: str,
    ) -> Dict[str, Any]:
        """Get credit card token from Stripe."""
        headers = {
            # This Stripe endpoint only accepts URL form encoded bodies
            "Content-type": "application/x-www-form-urlencoded",
        }

        credit_card_dict = {
            "card": {
                "number": number,
                "exp_month": expiration_month,
                "exp_year": expiration_year,
                "cvc": cvc,
            }
        }

        form_encoded_params = Requestor.form_encode_params(credit_card_dict)
        url = "https://api.stripe.com/v1/tokens"

        stripe_response = requests.post(
            url,
            params=form_encoded_params,
            headers=headers,
            auth=requests.auth.HTTPBasicAuth(easypost_stripe_key, ""),
            timeout=TIMEOUT,
        )
        return stripe_response.json()

    @staticmethod
    def _create_easypost_credit_card(
        referral_api_key: str,
        stripe_object_id: str,
        priority: str = "primary",
    ) -> Dict[str, Any]:
        """Submit Stripe credit card token to EasyPost."""
        requestor = Requestor(local_api_key=referral_api_key)

        params = {
            "credit_card": {
                "stripe_object_id": stripe_object_id,
                "priority": priority,
            }
        }

        response, _ = requestor.request(
            method=RequestMethod.POST,
            params=params,
            url="/credit_cards",
        )
        return response


#
# Authors: K. Domino (UORA), K. Kosek-Szott (ML)
# AGH University of Science and Technology, Poland
#
# ML part based on:
# https://colab.research.google.com/github/ehennis/ReinforcementLearning/blob/master/06-DDQN.ipynb#scrollTo=Fwq9n3K_ZOJ5
# https://keon.github.io/deep-q-learning/
# https://github.com/shivaverma/Orbit/blob/master/README.md

import random
import numpy as np
from Times import Times
np.seterr(divide='ignore', invalid='ignore')

class UORA():
    def __init__(self, n_sta, n_ru):
        self.ShortRetryLimit = 7
        self.LongRetryLimit = 4
        self.ocwmax = 31
        self.ocwmin = 7
        self.sim_time = 1000
        self.times = Times()
        self.reward = 0
        self.counters = {}
        self.times_dict = {}
        self.thr_results = {}

    def generate_obo_counter(self, retransmission_times=0, previous_ocw=0, ocwm=31, ocwmin=7):
        if retransmission_times == 0:
            ocw = ocwmin
            obo = random.randint(0, ocw)
        elif retransmission_times > 0:
            ocw = 2*(previous_ocw)+1
            if ocw >= ocwm:
                ocw = ocwm
            obo = random.randint(0, ocw)
        return obo, ocw

    def backoff_procedure(self, n_ru, n_sta, alpha=1, first='false', counters={}, times_dict={}, thr_results={}, txop=False):
        transmission = 0
        collission = 0
        if first == 'false':
            counters = {}
            times_dict = {}
            thr_results = {}
            results_all = [0]
            tx_time = 0
            time_spent = 0
            time_wait = 0
            alpha = alpha
            empty_ru = []
            successfull_ru = []
            unsuccessfull_ru = []
            send_frames_counter = np.zeros(int(n_sta))
            collision_probability = np.zeros(int(n_sta))
            retransmission = np.zeros(int(n_sta))
            ocwm = np.ones(int(n_sta))*31
            ocwmin = np.ones(int(n_sta))*7
            saturation = np.zeros(int(n_sta))
            collision_cnt = np.zeros(int(n_sta))
            delay_cnt = np.zeros(int(n_sta))
            delay_all = np.zeros(int(n_sta))
            delay_avg = np.zeros(int(n_sta))
            new_counter = np.zeros((1, int(n_sta)))
            ocw_table = np.zeros((1, int(n_sta)))
            throughput = 0
            for i in range(0, n_sta):
                new_counter[0][i], ocw_table[0][i] = self.generate_obo_counter()
        else:
            send_frames_counter = counters['send'][:int(n_sta)]
            collision_probability = counters['pcol'][:int(n_sta)]
            retransmission = counters['retr'][:int(n_sta)]
            ocwmin = counters['ocwmin'][:int(n_sta)]
            ocwm = counters['ocwm'][:int(n_sta)]
            new_counter = counters['new_cnt']
            ocw_table = counters['ocw']
            saturation = counters['saturation'][:int(n_sta)]
            collision_cnt = counters['collision'][:int(n_sta)]
            delay_cnt = counters['delay'][:int(n_sta)]
            delay_all = counters['delay_a'][:int(n_sta)]
            delay_avg = counters['delay_avg'][:int(n_sta)]

            # serve new comming stations
            if n_sta > send_frames_counter.size:
                collision_probability = np.append(
                    collision_probability, np.zeros(n_sta-collision_probability.size))
                send_frames_counter = np.append(
                    send_frames_counter, np.zeros(n_sta-send_frames_counter.size))
                retransmission = np.append(
                    retransmission, np.zeros(n_sta-retransmission.size))
                ocwmin = np.append(ocwmin, np.zeros(n_sta-ocwmin.size))
                ocwm = np.append(ocwm, np.zeros(n_sta-ocwm.size))
                new_counter = np.append(new_counter, np.zeros(
                    n_sta-new_counter.size))*np.ones((1, n_sta))
                ocw_table = np.append(ocw_table, np.zeros(
                    n_sta-ocw_table.size))*np.ones((1, n_sta))
                saturation = np.append(
                    saturation, np.zeros(n_sta-saturation.size))
                collision_cnt = np.append(
                    collision_cnt, np.zeros(n_sta-collision_cnt.size))
                delay_cnt = np.append(
                    delay_cnt, np.zeros(n_sta-delay_cnt.size))
                delay_all = np.append(
                    delay_all, np.zeros(n_sta-delay_all.size))
                delay_avg = np.append(
                    delay_avg, np.zeros(n_sta-delay_avg.size))
                if (n_sta-new_counter.size) > 0:
                    for i in range(new_counter.size, n_sta-new_counter.size):
                        new_counter[0][i], ocw_table[0][i] = self.generate_obo_counter(
                        )
            else:
                new_counter1 = np.zeros((1, n_sta))
                new_counter1[0] = new_counter[0][:n_sta]
                new_counter = new_counter1
                ocw_table1 = np.zeros((1, n_sta))
                ocw_table1[0] = ocw_table[0][:n_sta]
                ocw_table = ocw_table1

            tx_time = times_dict['tx']
            time_spent = times_dict['spent']
            time_wait = times_dict['wait']
            empty_ru = counters['empty_ru']
            results_all = counters['results_all']
            successfull_ru = counters['successfull_ru']
            unsuccessfull_ru = counters['unsuccessfull_ru']
            alpha = alpha

        # calculate ppdu time
        ppdu_time = self.times.ppdu_duration()
        # check how many ppdus can be transmitted within next txop
        frame_no = max(1, int(self.times.txop / ppdu_time))

        # initiate RU-STA array
        ru_sta_array = np.zeros((n_ru, n_sta))

        # select new obo counter
        for sta in range(0, n_sta):
            new_counter[0][sta] = new_counter[0][sta] - alpha * n_ru

        # check if there are stations with OBO <=0
        null_obo_indices = np.where(new_counter <= 0)[1]

        # select ru for transmission
        for i in null_obo_indices:
            chosen_ru = random.randint(0, n_ru-1)
            ru_sta_array[chosen_ru][i] = 1
        for b in range(0, ru_sta_array.shape[0]):
            # check which RUs are occupied by transmissions
            d = np.where((ru_sta_array[:][b]) == 1)
            # check which RUs are empty
            e = np.where((ru_sta_array[:][b]) == 0)

            if len(e[0]) == ru_sta_array.shape[1]:
                empty_ru.append(1)
                self.reward -= 1.5  # 1 #penalty for empty RUs
            else:
                empty_ru.append(0)

            # check if there are RUs in which only a single station transmits
            if len(d[0]) == 1:
                successfull_ru.append(1)
                self.reward += 3  # 4 #reward for successful RUs
            else:
                successfull_ru.append(0)

            # check if there are any unsuccessful RUs
            if len(d[0]) > 1:
                unsuccessfull_ru.append(1)
                self.reward -= 2  # 3.5 #penalty for unsuccessful RUs
            else:
                unsuccessfull_ru.append(0)

            if len(d[0]) == 1:  # successful tranmission/successful RU
                transmission = 1  # signalize successful transmission
                retransmission[d] = 0
                if not txop:
                    for sta in d[0]:
                        send_frames_counter[sta] += 1
                        delay_all[sta] += delay_cnt[sta]
                        delay_avg[sta] = delay_all[sta] / \
                            send_frames_counter[sta]
                        delay_cnt[sta] = 0
                    for sta in e[0]:
                        delay_cnt[sta] += (self.times.transmission_time())
                else:
                    for sta in d[0]:
                        send_frames_counter[sta] += frame_no
                    for sta in e[0]:
                        delay_cnt[sta] += (self.times.transmission_time())
                new_counter[0][d], ocw_table[0][d] = self.generate_obo_counter(
                    0, 0, ocwm[sta], ocwmin[sta])

            elif len(d[0]) > 1:  # collision/unsuccessful RU
                for sta in d[0]:
                    retransmission[sta] += 1
                    delay_cnt[sta] += (self.times.transmission_time())
                    collision_cnt[sta] += 1
                    new_counter[0][sta], ocw_table[0][sta] = self.generate_obo_counter(
                        retransmission[sta], ocw_table[0][sta], ocwm[sta], ocwmin[sta])
                for sta in e[0]:
                    delay_cnt[sta] += (self.times.transmission_time())

            else:  # empty RU

                for sta in e[0]:
                    delay_cnt[sta] += (self.times.non_transmission_time())

        for b in range(0, ru_sta_array.shape[0]):
            if sum(ru_sta_array[:][b]) > n_ru:
                collission = 1  # signalize collision

        # calculate times
        if transmission == 1:
            tx_tranmission = self.times.transmission_time(txop)
            time_spent += tx_tranmission
            tx_time += tx_tranmission
            retransmission[d] = 0
        elif collission == 1:
            tx_tranmission = self.times.transmission_time(txop)
            time_spent += tx_tranmission
            tx_time += tx_tranmission
        else:
            tx_nontransmission = self.times.non_transmission_time()
            tx_time += tx_nontransmission
            time_wait += tx_nontransmission

        # remember the results
        counters['retr'] = retransmission
        counters['pcol'] = collision_probability
        counters['new_cnt'] = new_counter
        counters['send'] = send_frames_counter
        counters['ocw'] = ocw_table
        counters['collision'] = collision_cnt
        counters['saturation'] = saturation
        counters['delay'] = delay_cnt
        counters['delay_a'] = delay_all
        counters['delay_avg'] = delay_avg
        times_dict['tx'] = tx_time
        times_dict['spent'] = time_spent
        times_dict['wait'] = time_wait
        simulation_time = tx_time
        counters['ocwm'] = ocwm
        counters['ocwmin'] = ocwmin
        counters['empty_ru'] = empty_ru
        counters['results_all'] = results_all
        counters['successfull_ru'] = successfull_ru
        counters['unsuccessfull_ru'] = unsuccessfull_ru

        # calculate collision probability
        if ~np.isnan(any(collision_cnt)) == True:
            for sta in range(0, n_sta):
                if (collision_cnt[sta] + send_frames_counter[sta]) == 0:
                    collision_probability[sta] = 0
                else:
                    collision_probability[sta] = collision_cnt[sta] / \
                        (collision_cnt[sta] + send_frames_counter[sta])
        else:
            collision_probability = 0

        # calculate success probability
        if any(send_frames_counter) > 0:
            success_probability = send_frames_counter / \
                (collision_cnt + send_frames_counter)
        else:
            success_probability = 0

        # calculate throughput
        throughput = (send_frames_counter*self.times.frame_size)/(tx_time*1e6)

        # update throughput results
        thr_results['thr'] = throughput
        thr_results['colision_prob'] = collision_probability
        thr_results['success'] = success_probability

        if(tx_time >= self.sim_time):
            self.done = 1

        return counters, times_dict, thr_results, alpha

    # ----------ML part--------------
    # Possible actions:
    # 0 increase alpha
    # 1 do nothing
    # 2 decrease alpha

    def reset(self, n_sta, n_ru):
        counters, times_dict, thr_results, alpha = self.backoff_procedure(
            n_ru, n_sta, 1, 'false', counters={}, times_dict={}, thr_results={}, txop=False)
        state = [0]
        return state, counters, times_dict, thr_results, alpha

    def stepreset(self, n_sta, n_ru, alpha):
        counters, times_dict, thr_results, alpha = self.backoff_procedure(
            n_ru, n_sta, alpha, 'false', counters={}, times_dict={}, thr_results={}, txop=False)
        return counters, times_dict, thr_results, alpha

    def step(self, action, n_ru, n_sta, counters, times_dict, thr_results, alpha, time):
        self.reward = 0
        self.done = 0
        time1 = 0  # allows to change the interval after which the impact of new alpha value on network performance is checked

        if action == 1:
            alpha = round(min(3, alpha+0.1), 2)
            self.reward -= 0.1

        if action == 2:
            alpha = round(max(0.1, alpha-0.1), 2)
            self.reward -= 0.1

        # check how the new alpha value impacts the network performance
        while time1 < time:
            counters, times_dict, thr_results, alpha = self.backoff_procedure(
                n_ru, n_sta,  alpha, 'true', counters, times_dict, thr_results)
            time1 += 0.1

        # calculate state
        state = [round(sum(counters['unsuccessfull_ru'])/(sum(counters['unsuccessfull_ru']
                                                              )+sum(counters['successfull_ru'])+sum(counters['empty_ru'])), 2)]

        return self.reward, state, self.done, counters, times_dict, thr_results, alpha