from uAgents.src.uagents import Agent, Context, Model
from aumanaque import Create_agent
from action import Action
import time

class Message(Model):
    message: str

central = Create_agent.Central()
action = Action()

@central.on_event('startup')
async def plan_event(ctx: Context):
    central.belief(ctx, "goalx", 0)
    central.belief(ctx, "robotx", 0)
    central.belief(ctx, "goaly", 0)
    central.belief(ctx, "roboty", 0)
    central.belief(ctx, "winning", False)
    action.set_belief_hardware(ctx, 'Goal', 'OK')

@central.on_interval(period=5)
async def plan_interval(ctx: Context):
    action.check_memory(ctx)
    plan_robot = action.check_goal(ctx)
    action.set_plan_hardware(ctx, "plan_move", {"Goal": "OK"}, plan_robot) if central.contexto(ctx, {"winning": False}) else False
    action.set_desire_hardware(ctx, "plan_move") if central.contexto(ctx, {"winning": False}) else False
    action.check_winning(ctx)

@central.on_message(model=Message)
async def message_handler(ctx: Context, sender: str, msg: Message):
    pass

if __name__ == "__main__":
    central.run()


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
import tensorflow as tf
import io, time
import framework.tools as tools

# =============================================================================
# Target BPE
# Embedding: Feed forward
# Emission: Bi-LSTM
# Transition: Bi-LSTM
# =============================================================================

#==============================================================================
# Extra vocabulary BPE symbols
unk_token_bpe = 0
start_token_bpe = 1
end_token_bpe = 2
#==============================================================================
from model.model_hmm_based.model_hmm import Model

class Model(Model):

    def __init__(self, FLAGS, session, log):

        super(Model, self).__init__(FLAGS, session, log)

    def build_model(self):
        self.get_reference_BPE_idx()

        self.initialize_transition_parameter_nn()

        self.build_optimizer()
        self.build_initializer()

        self.build_word_model_placeholders()

        self.build_emission()
        self.build_transition()
        self.build_forward_backward_nn()

        self.build_update_emission()
        self.build_update_transition()

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

    def build_word_model_placeholders(self):

        self.sources = tf.placeholder(dtype=self.tf_int_dtype,
                                      shape=(None,None),
                                      name='sources')

        self.targets = tf.placeholder(dtype=self.tf_int_dtype,
                                      shape=(None,None),
                                      name='targets')

        self.source_lengths = tf.placeholder(dtype=self.tf_int_dtype,
                                             shape=(None),
                                             name='source_lengths')

        self.target_lengths = tf.placeholder(dtype=self.tf_int_dtype,
                                             shape=(None),
                                             name='target_lengths')

        self.targets_null = tf.placeholder(dtype=self.tf_int_dtype,
                                          shape=(1),
                                          name='targets_null')

    def assign_variable_training_transition(self):
        self.assign_variable_training_transition_()

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

    def get_emission(self):
        # NN parameters Null
        target_embedding= tf.get_variable(name='targetEmbedding',
                                                shape=[self.FLAGS.target_vocabulary_size,
                                                       self.FLAGS.embedding_size],
                                                initializer=self.initializer,
                                                dtype=self.tf_float_dtype)

        target_embedded_null = tf.nn.embedding_lookup(params=target_embedding,
                                                      ids=self.targets_null)

        target_embedded_word = tf.nn.embedding_lookup(params=target_embedding,
                                                      ids=self.targets)

        nnLayer = tf.layers.Dense(units=self.FLAGS.hidden_units,
                                    activation=tf.nn.tanh,
                                    use_bias=True,
                                    name='nnLayer')

        target_state_null = nnLayer(target_embedded_null)

        lstm_cell_fw = tf.contrib.rnn.LSTMCell(num_units=self.FLAGS.hidden_units)
        lstm_cell_bw = tf.contrib.rnn.LSTMCell(num_units=self.FLAGS.hidden_units)

        with tf.variable_scope('lstmTarget'):

                (output_fw, output_bw),\
                (output_state_fw, output_state_bw) = \
                tf.nn.bidirectional_dynamic_rnn(cell_fw=lstm_cell_fw,
                                                cell_bw=lstm_cell_bw,
                                                inputs=target_embedded_word,
                                                sequence_length=self.target_lengths,
                                                dtype=self.tf_float_dtype)

                target_state = tf.concat([output_fw, output_bw], axis=2)

                nnLayer_lstm = tf.layers.Dense(units=self.FLAGS.hidden_units,
                                    activation=None,
                                    use_bias=False,
                                    name='nnLayerLSTM')

                target_state = nnLayer_lstm(target_state)

        #----------------------------------------------------------------------
        #target_state = nnLayer(target_state)

        target_state = tf.nn.dropout(x=target_state,
                                       keep_prob=self.keep_prob,
                                       name='nnDropoutNonNull')

        target_state_null = tf.nn.dropout(x=target_state_null,
                                       keep_prob=self.keep_prob,
                                       name='nnDropoutNull')

        nnLayerVocabulary = tf.layers.Dense(units=self.FLAGS.source_vocabulary_size,
                                 name='nnLayerVocabulary')

        target_state = nnLayerVocabulary(target_state)
        target_state_null = nnLayerVocabulary(target_state_null)

        target_state = tf.nn.softmax(target_state)
        self.target_state_null = tf.nn.softmax(target_state_null)

        input_scan = (target_state, self.sources)

        def get_source(value):
            target_state = value[0]
            sources = value[1]

            target_state = tf.gather(target_state, sources, axis=-1)
            target_state_null = tf.gather(self.target_state_null, sources, axis=-1)

            target_state_null = tf.tile(target_state_null, [tf.shape(target_state)[0], 1])

            emission_prob = tf.concat([target_state, target_state_null], axis=0)

            return emission_prob, 0

        emission_prob, _ = tf.map_fn(get_source,input_scan, dtype=(self.tf_float_dtype,
                                                                       tf.int32))

        return emission_prob

    def get_transition(self):
        # NN parameters Null
        target_embedding= tf.get_variable(name='targetEmbedding',
                                                shape=[self.FLAGS.target_vocabulary_size,
                                                       self.FLAGS.embedding_size],
                                                initializer=self.initializer,
                                                dtype=self.tf_float_dtype)

        target_embedded_null = tf.nn.embedding_lookup(params=target_embedding,
                                                      ids=self.targets_null)

        target_embedded_word = tf.nn.embedding_lookup(params=target_embedding,
                                                      ids=self.targets)

        nnLayer = tf.layers.Dense(units=self.FLAGS.hidden_units,
                                    activation=tf.nn.tanh,
                                    use_bias=True,
                                    name='nnLayer')

        target_state_null = nnLayer(target_embedded_null)

        lstm_cell_fw = tf.contrib.rnn.LSTMCell(num_units=self.FLAGS.hidden_units)
        lstm_cell_bw = tf.contrib.rnn.LSTMCell(num_units=self.FLAGS.hidden_units)

        with tf.variable_scope('lstmTarget'):

                (output_fw, output_bw),\
                (output_state_fw, output_state_bw) = \
                tf.nn.bidirectional_dynamic_rnn(cell_fw=lstm_cell_fw,
                                                cell_bw=lstm_cell_bw,
                                                inputs=target_embedded_word,
                                                sequence_length=self.target_lengths,
                                                dtype=self.tf_float_dtype)

                target_state = tf.concat([output_fw, output_bw], axis=2)

                nnLayer_lstm = tf.layers.Dense(units=self.FLAGS.embedding_size,
                                    activation=None,
                                    use_bias=False,
                                    name='nnLayerLSTM')

                target_state = nnLayer_lstm(target_state)

        #----------------------------------------------------------------------
        target_state = nnLayer(target_state)

        target_state = tf.nn.dropout(x=target_state,
                                       keep_prob=self.keep_prob,
                                       name='nnDropoutNonNull')

        target_state_null = tf.nn.dropout(x=target_state_null,
                                       keep_prob=self.keep_prob,
                                       name='nnDropoutNull')

        nnLayerJumpWidth = tf.layers.Dense(units=self.size_jump_width_set_nn,
                                 name='nnLayerJumpWidth')

        jump_width = nnLayerJumpWidth(target_state)
        jump_width_prob = tf.nn.softmax(jump_width)

        nnLayerP0 = tf.layers.Dense(units=1,activation=tf.nn.sigmoid,
                                            name='nnLayerP0')

        p0 = tf.squeeze(nnLayerP0(target_state), axis=-1)

        self.length_target = tf.shape(self.targets)[1]
        def get_jump_width_word(value):
            jump_width = value[0]
            sentence_length = value[1]
            word_idx = value[2]
            jump_width_idx = tf.range(self.max_jump_width-word_idx,
                                      self.max_jump_width+sentence_length-word_idx)
            return tf.gather(jump_width, jump_width_idx),jump_width_idx,0

        def get_jump_width_sentence(value):
            jump_width = value[0]
            p0 = value[1]
            sentence_length = tf.ones([self.length_target],
                                      dtype=tf.int32)*self.length_target
            word_idx = tf.range(0,self.length_target)
            input_scan = (jump_width, sentence_length, word_idx)
            jump_width_word,jump_width_idx,_ = tf.map_fn(get_jump_width_word,
                                        input_scan,
                                        (self.tf_float_dtype, tf.int32,tf.int32))

            transition = tf.concat([jump_width_word, tf.diag(p0)], axis=1)
            transition = tf.concat([transition, transition], axis=0)

            transition_log = tf.concat([tf.log(jump_width_word), tf.diag(tf.log(p0))], axis=1)
            transition_log = tf.concat([transition_log, transition_log], axis=0)
            return transition, transition_log

        transition, transition_log = tf.map_fn(get_jump_width_sentence, (jump_width_prob, p0), (self.tf_float_dtype, self.tf_float_dtype) )

        return transition, transition_log

    def train_batch(self,
                  sources,
                  source_lengths,
                  targets,
                  targets_null,
                  target_lengths):

        input_feed = {}
        input_feed[self.keep_prob.name] = self.FLAGS.keep_prob
        input_feed[self.sources.name] = sources
        input_feed[self.targets.name] = targets
        input_feed[self.source_lengths.name] = source_lengths
        input_feed[self.target_lengths.name] = target_lengths

        input_feed[self.targets_null.name] = targets_null

        output_feed = [self.updates_emission,
                       self.cost_emission,
                       self.updates_transition,
                       self.cost_transition
                       ]

        outputs = self.sess.run(output_feed, input_feed)

        cost = outputs[1] + outputs[3]

        return cost

    def eval(self,
             sources,
             source_lengths,
             targets,
             targets_null,
             target_lengths):

        target_length = target_lengths[0]
        source_length = source_lengths[0]
        source = sources[0][:source_length]

        input_feed = {}
        input_feed[self.keep_prob.name] = 1.0
        input_feed[self.sources.name] = sources
        input_feed[self.targets.name] = targets
        input_feed[self.source_lengths.name] = source_lengths
        input_feed[self.target_lengths.name] = target_lengths

        input_feed[self.targets_null.name] = targets_null

        output_feed = [self.emission_prob,
                       self.transition_prob]
        #----------------------------------------------------------------------
        outputs = self.sess.run(output_feed, input_feed)

        emission_final = np.array(outputs[0][0])
        transition_final = np.array(outputs[1][0])

        initial_transition, transition = self.get_transition_evaluation_nn(transition_final,
                                                                        target_length)

        state_seq, likelihood_seq = self.viterbi(np.array(source),
                                        target_length*2,
                                        initial_transition,
                                        transition,
                                        emission_final)

        return [state_seq], [likelihood_seq], [emission_final]

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

    def prepare_batch(self, seqs_x, seqs_y):

        x_lengths = np.array([len(s) for s in seqs_x])
        y_lengths = np.array([len(s) for s in seqs_y])
        x_lengths_max = np.max(x_lengths)
        y_lengths_max = np.max(y_lengths)

        self.batch_size = len(seqs_x)
        self.update_freq = len(seqs_x)

        y_null = [self.FLAGS.target_vocabulary_size - 1]

        word_x = np.ones((self.batch_size,
                     x_lengths_max),
                    dtype=self.int_dtype) * end_token_bpe

        word_x_lengths = np.ones((self.batch_size),
                                    dtype=self.int_dtype)

        word_y = np.ones((self.batch_size,
                     y_lengths_max),
                    dtype=self.int_dtype) * end_token_bpe

        word_y_lengths = np.ones((self.batch_size),
                                    dtype=self.int_dtype)

        for idx, s_x in enumerate(seqs_x):
            word_x[idx, :x_lengths[idx]] = s_x
            word_x_lengths[idx] = x_lengths[idx]

        for idx, s_y in enumerate(seqs_y):
            word_y[idx, :y_lengths[idx]] = s_y
            word_y_lengths[idx] = y_lengths[idx]

        return word_x, word_x_lengths, word_y, y_null, word_y_lengths

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

    def train(self):
        # Training loop
        self.log.info('TRAINING %s', tools.get_time_now())

        self.evaluate()
        for epoch_idx in range(self.FLAGS.max_epochs):
            self.log.info('----------------')
            if self.global_epoch_step.eval() >= self.FLAGS.max_epochs:
                self.log.info('Training is complete. Reach the max epoch number: %d ', self.global_epoch_step.eval())
                break

            for train_seq in self.train_set:
                start_time = time.time()
                batch = self.prepare_batch(*train_seq)
                update_info = self.train_batch(*batch)

                self.print_log(update_info, start_time)

                self.evaluate()
                self.save_model()

                # Update emission fixed parameters for each batch, emission_update_freq < 0:
                if self.emission_update_freq < 0 and \
                self.global_step.eval() % self.emission_update_freq == 0:
                    self.assign_variable_training_emission()

                # Update transition discrete parameters for each batch, jump_width_update_freq < 0:
                if self.jump_width_update_freq < 0 and \
                self.global_step.eval() % self.jump_width_update_freq == 0:
                    self.assign_variable_training_transition()

            # Update emission fixed parameters for each epoch, emission_update_freq > 0:
            if self.emission_update_freq > 0 and \
            self.global_step.eval() % self.emission_update_freq == 0:
                self.assign_variable_training_emission()

            # Update transition discrete parameters for each epoch, jump_width_update_freq > 0:
            if self.jump_width_update_freq > 0 and \
            self.global_step.eval() % self.jump_width_update_freq == 0:
                self.assign_variable_training_transition()

            #------------------------------------------------------------------
            #------------------------------------------------------------------
            # Increase the epoch index of the model
            self.log.info('Epoch %d done at %s',
                          self.global_epoch_step.eval(),
                          tools.get_time_now())
            self.global_epoch_step_op.eval()

    def evaluate(self):
        # Execute a validation step
        if self.FLAGS.valid_freq!= 0 and self.global_step.eval() % self.FLAGS.valid_freq == 0:
            start_time = time.time()

            #------------------------------------------------------------------
            #------------------------------------------------------------------

            alignment_set = []
            likelihood_set = []
            emission_set = []

            for valid_seq in self.valid_set:
                batch = self.prepare_batch(*valid_seq)
                eval_info = self.eval(*batch)

                #--------------------------------------------------------------

                for idx in range(1):
                    source_length = batch[1][idx]
                    target_length = batch[4][idx]
                    alignment = []
                    for index_source, index_target in enumerate(eval_info[0][idx]):
                        if index_source < source_length and index_target < target_length:
                            # Check alignment reference: Start from 0 or 1 ?
                            alignment.append(str(index_source+self.evaluate_alignment_start_from) \
                            + "-" + str(index_target+self.evaluate_alignment_start_from))
                            #alignment.append(str(index_source) + "-" + str(index_target))

                    alignment_set.append(alignment)

                likelihood_set.append(eval_info[1])
                emission_set.append(eval_info[2])

            alignment_set = self.converse_BPE_to_word(alignment_set)

            AER = self.calculate_AER(alignment_set)
            likelihood_score = np.mean(likelihood_set)

            self.log.info('VALIDATION: Epoch %d , Step %d , Likelihood: %.10f , AER: %.5f in %ds at %s',
                          self.global_epoch_step.eval(),
                          self.global_step.eval() ,
                          likelihood_score,
                          AER,
                          time.time() - start_time,
                          tools.get_time_now())

            if AER < self.best_AER:
                with io.open(self.get_file_path('result'), 'w') as file_handler:
                    for sent_alignment in alignment_set:
                        sent_alignment = u'{}\n'.format(sent_alignment)
                        sent_alignment = sent_alignment.replace('[','')
                        sent_alignment = sent_alignment.replace(']','')
                        sent_alignment = sent_alignment.replace(',','')
                        sent_alignment = sent_alignment.replace('\'','')
                        file_handler.write(sent_alignment)

            self.start_time = time.time()

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

    def get_reference_BPE_idx(self):
        source_idx_batch = []
        target_idx_batch = []

        source_file = open(self.FLAGS.source_idx_data)
        source_lines = source_file.readlines()

        target_file = open(self.FLAGS.target_idx_data)
        target_lines = target_file.readlines()

        for i in source_lines:
            source_idx_batch.append(i.split())

        for i in target_lines:
            target_idx_batch.append(i.split())

        self.source_idx_batch = source_idx_batch
        self.target_idx_batch = target_idx_batch

    def converse_BPE_to_word(self, alignment_set):
        word_alignments = []
        for subword_alignment, ref_s, ref_t in zip(alignment_set, self.source_idx_batch, self.target_idx_batch):
            word_alignment = []
            for subword in subword_alignment:
                subword_src = subword.split('-')[0]
                subword_tgt = subword.split('-')[1]
                word_src = 0
                word_tgt = 0
                for s in ref_s:
                    word_src_ref = s.split('-')[0]
                    subword_src_ref = s.split('-')[1]
                    if subword_src == subword_src_ref:
                        word_src = word_src_ref
                for t in ref_t:
                    word_tgt_ref = t.split('-')[0]
                    subword_tgt_ref = t.split('-')[1]
                    if subword_tgt == subword_tgt_ref:
                        word_tgt = word_tgt_ref
                if word_src + '-'+ word_tgt not in word_alignment:
                    word_alignment.append(word_src + '-'+word_tgt)
            word_alignments.append(word_alignment)

        return word_alignments

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

    def print_model_info(self):
        self.log.info('Source word vocabulary size: %s', self.FLAGS.source_vocabulary_size)
        self.log.info('Target word vocabulary size: %s', self.FLAGS.target_vocabulary_size)
        self.log.info('Word hidden units: %s', self.FLAGS.hidden_units)
        self.log.info('Word embedding size: %s', self.FLAGS.embedding_size)


from flask import Blueprint, jsonify, request
from flask_login import login_required, current_user
from sqlalchemy.orm import relationship, sessionmaker, joinedload, load_only
from app.models import db,  User, Like, Post

like_routes = Blueprint('like', __name__)
@like_routes.route('/posts/<int:postId>/likes', methods=['get'])
def get_like_by_post_id(postId):
    if not Post.query.filter(Post.id == postId).one_or_none():
        return {"errors": ["Post couldn't be found"]}, 404

    likes = Like.query.filter(Like.post_id == postId).filter(Like.like_status == True).options(joinedload(Like.user).options(load_only('id','first_name', 'last_name', 'username', 'preview_image'))).all()
    # return {'likes': [like.to_dict() for like in likes]}

    return {
        "Like" : [
            {
                "id": like.id,
                "user_Id": like.user_id,
                "post_id": like.post_id,
                "like_status": like.like_status,
                "created_at": like.created_at,
                "updated_at": like.updated_at,
                "Owner": {
                    "id": like.user.id,
                    "username": like.user.username,
                    "first_name": like.user.first_name,
                    "last_name": like.user.last_name,
                    "previewImage": like.user.preview_image
                }
            } for like in likes
        ]
    }

@login_required
@like_routes.route('/posts/<int:postId>/likes', methods=['POST'])
def like_a_post(postId):
    if not Post.query.filter(Post.id == postId).one_or_none():
        return {"errors": ["Post couldn't be found"]}, 404

    user_id = current_user.get_id()

    #check whether the user has liked the post or not
    the_like_relation = Like.query.filter(Like.user_id == user_id).filter(Like.post_id == postId).one_or_none()
    if the_like_relation:
        if the_like_relation.like_status == True:
            return {"errors": ["Current user liked this post already"]}, 401
        else:
            the_like_relation.like_status = True
            db.session.commit()
            return the_like_relation.to_dict()

    like = Like(
        user_id = user_id,
        post_id = postId,
        like_status = True
    )
    db.session.add(like)
    db.session.commit()

    return like.to_dict()

@login_required
@like_routes.route('/likes/<int:likeId>', methods=['PUT'])
def unlike_a_post(likeId):
    user_id = current_user.get_id()

    the_like_relation = Like.query.filter(Like.id == likeId).one_or_none()
    if not the_like_relation:
        return {"errors": ["Like couldn't be found"]}, 404
    if not int(user_id) == int(the_like_relation.user_id):
        return {'errors': ['Unauthorized']}
    # db.session.delete(the_like_relation)
    the_like_relation.like_status = False
    db.session.commit()
    return the_like_relation.to_dict()


__doc__ = """
Copyright 2008-2011 Free Software Foundation, Inc.
This file is part of GNU Radio

GNU Radio Companion is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

GNU Radio Companion is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
"""

import os
from gnuradio import gr
from .. base.Platform import Platform as _Platform
from .. gui.Platform import Platform as _GUIPlatform
from FlowGraph import FlowGraph as _FlowGraph
from Connection import Connection as _Connection
from Block import Block as _Block
from Port import Port as _Port
from Param import Param as _Param
from Generator import Generator
from Constants import \
    HIER_BLOCKS_LIB_DIR, BLOCK_DTD, \
    DEFAULT_FLOW_GRAPH, BLOCKS_DIRS
import Constants

COLORS = [(name, color) for name, key, sizeof, color in Constants.CORE_TYPES]

class Platform(_Platform, _GUIPlatform):

    def __init__(self):
        """
        Make a platform for gnuradio.
        """
        #ensure hier dir
        if not os.path.exists(HIER_BLOCKS_LIB_DIR): os.mkdir(HIER_BLOCKS_LIB_DIR)
        #convert block paths to absolute paths
        block_paths = set(map(os.path.abspath, BLOCKS_DIRS))
        #init
        _Platform.__init__(
            self,
            name='GNU Radio Companion',
            version=gr.version(),
            key='grc',
            license=__doc__.strip(),
            website='http://gnuradio.org/redmine/wiki/gnuradio/GNURadioCompanion',
            block_paths=block_paths,
            block_dtd=BLOCK_DTD,
            default_flow_graph=DEFAULT_FLOW_GRAPH,
            generator=Generator,
            colors=COLORS,
        )
        _GUIPlatform.__init__(self)

    ##############################################
    # Constructors
    ##############################################
    FlowGraph = _FlowGraph
    Connection = _Connection
    Block = _Block
    Port = _Port
    Param = _Param


from collections import deque

from algorithms.algo11_binary_tree import TreeNode

from algorithms.algo11_binary_tree import create_binary_tree

def get_path_of_a_binary_tree_recursively_wrapper1(root: TreeNode):
    """
        This is a wrapper for users to use, this function doesn't return anything since the function it calls inside
        will print out all the paths
    """
    path: list[TreeNode] = [root]
    # Here we don't need to return anything, just basically call the func get_path_of_a_binary_tree_recursively()
    # since this function print all the value out for every single step
    get_path_of_a_binary_tree_recursively1(root, path)

def get_path_of_a_binary_tree_recursively1(root: TreeNode, curr_path: list[TreeNode]):
    left_child = root.get_left()
    right_child = root.get_right()

    if not left_child and not right_child:
        for i in range(len(curr_path)):
            if i == len(curr_path) - 1:
                print(curr_path[i].value)
            else:
                print(curr_path[i].value, end=",")
    if left_child:
        left_list = curr_path.copy()
        left_list.append(left_child)
        get_path_of_a_binary_tree_recursively1(left_child, left_list)
    if right_child:
        right_list = curr_path.copy()
        right_list.append(right_child)
        get_path_of_a_binary_tree_recursively1(right_child, right_list)

def get_path_of_a_binary_tree_recursively2(root: TreeNode, curr_path: list[TreeNode], result_paths: list[list[int]]):
    """
        This time, we don't print out the paths right away, we will return all the results in a list of lists[int]
        Since we need to return result_paths, we just create it once in the wrapper function, and let this function
        modify/append elements into result_paths.
        result_paths is an object, therefore, it is passed by reference. That is, each time this function modifies result_path,
        the function modifies the object itself in the memory, so any changes to result_paths will affect result_paths
        itself.
        This is totally different from primitive vars: they are passed by values. That is, when we pass a primitive var
        to a function, it will have a copy of itself and that copy will be used by the function. Therefore, any changes
        to that var inside the function will not affect their value in the memory

    """
    left_child = root.get_left()
    right_child = root.get_right()

    if not left_child and not right_child:
        # if the node doesn't have any children, we append the current-path into the result list
        nodes_path = []
        for node in curr_path:
            nodes_path.append(node.value)
        result_paths.append(nodes_path)
    if left_child:
        left_list = curr_path.copy()
        left_list.append(left_child)
        get_path_of_a_binary_tree_recursively2(left_child, left_list, result_paths)
    if right_child:
        right_list = curr_path.copy()
        right_list.append(right_child)
        get_path_of_a_binary_tree_recursively2(right_child, right_list, result_paths)

def get_path_of_a_binary_tree_recursively_wrapper2(root: TreeNode) -> list[list[int]]:
    path: list[TreeNode] = [root]
    # We create result_paths here with initialization as an empty list,  we let get_path_of_a_binary_tree_recursively2
    # do the rest of work like how to modify/append elements to result_paths
    result_paths: list[list[int]] = []
    get_path_of_a_binary_tree_recursively2(root, path, result_paths)

    return result_paths

if __name__ == '__main__':
    root = create_binary_tree()
    get_path_of_a_binary_tree_recursively_wrapper1(root)    # 1,2,4
                                                            # 1,2,5,8
                                                            # 1,3,6,9
                                                            # 1,3,7

    print("new paths with return statements:")
    print(get_path_of_a_binary_tree_recursively_wrapper2(root))     # [[1, 2, 4], [1, 2, 5, 8], [1, 3, 6, 9], [1, 3, 7]]


"""KenDB3 API model decorator

The goal of this subsystem is to automate generation of REST-like endpoints.
Ideally, each field or every object should only be defined once - in its Django
model.

This subsystem currently only handles (de-)serialization to basic Python obj-
ects - acceptable for DjangoJSONEncoder. It will probably be expanded later.

TypeScript class declarations are automatically generated for all API models,
accessible via `import { MyModel } from 'dataman';` in frontend code.

# Usage

From the system's viewpoint, model instances are collections of fields that can
be read or assigned using getattr and setattr, respectively. However, Django
models and API objects don't necessarily coincide: sometimes it makes sense to
only serialize part of the model. Field groups solve this problem: every field
is assigned to one or more field groups (default being '*'); they are specified
when registering fields. Serialization and deserialization routines can then be
told to use fields from a particular group.

## Boilerplate

 1. Add a `_api` attribute, and initialize it to a APIEngine instance.
 2. Add a @api_model decorator.

Resulting model class template looks like this:

    from api_lib.api_fields import api_model, APIEngine

    @api_model
    class MyModel(models.Model):
        _api = APIEngine()
        # the rest of the model

## Specifying fields

Three syntactical options are available. The example for every option registers
two fields: `my_field` in the default '*' group, and `fav_pizza` in groups
'food_prefs' and 'moral_worth'.

 1. Annotation syntax, usually appropriate for Django Fields. Yes, this is
    incompatible with type hints. We don' use 'em here. Believe or not, annota-
    tions were designed for different uses.

        my_field: _api() = models.CharField(...)
        fav_pizza: _api('food_prefs', 'moral_worth') = models.CharField(...)

 2. Decorator syntax, usually appropriate for @property's. Note that @property
    itself is missing, replaced entirely by @_api().property.

        @_api().property
        def my_field(self): ...
        @my_field.setter
        def my_field(self, value): ...

        @_api('food_prefs', 'moral_worth').property
        def fav_pizza(self): ...
        @my_field.setter
        def fav_pizza(self, value): ...

 3. Manually, for all those pesky special cases.

        # some weird stuff that defines a 'my_field' attribute
        _api.add_field('my_field')

        # more weird weirdness that adds a 'fav_pizza' attribute
        _api.add_field('fav_pizza', groups=['food_prefs', 'moral_worth'])

## Complete model example

    from api_lib.api_fields import api_model, APIEngine

    @api_model
    class Car(models.Model):
        _api = APIEngine()

        make: _api() = models.CharField(max_length=64)
        color = models.CharField(max_length=16)
        design_desc: api('looks') = models.TextField()

        @_api('*', 'looks').property
        def color_rgb(self):
            return to_rgb(self.color)

        @color_rgb.setter
        def color_rgb(self, value):
            return self.color = from_rgb(value)

## Serialization and deserialization

    >>> myCar.api_serialize()
    {'id': 123, 'make': 'Batmobile', 'color_rgb': [0, 0, 0]}
    >>> myCar.api_serialize('looks')
    {'id': 123, 'design_desc': 'Looks cool.', 'color_rgb': [0, 0, 0]}
    >>> t34 = Car.api_deserialize({'id': 456, 'make': 'Soviet tank T-34'})
    >>> t34
    <Car: Car object (456)>
    >>> t34.make
    'Soviet tank T-34'
    >>> t34.color
    ''

## Handling of relational fields

Relational fields are serialized to relevant primary keys. This is currently
implemented by using fields with `_id` suffix if they exist.

Note that fields are set in the order of their registration.
"""

import builtins
from collections import namedtuple
import re

from django.db import models

from taggit.managers import _TaggableManager

from . import api_autogenerator
from . import api_server

def api_model(cls):
    """Create API methods and classmethods in cls and schedule autogeneration.

    This decorator expects to find an APIEngine instance in cls._api, which
    should have been used to mark API fields. See module docstring for details.
    """

    cls._api._assemble_groups(cls)

    api_server.all_models[cls._api.api_name] = cls

    cls.api_serialize = _api_serialize_impl
    cls.api_deserialize = _api_deserialize_impl

    api_autogenerator.include_model(cls)

    return cls

class _Registrar:
    """An object used to mark fields."""

    def __init__(self, api_engine, groups):
        self.groups = groups
        self.api_engine = api_engine
        api_engine._request(self, groups=groups)

    def __repr__(self):
        return '@_api(' + ', '.join(repr(g) for g in self.groups) + ')'

    def __str__(self):
        return repr(self)

    def property(self, getter):
        """Return a property attribute that is marked as an API field."""
        if isinstance(getter, builtins.property):
            raise TypeError(
                "Don't use @property together with @_api().property")

        registrar = self

        class _APIProperty(builtins.property):
            """Helps keep track of property mutations.

            Each use of property.getter, property.setter or property.deleter
            creates a new instance of property, which makes attributes defined
            with @property hard to track. This class updates references automa-
            tically.
            """

            def getter(self, getter_fn):
                return self._update(builtins.property.getter(self, getter_fn))
            def setter(self, setter_fn):
                return self._update(builtins.property.setter(self, setter_fn))
            def deleter(self, deleter_fn):
                return self._update(builtins.property.deleter(self, deleter_fn))

            def _update(self, new_instance):
                request.find_by = new_instance
                return new_instance

            def __repr__(self):
                spr = builtins.property.__repr__(self)
                return f"{registrar}.property({spr})"
            def __str__(self):
                spr = builtins.property.__str__(self)
                return f"{registrar}.property({spr})"

        first_wrapper = _APIProperty(getter)
        request = self.api_engine._request(first_wrapper, groups=self.groups)
        return first_wrapper

class APIEngine:
    """Orchestrator of API field registrations.

    See module docstring for details.
    """

    class _Request:
        """A request to register a field.

        See APIEngine._request() for details.
        """
        def __init__(self, find_by, groups, getter=None, setter=None):
            if isinstance(groups, str):
                raise TypeError('groups must be a non-str iterable')
            bad_groups = [g for g in groups if not isinstance(g, str)]
            if bad_groups:
                raise TypeError(f"groups must be strs, not {bad_groups}")

            self.find_by = find_by
            self.groups = groups
            self.accessors = (getter, setter) if getter or setter else None

    def __init__(self):
        self._requests = []
        self.field_groups = None
        self.api_name = None
        pass

    def __call__(self, *groups):
        return _Registrar(self, groups or ['*'])

    def _request(self, find_by, **kwargs):
        """Request that a field identified by find_by be registered as a field.

        find_by can take these values:
          - Attribute name (str)
          - Attribute object that will be looked up in cls.__dict__
          - _Registrar object that will be looked up in cls.__annotations__

        If an attribute object isn't found, of if multiple attributes match, an
        error will be raised by @api_method.

        The _Request object is returned and can be modified afterwards.
        """
        if self._requests is None:
            raise ValueError('API is already assembled, '
                             f"cannot request registration of {find_by!r}")

        request = APIEngine._Request(find_by, **kwargs)
        self._requests.append(request)
        return request

    def add_field(self, name, groups='*', getter=None, setter=None):
        """Manually register an API field in given groups.

        Prefer using annotations or decorators to this manual method.

        groups can be a single str or an iterable of strs. If omitted, defaults
        to '*'.

        getter and setter, if specified, will be used to serialize and deseri-
        alize the field. Their call signatures should match getattr and setattr
        builtins.
        """
        if isinstance(groups, str):
            groups = [groups]
        self._request(name, groups=groups, getter=getter, setter=setter)

    def add_related(self, name, groups='*'):
        """Register a related manager as an API field in given groups.

        groups can be a single str or an iterable of strs. If omitted, defaults
        to '*'.
        """
        self.add_field(name + '_ids', groups,
                       getter=_related_manager_get,
                       setter=_related_manager_set)

    def _assemble_groups(self, cls):
        field_groups = {}
        requests = self._requests
        self._requests = None

        for request in requests:
            if isinstance(request.find_by, str):
                name = request.find_by
            elif isinstance(request.find_by, _Registrar):
                if not hasattr(cls, '__annotations__'):
                    continue
                name = _find_exactly_one(
                    (name for name, annotation in cls.__annotations__.items()
                     if annotation is request.find_by),
                    message_if_zero=None,
                    message_if_many=('@_api() annotation reused on fields {{}}'
                                     f" in {cls}")
                )
                if name is None:
                    continue
            else:
                name = _find_exactly_one(
                    (name for name, value in cls.__dict__.items()
                     if value is request.find_by),
                    message_if_zero=(f"Attribute object {request.find_by}, "
                                     f"marked as API, not found in {cls}"),
                    message_if_many=(f"Attribute object {request.find_by}, "
                                     'marked as API, found in several fields '
                                     f"in {cls}: {{}}")
                )

            if request.accessors:
                field = FieldMeta(name, *request.accessors)
            else:
                field = _determine_field_meta(cls, name)

            for group in request.groups:
                if group not in field_groups:
                    field_groups[group] = []
                field_groups[group].append(field)

        self.field_groups = field_groups
        self.api_name = re.sub(r'(?<!^)(?=[A-Z])', '_', cls.__name__).lower();
        self.all_fields = { meta.name for group in self.field_groups.values()
                                      for meta in group }

    def _get_fields(self, group):
        result = self.field_groups.get(group, None)
        if result is not None:
            return result
        raise ValueError(f"No fields registered in group {group!r}")

    def __repr__(self):
        return '_api: APIEngine'

    def __str__(self):
        return 'APIEngine'

def _find_exactly_one(candidates, message_if_zero, message_if_many):
    """Extract the only item from candidates, or optionally raise errors."""

    non_existant = object()
    candidate_iterable = iter(candidates)

    # Candidate 1 should exist
    candidate1 = next(candidate_iterable, non_existant)
    if candidate1 is non_existant:
        if message_if_zero:
            raise ValueError(message_if_zero)
        else:
            return None

    # Candidate 2 should not exist
    candidate2 = next(candidate_iterable, non_existant)
    if candidate2 is not non_existant:
        if message_if_many:
            raise ValueError(message_if_many.format(
                [candidate1, candidate2] + list(candidate_iterable)
            ))
        else:
            return None

    return candidate1

FieldMeta = namedtuple('FieldMeta',
                       'name, getter, setter',
                       defaults=(getattr, setattr))

def _determine_field_meta(cls, name):
    """Create FieldMeta for field registered as name in model cls."""

    if not hasattr(cls, name):
        # Probably a dynamic attribute
        return FieldMeta(name)

    attribute = getattr(cls, name)

    # Check for foreign keys and use object IDs instead
    if hasattr(attribute, 'field'):
        django_field = attribute.field
        if isinstance(django_field, models.ForeignKey):
            return FieldMeta(django_field.attname)
        elif isinstance(django_field, models.OneToOneField):
            return FieldMeta(django_field.attname)

    # Check for TaggableManager
    if isinstance(attribute, _TaggableManager):
        return FieldMeta(name, _taggable_manager_get, _taggable_manager_set)

    # Check for RelatedManager
    if hasattr(attribute, 'related_manager_cls'):
        return FieldMeta(name + '_ids',
                         _related_manager_get,
                         _related_manager_set)

    return FieldMeta(name)

def _related_manager_get(obj, name):
    manager = getattr(obj, name.removesuffix('_ids'))
    return [pk for (pk,) in manager.values_list('pk')]

def _related_manager_set(obj, name, value):
    manager = getattr(obj, name.removesuffix('_ids'))
    objects = manager.model.objects.filter(pk__in=value)
    manager.set(objects)

def _taggable_manager_get(obj, name):
    return list(getattr(obj, name).names())

def _taggable_manager_set(obj, name, value):
    return getattr(obj, name).set(value)

def _api_serialize_impl(self, group='*'):
    """Serializes this object into a Python dict.

    The dict will include the values of all API fields in the requested group,
    and `'id': self.pk`.
    """

    fields = type(self)._api._get_fields(group)
    result = {name: getter(self, name) for name, getter, _ in fields}
    result['id'] = self.pk
    return result

@classmethod
def _api_deserialize_impl(cls, data, group='*'):
    """Creates an object from a Python dict.

    The object is created roughly so:

        obj = type(self)()
        if 'id' in data: obj.pk = data['id']
        if 'field_1' in data: obj.field_1 = data['field_1']
        ...
        if 'field_N' in data: obj.field_N = data['field_N']

    All API fields from the requested group will be looked up in data. Missing
    fields, and dict entries not corresponding to a field are silently ignored.

    Returned object is not saved automatically.
    """
    fields = cls._api._get_fields(group)

    obj = cls()
    if 'id' in data:
        obj.pk = data['id']

    for field, _, setter in fields:
        if field in data:
            setter(self, field, data[field])
    return obj


from aiogram import types
from config import dp,bot
from .messages import *
from aiogram.dispatcher import FSMContext
from Include.core.states import AddGroups, MessageToGroup,ChatRemove
from Include.core.helpers.requestsApi import *
from Include.core.helpers import telegram as tg_helper
import openpyxl

@dp.callback_query_handler(text='create_newsletter',state="*")
async def process_callback_authbtn(callback_query: types.CallbackQuery):
    await bot.edit_message_text(chat_id=callback_query.from_user.id,message_id=callback_query.message.message_id,
    text=select_group,reply_markup=get_groups_btn())

@dp.callback_query_handler(lambda c: str(c.data).split("_")[0].__eq__("createnewslettergroup"),state="*")
async def create_newsletter_group(callback_query: types.CallbackQuery, state: FSMContext):
    groupid = str(callback_query.data).split("_")[1]
    async with state.proxy() as data:
        data['groupid']=groupid
        await MessageToGroup.WriteAndSendMSG.set()
        await bot.send_message(callback_query.from_user.id,write_msg)

@dp.message_handler(lambda message: message.text, state=MessageToGroup.WriteAndSendMSG, content_types=types.ContentTypes.TEXT)
async def WriteAndSendMSG(message: types.Message, state: FSMContext):
    if message.text == "/cancel":
        await stop_state(state, message)
        return
    async with state.proxy() as data:
            send_msg(data['groupid'],message.text)
            await bot.send_message(message.from_user.id,msg_sended)

@dp.message_handler(lambda message: message.text, state=ChatRemove.ChatId)
async def ChatRemove_func(message: types.Message, state: FSMContext):
    if message.text == "/cancel":
        await stop_state(state,message)
        return
    async with state.proxy() as data:
        try:
            id = int(message.text)
            if delete_chat(id):
                await bot.send_message(message.from_user.id, text=chat_deleted)
                await state.finish()
                return
            else:
                await bot.send_message(message.from_user.id, text=chat_del_err)
        except:
            await bot.send_message(message.from_user.id, text=chat_del_err)
            pass

@dp.callback_query_handler(text='remove_chat',state="*")
async def process_callback_authbtn(callback_query: types.CallbackQuery):
    await ChatRemove.ChatId.set()
    await bot.send_message(callback_query.from_user.id,text=chat_del_msg)

@dp.callback_query_handler(lambda c: str(c.data).split("_")[0].__eq__("group"),state="*")
async def manage_group(callback_query: types.CallbackQuery):
    groupid = str(callback_query.data).split("_")[1]
    chats = get_chats_in_group(groupid)
    btns = {
        ("Удалить группу", "removegroup_"+str(groupid)),
        ("Удалить чат", "remove_chat"),
        ("Назад", "create_newsletter"),
        ("Рассылка в группу", "createnewslettergroup_"+str(groupid))
    }
    group_manager = tg_helper.create_inline_markup(*btns)
    text = 'Чаты в этой группе:\n'
    i=0
    for chat in chats:
        i+=1
        text = text+str(i)+" ("+str(chat['id'])+"). "+str(chat['name'])+"\n"
    await bot.edit_message_text(chat_id=callback_query.from_user.id,message_id=callback_query.message.message_id,
                          text=text,reply_markup=group_manager)

@dp.callback_query_handler(lambda c: str(c.data).split("_")[0].__eq__("removegroup"),state="*")
async def remove_group(callback_query: types.CallbackQuery):
    groupid = str(callback_query.data).split("_")[1]
    delete_group(groupid)
    await bot.edit_message_text(chat_id=callback_query.from_user.id, message_id=callback_query.message.message_id,
                                text=group_deleted + "\n" + select_group, reply_markup=get_groups_btn())

def get_groups_btn():
    groups = get_groups()
    data_list = []
    for group in groups:
        tulist = (str(group['name']), "group_"+ str(group['id']))
        data_list.append(tulist)
    btns = tg_helper.create_inline_markup(*data_list, row_width=2)
    return btns

@dp.message_handler(state=AddGroups.GetFile, content_types=['document'])
async def get_names(message: types.Message, state: FSMContext):
    async with state.proxy() as data:
        file = await bot.get_file(message.document.file_id)
        file_path = file.file_path
        await bot.download_file(file_path, message.document.file_name)
        try:
            wb = openpyxl.load_workbook(message.document.file_name)
            sheetes = wb.get_sheet_names()
            sheet = wb.get_sheet_by_name(sheetes[0])
            rows = sheet.max_row
            groups = []
            groups.append(data['name'])
            for i in range(1, rows + 1):
                cell = sheet.cell(row=i, column=1).value
                groups.append(cell)
        except:
            await bot.send_message(message.from_user.id, text=file_error)
        add_groups(groups)
        await bot.send_message(message.from_user.id,text=good_loaded,reply_markup=START_INLINE_BTN)

@dp.message_handler(lambda message: message.text, state=AddGroups.NewGroupName, content_types=types.ContentTypes.TEXT)
async def get_name(message: types.Message, state: FSMContext):
    if message.text == "/cancel":
        await stop_state(state,message)
        return
    async with state.proxy() as data:
        data['name'] = message.text
        await AddGroups.next()
        await bot.send_message(message.from_user.id,send_me_file)

@dp.callback_query_handler(text='create_group',state="*")
async def process_callback_authbtn(callback_query: types.CallbackQuery):
    await bot.send_message(callback_query.from_user.id,text=new_gropu_name)
    await AddGroups.NewGroupName.set()

async def stop_state(state,message):
    current_state = await state.get_state()
    if current_state is None:
        return
    await state.finish()
    await message.reply('Действие отменено', reply_markup=types.ReplyKeyboardRemove())


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

# Run this app with `python app.py` and
# visit http://127.0.0.1:8050/ in your web browser.

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.express as px
import pandas as pd
import numpy as np
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.graph_objects as go
from dash.dependencies import Input, Output
from jupyter_dash import JupyterDash

def plotConfiguration(data):
        data["particle"]=data["particle"].astype(str)
        graphs=[]
        for particle,particleData in data.groupby("particle"):
            graphs.append(go.Scatter3d( x=particleData['x'], y=particleData['y'], z=particleData['z'] ,name=str(particle)))
        fig=go.Figure(data=graphs)
        return fig

class configurationsApp:

    def __init__(self,filename):
        external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

        self.app = JupyterDash(__name__, external_stylesheets=external_stylesheets)
        fig=go.Figure(data=[])

        try:
            conf=pd.read_csv(filename,sep=" ",index_col=False)
            fig=plotConfiguration(conf)

        except FileNotFoundError:
            print ("File not found")

        self.app.layout = html.Div(children=[
            html.H1(children='Visualization of PIMC simulation'),

            html.H2(children='''
                Configurations
            '''),

            dcc.Graph(
                id='last-configuration',
                figure=fig
            )
            ])


# Name: Grammar
#
# Version: 0.1.5
#
# Author: Sinuhe Valencia
#
# Author_email: sierisimo@gmail.com
#
# Description:
#   Group of classes and functions that helps to make the parsing of grammar files easy.

class ParseGrammar():
  """A function for parsing grammar files."""
  def __init__(self,src):
    extension = src.split('.')[-1]
    if extension != 'mgram':
      raise ParsingExceptionError("Extension for simple grammar is 'mgram',founded was: "+extension)
    self.src = src
  def parse_rules(self):
    rules = {}

    with open(self.src,"rt") as grammar_file:
      line_number = 0
      while True:
          in_line = grammar_file.readline()
          if not in_line:
              break
          in_line = in_line[:-1]
          line_number = line_number + 1
          statement = in_line.split('::')

          #This are the syntax checks
          first_c = "<" in statement[0]
          second_c = ">" in statement[0]
          third_c = "::" in in_line
          fourth_c = "=" in statement[1]

          if not first_c:
            raise ParsingExceptionError("Missing '<' at line: "+str(line_number)+"  in statement: "+in_line)
          if not second_c:
            raise ParsingExceptionError("Missing '>' at line: "+str(line_number)+"  in statement: "+in_line)
          if not third_c:
            raise ParsingExceptionError("Missing '::' at line: "+str(line_number)+"  in statement: "+in_line)
          if not fourth_c:
            raise ParsingExceptionError("Missing '=' at line: "+str(line_number)+"  in statement: "+in_line)

          rule = statement[0]
          cons = statement[1].split('=')[-1]

          rule = rule.split('<')[-1].split('>')[0]

          if "'" not in cons:
            tmp_list = cons.split("<")[1:]
            cons_list = []
            for i in tmp_list:
              i = i.split(">")[0]
              cons_list.append(i)
            else:
              cons = cons_list

          if rule in rules:
            rules[rule].append(cons)
          else:
            rules[rule] = [cons]

    grammar_file.close()

    return rules

class Grammar():
  """Just a simple class that holds the information of the parsing """
  def __init__(self,file_name):
    parser = ParseGrammar(file_name)
    self.rules = parser.parse_rules()

  #TODO: Make the 'deep' stop the recursion and start getting the notes...
  def expand(self,sentence,deep=5):
    from random import randrange

    sentence_list = []
    for i in sentence.split(" "):
      if not i in self.rules and i != ' ' and i != '':
        raise RuleExceptionError(i,0)
      elif i != '' and i != ' ':
        sentence_list.append(i)
    full_sentence = []

    def add_note(rule):
      set_of_rules = self.rules.get(rule)
      size = len(set_of_rules)
      choose = 0

      if size != 1:
        choose = randrange(0,size)

      rule_c = set_of_rules[choose]

      if type(rule_c) != list:
        full_sentence.append(rule_c)
      else:
        for i in range(0,len(rule_c)):
          add_note(rule_c[i])

    for l in sentence_list:
      add_note(l)

    return full_sentence

  def __str__(self):
    return "Grammar: \n"+str(self.rules)

class ProbabilisticGrammar():
  """A class that holds the information of parsing a probabilistic grammar file"""
  def __init__(self):
    pass

class RuleExceptionError(Exception):
  """Exception for rules bad written"""
  def __init__(self, rule, razon):
    self.rule = rule
    self.razon = razon

  def __str__(self):
    if self.razon == 0:
      return "Error: The current rule is not specified in grammar file"

class ParsingExceptionError(Exception):
  """Class for letting the user know that his file name is wrong"""
  def __init__(self,value):
    self.value = value
  def __str__(self):
    return repr(self.value)


import logging
import sys

def setup():
    logging.basicConfig(
        level=logging.INFO,
        handlers=[ColorStdErr()]
    )

class ColorStdErr(logging.StreamHandler):
    def __init__(self):
        class AddColor(logging.Formatter):
            def format(self, record):
                msg = super().format(record)
                color = '\033[1;' + ('32m', '36m', '33m', '31m', '41m')[
                    min(4, int(4*record.levelno / logging.FATAL))]
                return color + record.levelname + '\033[1;0m: ' + msg
        super().__init__(sys.stderr)
        self.setFormatter(AddColor())


# This file is an example file for how predictions should be made

print('Importing libraries...')
import numpy as np
import pandas as pd
import tensorflow as tf
import keras
import gzip
import glob

def compress():
    '''Compress data in all .h5 files'''
    for i in glob.glob('./*.h5'):
        l = gzip.compress(open(i, 'rb').read(),compresslevel=9)
        open(i, 'wb').write(l)

def decompress():
    '''Decompress data in all .h5 files'''
    for i in glob.glob('./*.h5'):
        l = gzip.decompress(open(i, 'rb').read())
        open(i, 'wb').write(l)

print('Decompressing weights files...')
decompress() # remove this line if .h5 files are already decompressed

print('Loading data...')
words = [word.strip() for word in open('./wordle.txt').readlines()]
word_dict = {v:i for i,v in enumerate(words)}
idx_dict = {i:v for i,v in enumerate(words)}

data = pd.read_csv('./wordle.csv')
labels = np.array([int(i) for i in data.pop('labels').to_numpy(dtype=np.int16)])
data.head()

data = data.to_numpy().flatten()

t = sorted(set(''.join(data)))
char2idx = {v:i for i,v in enumerate(t)}
idx2char = {i:v for i,v in enumerate(t)}
del t

def encode(array: np.ndarray):
    l = []
    for i in array:
        l.append([char2idx[v] for v in i])
    d = np.array(l, dtype=np.int8)
    return d

print('Encoding data...')
data = encode(data)

def build_model(mode: int=0):
    '''Returns a keras.Sequential model with the loaded weights.'''
    mode+=1
    model = keras.Sequential([
    tf.keras.layers.Flatten(input_shape=(5,)),
    tf.keras.layers.Dense(128*mode, activation='relu'),
    tf.keras.layers.Dense(256*mode, activation='relu'),
    tf.keras.layers.Dense(512*mode, activation='relu'),
    tf.keras.layers.Dense(len(set(labels)), activation='softmax')
    ])
    model.compile(optimizer='nadam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), metrics=['accuracy'], jit_compile=True)
    model.summary()
    model.load_weights('./smaller_model.h5' if mode==1 else './larger_model.h5')
    return model

model = build_model(0)

def predict(arr: np.ndarray):
    out = tuple(model.predict(encode(arr.flatten())).flatten())
    out = ((words[i],v) for i,v in enumerate(out))
    return sorted(out, key=lambda x: x[1], reverse=True)

# test lines to showcase accuracy
print(*predict(np.array(['fle_s']))[:5]) # outputs tuple[tuple[str, float]]
# the [:5] takes the 5 most likely options


from itertools import groupby, count

from PySide2 import QtCore, QtGui, QtWidgets

class HeaderView(QtWidgets.QHeaderView):
    # TODO: Hijack the model data for the actual columns to replace text for
    # collapsed columns with '...'
    BTN_MARGIN = 2
    BTN_SIZE = 16

    def __init__(self, orientation, parent=None):
        super(HeaderView, self).__init__(orientation, parent)
        self._groups = {}

    def mouseMoveEvent(self, event):
        super(HeaderView, self).mouseMoveEvent(event)
        logical = self.logicalIndexAt(event.pos())
        if logical >= 0 and self.has_button(logical):
            # Update the section to ensure the button highlights are enabled
            # when the mouse travels over
            self.updateSection(logical)

    def mousePressEvent(self, event):
        # type: (QtCore.QEvent) -> None
        logical = self.logicalIndexAt(event.pos())
        btn_rect = self.get_button_rect(logical)
        if btn_rect is not None and btn_rect.contains(event.pos()):
            name, data = self._get_group_data(logical)
            self.set_group_collapsed(name, not data['collapsed'])
            return
        super(HeaderView, self).mousePressEvent(event)

    def paintSection(self, painter, rect, logical_index):
        # type: (QtGui.QPainter, QtCore.QRect, int) -> None
        if not rect.isValid():
            return

        painter.save()
        style = self.style()

        opt = self.get_section_style_option(logical_index)
        opt.rect = rect

        # Check if the index is the last column of a group and add a button
        btn_opt = None
        btn = None
        grp = self._get_group_data(logical_index)
        if grp is not None:
            name, data = grp
            if data['indexes'][-1] == logical_index:
                btn = QtWidgets.QPushButton()
                btn_opt = QtWidgets.QStyleOptionButton()
                btn.initStyleOption(btn_opt)
                btn_opt.rect = self._get_button_rect(rect)
                horizontal = self.orientation() == QtCore.Qt.Horizontal
                btn_opt.text = (('>' if horizontal else 'v')
                                if data['collapsed'] else
                                ('<' if horizontal else '^'))
                if data['collapsed']:
                    opt.textAlignment = QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter
                    opt.text = name
            # Set the background colour for all the grouped column headers
            opt.palette.setColor(QtGui.QPalette.All, QtGui.QPalette.Background, data['colour'])

        # Update the state based on the current mouse state in relation to the
        # section rect
        mouse_pos = self.mapFromGlobal(QtGui.QCursor.pos())
        if btn_opt is not None and btn_opt.rect.contains(mouse_pos):
            if QtWidgets.QApplication.mouseButtons() & QtCore.Qt.LeftButton:
                btn_opt.state |= QtWidgets.QStyle.State_Sunken
            else:
                btn_opt.state |= QtWidgets.QStyle.State_MouseOver
        elif opt.rect.contains(mouse_pos):
            if QtWidgets.QApplication.mouseButtons() & QtCore.Qt.LeftButton:
                opt.state |= QtWidgets.QStyle.State_Sunken
            else:
                opt.state |= QtWidgets.QStyle.State_MouseOver

        # style.drawControl(QtWidgets.QStyle.CE_HeaderSection, opt, painter, self)
        painter.setBrush(opt.palette.background())
        painter.drawRect(rect)

        # Horizontal header labels look wrong if not equally aligned - only
        # modify the header for columns with a button
        # Vertical header labels look wrong if not aligned horizontally, so all
        # columns in a group should share the indent, not just the button column
        if btn is not None or (self.orientation() == QtCore.Qt.Vertical and grp is not None):
            text_rect = style.subElementRect(QtWidgets.QStyle.SE_HeaderLabel, opt)
            opt.rect = text_rect.adjusted(0, 0, -(self.BTN_SIZE + self.BTN_MARGIN * 2), 0)
        style.drawControl(QtWidgets.QStyle.CE_HeaderLabel, opt, painter, self)

        # WIP: sortIndicator drawing
        if opt.sortIndicator:
            opt.rect = style.subElementRect(QtWidgets.QStyle.SE_HeaderArrow, opt)
            style.proxy().drawPrimitive(QtWidgets.QStyle.PE_IndicatorHeaderArrow, opt, painter)

        if btn is not None:
            style.drawControl(QtWidgets.QStyle.CE_PushButton, btn_opt, painter, btn)

        painter.restore()

    def sectionSizeFromContents(self, logical_index):
        # type: (int) -> QtCore.QSize
        size = super(HeaderView, self).sectionSizeFromContents(logical_index)
        grp = self._get_group_data(logical_index)
        if grp is not None:
            name, data = grp
            # If the index has a button, modify the size
            if data['indexes'][-1] == logical_index:
                opt = QtWidgets.QStyleOptionHeader()
                self.initStyleOption(opt)
                # Use the group name if collapsed, or header label if not
                if not data['collapsed']:
                    name = self.model().headerData(logical_index,
                                                   self.orientation(),
                                                   QtCore.Qt.DisplayRole)
                margins = self.style().pixelMetric(QtWidgets.QStyle.PM_HeaderMargin, opt) * 2
                text_width = max(size.width(), opt.fontMetrics.width(name))
                size = QtCore.QSize(text_width + self.BTN_SIZE + self.BTN_MARGIN * 2 + margins,
                                    size.height())
        return size

    def add_group(self, name, indexes, collapsed=False, colour=None):
        # type: (str, list[int], bool, QtGui.QColor) -> None
        # Ensure each index is only contained in one group
        for group_name, group_data in self._groups.items():
            overlap = set(group_data['indexes']) & set(indexes)
            if overlap:
                self._groups[group_name] = [i for i in indexes if i not in indexes]

        # Ensure all visual indexes are in order
        visuals = sorted(self.visualIndex(idx) for idx in indexes)
        logical_in_visual_order = []
        minimum = visuals[0]
        for idx, visual_index in enumerate(visuals):
            ordered_visual_index = minimum + idx
            if visual_index != ordered_visual_index:
                self.moveSection(visual_index, ordered_visual_index)
            logical_in_visual_order.append(self.logicalIndex(ordered_visual_index))

        btn_index = logical_in_visual_order[-1]
        self._groups[name] = {
            'colour': colour or QtGui.QColor('red'),
            'indexes': logical_in_visual_order,
            'collapsed': collapsed,
            'size': self.sectionSize(btn_index),
        }

        # Expand / collapse if any of the indexes are collapsed
        self.set_group_collapsed(name, collapsed)

        # TODO: Replace hack fix
        # If not collapsing, the size is the same and nothing is recalculated,
        # which prevents the custom header size (including button) being applied
        if not collapsed:
            hidden_state = self.isSectionHidden(btn_index)
            self.setSectionHidden(btn_index, not hidden_state)
            self.setSectionHidden(btn_index, hidden_state)

    def get_button_rect(self, logical_index):
        # type: (int) -> QtCore.QRect|None
        grp = self._get_group_data(logical_index)
        if grp is None:
            return
        rect = self.get_section_rect(logical_index)
        return self._get_button_rect(rect)

    def get_section_rect(self, logical_index):
        # type: (int) -> QtCore.QRect
        # Accumulate the offset from the
        distance = self.offset()
        for visual_index in range(self.count()):
            logical = self.logicalIndex(visual_index)
            if self.isSectionHidden(logical):
                continue
            if logical == logical_index:
                size = self.sectionSize(logical)
                if self.orientation() == QtCore.Qt.Horizontal:
                    rect = QtCore.QRect(distance, 0, size, self.height())
                else:
                    rect = QtCore.QRect(0, distance, self.width(), size)
                return rect
            distance += self.sectionSize(logical)

    def get_section_style_option(self, logical_index):
        # type: (int) -> QtWidgets.QStyleOptionHeader
        model = self.model()
        orientation = self.orientation()
        root = self.rootIndex()
        selection_model = self.selectionModel()
        style = self.style()

        opt = QtWidgets.QStyleOptionHeader()
        self.initStyleOption(opt)

        # State
        state = QtWidgets.QStyle.State_None
        if self.isEnabled():
            state |= QtWidgets.QStyle.State_Enabled
        if self.window().isActiveWindow():
            state |= QtWidgets.QStyle.State_Active
        if self.sectionsClickable() and selection_model and self.highlightSections():
            if orientation == QtCore.Qt.Horizontal:
                if selection_model.columnIntersectsSelection(logical_index, root):
                    state |= QtWidgets.QStyle.State_On
                if selection_model.isColumnSelected(logical_index, root):
                    state |= QtWidgets.QStyle.State_Sunken
            else:
                if selection_model.rowIntersectsSelection(logical_index, root):
                    state |= QtWidgets.QStyle.State_On
                if selection_model.isRowSelected(logical_index, root):
                    state |= QtWidgets.QStyle.State_Sunken
        opt.state |= state

        # Margin
        margin = 2 * style.pixelMetric(QtWidgets.QStyle.PM_HeaderMargin, opt, self)
        header_arrow_alignment = style.styleHint(QtWidgets.QStyle.SH_Header_ArrowAlignment, opt, self)
        is_header_arrow_on_the_side = header_arrow_alignment & QtCore.Qt.AlignVCenter
        if (self.isSortIndicatorShown()
                and self.sortIndicatorSection() == logical_index
                and is_header_arrow_on_the_side):
            margin += style.pixelMetric(QtWidgets.QStyle.PM_HeaderMarkSize, opt, self)

        # Icon
        opt.iconAlignment = QtCore.Qt.AlignVCenter
        icon = model.headerData(logical_index, orientation, QtCore.Qt.DecorationRole)
        if icon and not icon.isNull():
            opt.icon = icon
            margin += (style.pixelMetric(QtWidgets.QStyle.PM_SmallIconSize, opt, self) +
                       style.pixelMetric(QtWidgets.QStyle.PM_HeaderMargin, opt, self))

        # Text
        text_alignment = model.headerData(logical_index, orientation,
                                          QtCore.Qt.TextAlignmentRole)
        opt.textAlignment = text_alignment or QtCore.Qt.AlignCenter  # d.defaultAlignment
        opt.text = model.headerData(logical_index, orientation, QtCore.Qt.DisplayRole)
        if self.textElideMode() != QtCore.Qt.ElideNone:
            text_rect = style.subElementRect(QtWidgets.QStyle.SE_HeaderLabel, opt, self)
            opt.text = opt.fontMetrics.elidedText(opt.text, self.textElideMode(),
                                                  text_rect.width() - margin)

        # Brushes
        background_brush = model.headerData(logical_index, orientation, QtCore.Qt.BackgroundRole)
        if background_brush is not None:
            brush = QtGui.QBrush(background_brush)
            opt.palette.setBrush(QtGui.QPalette.Button, brush)
            opt.palette.setBrush(QtGui.QPalette.Window, brush)

        foreground_brush = model.headerData(logical_index, orientation, QtCore.Qt.ForegroundRole)
        if foreground_brush is not None:
            opt.palette.setBrush(QtGui.QPalette.ButtonText, QtGui.QBrush(foreground_brush))

        # Header section attributes
        if self.isSortIndicatorShown() and self.sortIndicatorSection() == logical_index:
            opt.sortIndicator = (QtWidgets.QStyleOptionHeader.SortDown
                                 if (self.sortIndicatorOrder() == QtCore.Qt.AscendingOrder) else
                                 QtWidgets.QStyleOptionHeader.SortUp)
        opt.section = logical_index
        opt.orientation = orientation

        # Position
        visual = self.visualIndex(logical_index)
        first = self.logicalIndex(0) == logical_index
        last = self.logicalIndex(self.count() - 1) == logical_index
        if first and last:
            opt.position = QtWidgets.QStyleOptionHeader.OnlyOneSection
        elif first:
            opt.position = (QtWidgets.QStyleOptionHeader.End
                            if self.isRightToLeft() else
                            QtWidgets.QStyleOptionHeader.Beginning)
        elif last:
            opt.position = (QtWidgets.QStyleOptionHeader.Beginning
                            if self.isRightToLeft() else
                            QtWidgets.QStyleOptionHeader.End)
        else:
            opt.position = QtWidgets.QStyleOptionHeader.Middle

        # Selection
        if self.orientation() == QtCore.Qt.Horizontal:
            previous_selected = self.selectionModel().isColumnSelected(
                self.logicalIndex(visual - 1), root)
            next_selected = self.selectionModel().isColumnSelected(
                self.logicalIndex(visual + 1), root)
        else:
            previous_selected = self.selectionModel().isRowSelected(
                self.logicalIndex(visual - 1), root)
            next_selected = self.selectionModel().isRowSelected(
                self.logicalIndex(visual + 1), root)
        if previous_selected and next_selected:
            opt.selectedPosition = QtWidgets.QStyleOptionHeader.NextAndPreviousAreSelected
        elif previous_selected:
            opt.selectedPosition = QtWidgets.QStyleOptionHeader.PreviousIsSelected
        elif next_selected:
            opt.selectedPosition = QtWidgets.QStyleOptionHeader.NextIsSelected
        else:
            opt.selectedPosition = QtWidgets.QStyleOptionHeader.NotAdjacent

        return opt

    def has_button(self, logical_index):
        # type: (int) -> bool
        grp = self._get_group_data(logical_index)
        return grp is None or logical_index == grp[1]['indexes'][-1]

    def is_collapsed_group(self, name):
        # type: (str) -> bool|None
        return self._groups.get(name, {}).get('collapsed')

    def is_collapsed_index(self, logical_index):
        # type: (int) -> bool
        for data in self._groups.values():
            if logical_index in data['indexes']:
                return data['collapsed']
        return False

    def is_grouped(self, logical_index):
        # type: (int) -> bool
        return self._get_group_data(logical_index) is not None

    def remove_group(self, name):
        # type: (str) -> bool
        removed = self.set_group_collapsed(name, False)
        self._groups.pop(name, None)
        return removed

    def set_group_collapsed(self, name, collapsed):
        # type: (str, bool) -> bool
        data = self._groups.get(name)
        if not data:
            return False
        # Hide/Show all following indexes
        data['collapsed'] = collapsed
        indexes = data['indexes']
        btn_index = indexes[-1]

        # Font metrics for the name
        if collapsed:
            # [ margin text margin btn_margin btn btn_margin ]
            opt = QtWidgets.QStyleOptionHeader()
            self.initStyleOption(opt)
            size = ((opt.fontMetrics.width(name) + self.style().pixelMetric(QtWidgets.QStyle.PM_HeaderMargin, opt) * 2)
                    if self.orientation() == QtCore.Qt.Horizontal else
                    opt.fontMetrics.height())
            size += self.BTN_SIZE + self.BTN_MARGIN * 2
            # Save the width before replacing it so it can be restored
            data['size'] = self.sectionSize(btn_index)
        else:
            size = data['size']
        self.resizeSection(btn_index, size)

        # Toggle the visibility of the columns
        for index in indexes[:-1]:
            self.setSectionHidden(index, collapsed)

        return True

    def toggle_group(self, name):
        # type: (str) -> None
        data = self._groups.get(name)
        if data:
            self.set_group_collapsed(name, not data['collapsed'])

    def _get_button_rect(self, section_rect):
        # type: (QtCore.QRect) -> QtCore.QRect
        pos = section_rect.topRight()
        btn_height = section_rect.height() - self.BTN_MARGIN * 2
        btn_width = min(self.BTN_SIZE, section_rect.width() - self.BTN_MARGIN * 2)
        return QtCore.QRect(
            pos.x() - self.BTN_MARGIN - btn_width,
            pos.y() + self.BTN_MARGIN,
            btn_width,
            btn_height,
        )

    def _get_group_data(self, logical_index):
        # type: (int) -> tuple[str, dict]
        for name, data in self._groups.items():
            if logical_index in data['indexes']:
                return name, data

if __name__ == '__main__':
    from collections import namedtuple
    import sys

    class EntityModel(QtCore.QAbstractItemModel):
        def __init__(self, columns=None, entities=None, parent=None):
            # type: (tuple, list, QtWidgets.QWidget) -> None
            super(EntityModel, self).__init__(parent)
            self._entities = entities or []
            self.columns = columns or ()

        def add_entities(self, entities, index=None, parent=QtCore.QModelIndex()):
            # type: (list, int, QtCore.QModelIndex) -> None
            start = self.rowCount() if index is None else index
            num = len(entities)
            self.beginInsertRows(parent, start, start + num)
            self.insertRows(start, num, parent)
            self._entities = self._entities[:start] + entities + self._entities[start:]
            self.endInsertRows()

        def remove_entities(self, entities, parent=QtCore.QModelIndex()):
            # Guarantees order, avoids errors for invalid entities (but won't give warning)
            indexes = [idx for idx, e in enumerate(self._entities) if e in entities]
            # Remove in reversed order to prevent modifying indices during loop
            for _, grp in groupby(reversed(indexes), key=lambda x, y=count(): next(y) + x):
                indices = list(grp)
                # Indices are reversed
                end, start = indices[0], indices[-1]
                self.beginRemoveRows(parent, start, end)
                self.removeRows(start, end-start, parent)
                self._entities[start:end + 1] = []
                self.endRemoveRows()

        def set_entities(self, entities):
            # type: (list) -> None
            self.beginResetModel()
            self._entities = entities
            self.endResetModel()

        def columnCount(self, parent=QtCore.QModelIndex()):
            # type: (QtCore.QModelIndex) -> int
            return len(self.columns)

        def data(self, index, role=QtCore.Qt.DisplayRole):
            # type: (QtCore.QModelIndex, int) -> object
            if not index.isValid():
                return
            if role == QtCore.Qt.DisplayRole:
                field = self.columns[index.column()]
                entity = self._entities[index.row()]
                value = getattr(entity, field)
                return str(value)

        def flags(self, index):
            # type: (QtCore.QModelIndex) -> QtCore.Qt.ItemFlags
            return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable

        def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
            # type: (int, QtCore.Qt.Orientation, int) -> str
            if role == QtCore.Qt.DisplayRole:
                if orientation == QtCore.Qt.Horizontal:
                    return self.columns[section]
                else:
                    return str(section)

        def index(self, row, column, parent=QtCore.QModelIndex()):
            # type: (int, int, QtCore.QModelIndex) -> QtCore.QModelIndex
            return self.createIndex(row, column, self._entities[row] if self._entities else None)

        def parent(self, child):
            # type: (QtCore.QModelIndex) -> QtCore.QModelIndex
            return QtCore.QModelIndex()

        def rowCount(self, parent=QtCore.QModelIndex()):
            # type: (QtCore.QModelIndex) -> int
            return len(self._entities)

    app = QtWidgets.QApplication(sys.argv)

    attrs = ('name', 'gender', 'age', 'race')
    Entity = namedtuple('Entity', attrs)

    model = EntityModel(attrs, [
        Entity('nuala', 'female', 30, 'human'),
        Entity('matthew', 'male', 30, 'human'),
        Entity('ray', 'male', 30, 'human'),
        Entity('mel', 'male', 31, 'human'),
    ])
    # TODO: Enable sorting on view
    # proxy = QtCore.QSortFilterProxyModel()
    # proxy.setSourceModel(model)
    view = QtWidgets.QTableView()
    header = HeaderView(QtCore.Qt.Horizontal, view)
    view.setHorizontalHeader(header)
    vheader = HeaderView(QtCore.Qt.Vertical, view)
    view.setVerticalHeader(vheader)
    view.setModel(model)
    # view.setSortingEnabled(True)
    header.add_group('Character', [0, 2, 3], collapsed=False)
    vheader.add_group('Half', [1, 2], collapsed=False)
    view.show()
    view.resizeColumnsToContents()
    view.resizeRowsToContents()

    app.exec_()
    sys.exit()


"""Entity definitions (data classes) for Heartbridge. Each
class is a different record type from the Health app.
"""

from dataclasses import dataclass, fields, InitVar
from typing import ClassVar
from datetime import datetime

@dataclass(order=True)
class BaseHealthReading:
    timestamp: datetime
    value: InitVar[str] = None

    @property
    def field_names(self):
        return [x.name for x in fields(self)]

    @property
    def timestamp_string(self) -> str:
        return datetime.strftime(self.timestamp, "%Y-%m-%d %H:%M:%S")

    def get_value(self):
        """Gets the value of a health reading, determined by the `value_attribute`
        class variable.
        """
        if self.__annotations__.get("value_attribute"):
            value_key = getattr(self, "value_attribute")
            return self.__getattribute__(value_key)
        return None

    def to_dict(self):
        """Converts the data object to a dictionary. Call only
        on a subclass of BaseHealthReading.
        """
        if self.__annotations__.get("value_attribute"):
            value_key = getattr(self, "value_attribute")
            return {
                "timestamp": self.timestamp_string,
                value_key: self.__getattribute__(value_key),
            }

@dataclass(order=True)
class GenericHealthReading(BaseHealthReading):
    reading: str = None
    value_attribute: ClassVar[str] = "reading"

    def __post_init__(self, value):
        self.reading = value

@dataclass(order=True)
class HeartRateReading(BaseHealthReading):
    heart_rate: float = None
    value_attribute: ClassVar[str] = "heart_rate"

    def __post_init__(self, value):
        self.heart_rate = int(value)

@dataclass(order=True)
class RestingHeartRateReading(BaseHealthReading):
    resting_heart_rate: int = None
    value_attribute: ClassVar[str] = "resting_heart_rate"

    def __post_init__(self, value):
        self.resting_heart_rate = int(value)

@dataclass(order=True)
class HeartRateVariabilityReading(BaseHealthReading):
    heart_rate_variability: float = None
    value_attribute: ClassVar[str] = "heart_rate_variability"

    def __post_init__(self, value):
        self.heart_rate_variability = round(float(value), 2)

@dataclass(order=True)
class StepsReading(BaseHealthReading):
    step_count: int = None
    value_attribute: ClassVar[str] = "step_count"

    def __post_init__(self, value):
        self.step_count = int(value)

@dataclass(order=True)
class FlightsClimbedReading(BaseHealthReading):
    climbed: int = None
    value_attribute: ClassVar[str] = "climbed"

    def __post_init__(self, value):
        self.climbed = int(value)

@dataclass(order=True)
class CyclingDistanceReading(BaseHealthReading):
    distance_cycled: float = None
    value_attribute: ClassVar[str] = "distance_cycled"

    def __post_init__(self, value):
        self.distance_cycled = float(value)


# A Crypto Notifier Application

# Import modules

import pandas as pd
import os
from pathlib import Path
import numpy as np
from pycoingecko import CoinGeckoAPI
import questionary
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException
from dotenv import load_dotenv

load_dotenv()

# Set Variable for coingecko API
cg = CoinGeckoAPI()

#Data for app to pull
#Dataframe for swing thresholds
crypto_swing_thresholds = {'Coin':['bitcoin','ethereum','ripple','cardano','solana'],'pct_change_Threshold':[0.029452,0.038992,0.044968,0.045519,0.057073]}
swing_thresholds_df = pd.DataFrame(crypto_swing_thresholds).set_index('Coin')
#Simulation data - two weeks DataFrame
simdata_path = Path('./Resources/simdata.csv')
# Read in data and index by dates
two_week_daily_pct_change_df = pd.read_csv(simdata_path, index_col='date')

# Define code for ticker
def get_symbols():
    symbols = []
    symbols = questionary.checkbox(
        "Select CryptoCurrencies",
        choices=["bitcoin","ethereum","ripple","cardano", "solana"
        ]).ask()
    return symbols

# what is ave % increase threshold by comparing x time in the past generate average daily return swing
# put user two weeks back and compare each day
# if day has a big swing send message
# if after two weeks send message no big swings
#
def get_symbols():
    symbols = []
    symbols = questionary.checkbox(
        "Select CryptoCurrencies",
        choices=["bitcoin","ethereum","ripple","cardano", "solana"]
    ).ask()
    return symbols

def get_user_number():
    phone_number = []
    phone_number = questionary.text("What is your 10 digit phone number?:").ask()
    return phone_number

# Define code for Twilio message
# Set up twilio client
# Create twilio id and tolken for use in sdk
twilio_account_id = os.environ["TWILIO_ACCOUNT_ID"]
twilio_token = os.environ["TWILIO_AUTH_TOKEN"]
twilio_phone_number = os.environ["TWILIO_PHONE_NUMBER"]

# Create twilio rest client
client = Client(twilio_account_id, twilio_token)

# Define generate twilio message
def generate_twilio_message(phone_number, twilio_phone_number, information):
    try:
        client.messages.create(to=f"+1{phone_number}", from_=f"+1{twilio_phone_number}",
                                    body=information)
    # Implement your fallback code
    except TwilioRestException as e:
        print(e)

if __name__ == "__main__":
    # User input phone number to variable
    user_phone_number = get_user_number()

    # User input tickers to variable
    user_symbols = get_symbols()

    # Create conditional to pass information into message
    # Create empty message list to house information
    message_list = []

    for row in two_week_daily_pct_change_df:
        for symbol in user_symbols:
            if float(row[symbol]) >= float(swing_thresholds_df[symbol]):
                information_to_send = f"There was a big price swing for {symbol} today compared to the last three years worth of daily changes \n"
                message_list.append(information_to_send)
            else:
                no_big_swing = f'There was no significant price swing for {symbol} from yesterday compared to the last three years worth of daily changes'
                message_list.append(no_big_swing)

    message_list = ". ".join(message_list)
    generate_twilio_message(user_phone_number, twilio_phone_number, message_list)

    print(f"A message has been sent to your phone {user_phone_number} with a two week summary report for {user_symbols}")


import torch
from torch import nn
from torch.nn.utils.rnn import *
import numpy as np
from torch.utils.data import DataLoader, Dataset, TensorDataset
import torch.nn.functional as F
import random
import time
import torch.optim as optim
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
import torch.nn.utils as utils
import pickle as pk
from torch.utils.data import DataLoader, Dataset
import time
import Levenshtein

letter_list = ['0','A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',\
             'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '-', "'", '.', '_', '+', ' ','<sos>','<eos>']

############ Transformer Functions #####################
def transform_letter_to_index(transcript, letter_list):
    train_Y=[]
    for sen in transcript:
        res=[letter_list.index('<sos>')]
        for word in sen:
            for letter in word.decode('utf-8'):
                res.append(letter_list.index(letter))
            res.append(letter_list.index(' '))
        res.pop()
        res.append(letter_list.index('<eos>'))
        train_Y.append(res)
    return train_Y

def transform_index_to_letter(pred_indx):#input (N,L)
    res=[]
    for row in pred_indx:
        string=''
        for i in row:
            if letter_list[i]=='<eos>':
                string += letter_list[i]
                break
            else:
                string+=letter_list[i]
        res.append(string)
    return res

def transform_index_to_letter_test(pred_indx):#input (N,L)
    res=[]
    for row in pred_indx:
        string=''
        for i in row:
            if letter_list[i]=='<eos>' or letter_list[i]=='0':
                break
            else:
                string+=letter_list[i]
        res.append(string)
    return res

def transform_index_to_letter_Train(pred_indx):#input (N,L)
    res=[]
    for row in pred_indx:
        string=''.join(letter_list[i] for i in row)
        res.append(string)
    return res

############# DataLoader ##############################
class MyDataset(Dataset):
    def __init__(self, x, y):
        super().__init__()
        assert len(x) == len(y)
        self._x = x
        self._y = y

    def __len__(self):
        return len(self._x)

    def __getitem__(self, index):
        x = self._x[index]
        y = self._y[index]
        return x, y

class MyDataset_test(Dataset):
    def __init__(self, x):
        super().__init__()
        self._x = x

    def __len__(self):
        return len(self._x)

    def __getitem__(self, index):
        x = self._x[index]
        return x

def my_collate(batch):
    X = [torch.tensor(x) for x,_ in batch]
    Y = [torch.LongTensor(y) for _,y in batch]
    return [X, Y]

def my_collate_test(batch):
    X = [torch.tensor(x) for x in batch]
    return X


from selenium import webdriver
from pprint import pprint
import time
from collections import Counter

driver = webdriver.Chrome('chromedriver')
driver.get('http://zzzscore.com/color/')
driver.implicitly_wait(100)  # time.sleep과 같은 역할

btns = driver.find_elements_by_xpath('//*[@id="grid"]/div')
                                      # 형식이 통일되어있지 않기에 [*]를 못 씀
                                    # bs의 findAll처럼 리스트로 반환해줌
                                    # 셀레늄의 장점 : 셀레늄으로 가져온 요소는
                                    # 실시간으로 값이 바뀌는 것을 감지
                                    # 크롬 드라이버와 연결되어 계속 최신화

def analysis():  # 색 비교로 유일한 색(다른 색)을 찾아 버튼 클릭하는 함수
    # 색정보 css property
    btns_rgba = [btn.value_of_css_property('background-color') for btn in btns]
    pprint(btns_rgba)

    result = Counter(btns_rgba)   # 딕셔너리 형태로 반환
    pprint(result) #여기서 value가 1인게 정답

    #value가 1인 것 탐색
    for key, value in result.items():
        if value == 1:
            answer = key
            break
    else:
        answer = None
        print("정답을 찾을 수 없습니다.")

    # 정답 클릭
    # 1. btns_rgba에서 인덱스 값을 구하고
    # 2. 그 인덱스 값으로 btns 인덱스에 접근. 클릭
    if answer:
        index = btns_rgba.index(answer)
        btns[index].click()
'''
start = time.time()
while time.time()-start <= 60:
    analysis()
'''
# 색 비교가 아닌 소스코드에서 정답 찾기
start = time.time()
while time.time() - start <= 60:
    try:
        btn = driver.find_element_by_class_name("main")
        btn.click()
    except:
        pass


"""
PyTest file for tools.selector
"""

import pytest
import numpy as np
import xpipe.xhandle.ioshear as ioshear
import tempfile

class TestRaw(object):
    def test_data(self, tmpdir):
        dirname = str(tmpdir.mkdir("test"))
        fname = dirname + "/" + "test.dat"

        arr = np.arange(10)[:, np.newaxis]

        np.savetxt(fname, arr)

        res = ioshear.read_raw(fname)
        np.testing.assert_allclose(res, arr)

    def test_empty(self, tmpdir):
        dirname = str(tmpdir.mkdir("test"))
        fname = dirname + "/" + "test.dat"

        arr = np.array([])

        res = ioshear.read_raw(fname)

        np.testing.assert_allclose(res, arr)

class TestMultipleRaw(object):
    def test_data(self, tmpdir):
        dirname = str(tmpdir.mkdir("test"))

        nfile = 10
        ans = []

        tmp_names = []
        for i in np.arange(nfile):
            fname = dirname + "/" + str(i) + ".dat"

            arr = np.arange(i+1)[:, np.newaxis]
            np.savetxt(fname, arr)

            tmp_names.append(fname)
            ans.append(arr)

        res = ioshear.read_multiple_raw(tmp_names)

        assert len(res) == nfile
        np.testing.assert_allclose(np.vstack(res), np.vstack(ans))

    def test_empty(self, tmpdir):
        dirname = str(tmpdir.mkdir("test"))

        nfile = 10
        ans = []
        tmp_names = []
        for i in np.arange(nfile):
            fname = dirname + "/" + str(i) + ".dat"

            if i % 2 == 0:
                arr = np.arange(i+1)[:, np.newaxis]
                np.savetxt(fname, arr)
            elif i in (5, 7):
                arr = np.array([])
                np.savetxt(fname, arr)
            else:
                arr = np.array([])

            tmp_names.append(fname)
            ans.append(arr)

        res = ioshear.read_multiple_raw(tmp_names)

        assert len(res) == nfile
        for i in np.arange(nfile):
            assert len(res[i]) == len(ans[i])

def test_sheared_raw(tmpdir):
    dirname = str(tmpdir.mkdir("test"))
    shears = ioshear.sheared_tags

    arr = np.arange(10)

    snames = []
    base_name = dirname + "/test.dat"
    for i in np.arange(len(shears)):
        fname = base_name.replace(".dat", shears[i]+ ".dat")
        np.savetxt(fname, arr)
        snames.append(fname)

    res = ioshear.read_sheared_raw(base_name)
    assert len(res) == len(shears)

def test_multiple_sheared_raw(tmpdir):
    dirname = str(tmpdir.mkdir("test"))
    shears = ioshear.sheared_tags

    arr = np.arange(10)

    base_name1 = dirname + "/test1.dat"
    for i in np.arange(len(shears)):
        fname = base_name1.replace(".dat", shears[i]+ ".dat")
        np.savetxt(fname, arr)

    base_name2 = dirname + "/test2.dat"
    for i in np.arange(len(shears)):
        fname = base_name2.replace(".dat", shears[i]+ ".dat")
        np.savetxt(fname, arr)

    base_name = [base_name1, base_name2]
    res = ioshear.read_multiple_sheared_raw(base_name)
    assert len(res) == len(shears)

class TestXPatches(object):
    def test_data(self, monkeypatch):

        def mock_xread(data):
            return data, data.T[:, :, np.newaxis], None
        monkeypatch.setattr(ioshear, "xread", mock_xread)

        ans_labels = []
        raw_chunks = []
        for i in np.arange(-5, 5, 1):
            val = np.abs(i)
            arr = np.ones(shape=(val, 4)) * i
            raw_chunks.append(arr)
            ans_labels.append(np.ones(val) * i)

        ans_infos = np.vstack(raw_chunks)
        ans_datas = np.vstack(raw_chunks).T[:, :, np.newaxis]
        ans_labels = np.concatenate(ans_labels) + 5.

        infos, datas, labels = ioshear.xpatches(raw_chunks)

        np.testing.assert_allclose(infos, ans_infos)
        np.testing.assert_allclose(datas, ans_datas)
        np.testing.assert_allclose(labels, ans_labels)