import json
import numpy as np
import os
import random
from utils import *
import itertools
from tkinter import _flatten
from collections import Counter
class BinaryRecommendEnv(object):
    def __init__(self, kg, dataset, data_name, seed=1, max_turn=15, bit_length=20, attr_bit_state=20, mode='train', command=1, ask_num=1, entropy_way='weight entropy', fm_epoch=0):
        self.data_name = data_name
        self.command = command
        self.mode = mode
        self.seed = seed
        self.max_turn = max_turn    #conversation maximum turns
        self.attr_bit_state = attr_bit_state  # The number of binary bits to record the entropy of features
        self.bit_length = bit_length # The number of binary bits to record the number of candidate items
        self.kg = kg
        self.dataset = dataset
        self.feature_length = getattr(self.dataset, 'feature').value_len
        self.user_length = getattr(self.dataset, 'user').value_len
        self.item_length = getattr(self.dataset, 'item').value_len

        # action parameters
        self.ask_num = ask_num
        self.rec_num = 10
        #  entropy  or weight entropy
        self.ent_way = entropy_way

        # user's profile
        self.reachable_feature = []   # user reachable feature
        self.user_acc_feature = []  # user accepted feature which asked by agent
        self.user_rej_feature = []  # user rejected feature which asked by agent
        self.cand_items = []   # candidate items

        #user_id  item_id   cur_step   cur_node_set
        self.user_id = None
        self.target_item = None
        self.cur_conver_step = 0        #  the number of conversation in current step
        self.cur_node_set = []     # maybe a node or a node set  /   normally save feature node
        # state veactor
        self.user_embed = None
        self.conver_his = []    #conversation_history
        self.cand_item_num = []    #the number of candidate items  [binary ]
        self.attr_ent = []  # attribute entropy

        self.ui_dict = self.__load_rl_data__(data_name, mode=mode)  # np.array [ u i weight]
        self.user_weight_dict = dict()
        self.user_items_dict = dict()

        #init seed & init user_dict
        set_random_seed(self.seed) # set random seed
        if mode == 'train':
            self.__user_dict_init__() # init self.user_weight_dict  and  self.user_items_dict
        elif mode == 'test':
            self.ui_array = None    # u-i array [ [userID1, itemID1], ...,[userID2, itemID2]]
            self.__test_tuple_generate__()
            self.test_num = 0
        # === Init feature_map ( used for printing real_name feature path)
        self.__load_feature_map(self.data_name)
        # embeds = {
        #     'ui_emb': ui_emb,
        #     'feature_emb': feature_emb
        # }
        # load fm epoch
        embeds = load_embed(data_name, epoch=fm_epoch)
        self.ui_embeds =embeds['ui_emb']
        self.feature_emb = embeds['feature_emb']
        # self.feature_length = self.feature_emb.shape[0]-1

        self.action_space = 2

        self.state_space_dict = {
            1: self.max_turn + self.bit_length + self.attr_bit_state + self.ui_embeds.shape[1],
            2: self.attr_bit_state,  # attr_ent
            3: self.max_turn,  #conver_his
            4: self.bit_length,  #cand_item
            5: self.ui_embeds.shape[1], # user_embedding
            6: self.bit_length + self.attr_bit_state + self.max_turn, #attr_ent + conver_his + cand_item
            7: self.bit_length + self.max_turn,
        }
        self.state_space = self.state_space_dict[self.command]
        self.reward_dict = {
            'ask_suc': 0.01,
            'ask_fail': -0.1,
            'rec_suc': 1,
            'rec_fail': -0.1,
            'until_T': -0.3,      # MAX_Turn
            'cand_none': -0.1
        }
        self.history_dict = {
            'ask_suc': 1,
            'ask_fail': -1,
            'rec_scu': 2,
            'rec_fail': -2,
            'until_T': 0
        }
        self.attr_count_dict = dict()   # This dict is used to calculate entropy

    def __load_rl_data__(self, data_name, mode):
        if mode == 'train':
            with open(os.path.join(DATA_DIR[data_name], 'UI_Interaction_data/review_dict_valid.json'), encoding='utf-8') as f:
                print('train_data: load RL valid data')
                mydict = json.load(f)
        elif mode == 'test':
            with open(os.path.join(DATA_DIR[data_name], 'UI_Interaction_data/review_dict_test.json'), encoding='utf-8') as f:
                print('test_data: load RL test data')
                mydict = json.load(f)
        return mydict

    def __user_dict_init__(self):   #Calculate the weight of the number of interactions per user
        ui_nums = 0
        for items in self.ui_dict.values():
            ui_nums += len(items)
        for user_str in self.ui_dict.keys():
            user_id = int(user_str)
            self.user_weight_dict[user_id] = len(self.ui_dict[user_str])/ui_nums
        print('user_dict init successfully!')

    def __test_tuple_generate__(self):
        ui_list = []
        for user_str, items in self.ui_dict.items():
            user_id = int(user_str)
            for item_id in items:
                ui_list.append([user_id, item_id])
        self.ui_array = np.array(ui_list)
        np.random.shuffle(self.ui_array)
    def __load_feature_map(self, data_name):
        ID_MAP_DIR = DATA_DIR[data_name] + '/ID_map_info'
        FEATURE_FILE_DICT = {
            LAST_FM: 'tag_reverse_map.json',
            LAST_FM_STAR: 'tag_reverse_map.json',
            YELP_STAR: 'second-layer_tag_reverse_map.json',
        }
        with open(os.path.join(ID_MAP_DIR, FEATURE_FILE_DICT[data_name]),
                  encoding='utf-8') as f:
            self.feature_reverse_map = json.load(f)
            print('feature_reverse_map init successfully!')

    def fea_map(self, fea_id):
        return self.feature_reverse_map[str(fea_id)]["real_name"]

    def reset(self):
        # === 1. init  user_id  item_id  cur_step   cur_node_set
        self.cur_conver_step = 0   #reset cur_conversation step
        self.cur_node_set = []   #maybe a (feature) node or a (feature) node set  /  depend on  {args.ask_num & user_accept feature num}
        if self.mode == 'train':
            users = list(self.user_weight_dict.keys())
            # self.user_id = np.random.choice(users, p=list(self.user_weight_dict.values())) # select user  according to user weights
            self.user_id = np.random.choice(users)
            self.target_item = np.random.choice(self.ui_dict[str(self.user_id)])
        elif self.mode == 'test':
            self.user_id = self.ui_array[self.test_num, 0]
            self.target_item = self.ui_array[self.test_num, 1]
            self.test_num += 1

        # init user's profile
        cprint("-----------Reset Conversational Recomendation!------------")
        print('user_id:{}, target_item:{}'.format(self.user_id, self.target_item))
        self.feature_groundtrue = self.kg.G['item'][self.target_item]['belong_to']
        self.reachable_feature = []  # user reachable feature in cur_step
        self.user_acc_feature = []  # user accepted feature which asked by agent
        self.user_rej_feature = []  # user rejected feature which asked by agent
        self.cand_items = list(range(self.item_length))

        # === 2. init  state vector [Reinforcement learning input]
        self.user_embed = self.ui_embeds[self.user_id].tolist()  # init user_embed   np.array---list
        self.conver_his = [0] * self.max_turn  # conversation_history
        self.cand_item_num = [self.feature_length >> d & 1 for d in range(self.bit_length)][::-1]  #Binary representation of candidate set length
        self.attr_ent = [0] * self.attr_bit_state  # attribute entropy

        # ===============    Transition Stage ===========
        # === 3. [Turn-1 init user prefer feature]
        user_init_fea = self._user_init_prefer_feature(user_init_prefer_fea_num=1)
        # --- 3.1 update user's profile : self.user_acc_feature
        self._update_user_profile(acc_feature=user_init_fea, rej_feature=[])

        # === 4. init Graph path reasoning
        # --- 4.1 init jump path : from user to user's init feature
        self._update_cur_node_set(acc_feature_node=user_init_fea)  # == [latest jump point(set) on the graph] ===

        # --- 4.2 update reachable feature from current feature node
        self._updata_reachable_feature()  # User init prefered feature & update reachable_feature & update self.cur_node_set
        self._remove_asked_reachable_fea()  # remove init user's prefered feature from reachable feature
        print('Number of reachable features: {}'.format(len(self.reachable_feature)))
        # --- 4.3 output jumping log
        self._graph_path_extend(user_start=True, acc_feature_node=self.user_acc_feature.copy())  # Update graph path

        # === 5. update conversational feedback & state_vector(self.conver_his) [RL reward & turn idx]
        # --- 5.1 update RL input : conversational history encode
        self.conver_his[self.cur_conver_step] = self.history_dict['ask_suc']
        self.cur_conver_step += 1  # the number of conversation in current step
        # --- 5.2  update RL input : the info of candidate items length & update cand_items
        self._update_cand_items(acc_feature=self.user_acc_feature, rej_feature=[])
        print(f'===Number of candidate items: [{len(self.cand_items)}]')
        # === 5.3  update feature_entropy
        self._update_feature_entropy()  #update entropy

        # === 6. score reachable_feature & sort the score
        self._mini_sort_reachable_feature()  # move the top-k (k=ask_num) reachable feature to the front
        return self._get_state()

    def _get_state(self):
        if self.command == 1:
            state = [self.user_embed, self.conver_his, self.attr_ent, self.cand_item_num]
            state = list(_flatten(state))
        elif self.command == 2: #attr_ent
            state = self.attr_ent
            state = list(_flatten(state))
        elif self.command == 3: #conver_his
            state = self.conver_his
            state = list(_flatten(state))
        elif self.command == 4: #cand_len
            state = self.cand_item_num
            state = list(_flatten(state))
        elif self.command == 5:  #user_embedding
            state = self.user_embed
            state = list(_flatten(state))
        elif self.command == 6: #attr_ent + conver_his + cand_len
            state = [self.conver_his, self.attr_ent, self.cand_item_num]
            state = list(_flatten(state))
        elif self.command == 7: #conver_his + cand_len
            state = [self.conver_his, self.cand_item_num]
            state = list(_flatten(state))
        return state

    def step(self, action):   #action:0  ask   action:1  recommend   setp=MAX_TURN  done
        done = 0
        print('---------------step:{}-------------'.format(self.cur_conver_step))

        if self.cur_conver_step == self.max_turn:
            reward = self.reward_dict['until_T']
            self.conver_his[self.cur_conver_step-1] = self.history_dict['until_T']
            print('--> Maximum number of turns reached !')
            done = 1
        elif action == 0:   #ask feature
            print('-->action: ask features')
            reward, done, acc_feature, rej_feature = self._agent_ask_user_response(
                ask_num=self.ask_num)  # update user's profile:  user_acc_feature & user_rej_feature
            if done == 1: #  reachable feature set is empty
                cprint(f'There are no attributes to ask!')
                return self._get_state(), reward, done

            # -------------------------------------------------------
            # == update cand_items & state_vector(self.cand_item_num)
            self._update_cand_items(acc_feature, rej_feature)  # %%% only consider accepted features! See 'CPR' paper! %%%
            # == update user's profile : self.user_acc_feature, self.user_rej_feature
            self._update_user_profile(acc_feature, rej_feature)

            if len(acc_feature):  # can reach new feature：  update current node and reachable_feature
                print(f'--> User accept feature : {acc_feature}{tuple(map(self.fea_map, acc_feature))}')
                self._graph_path_extend(acc_feature_node=acc_feature)  # ==1. Update graph path
                self._update_cur_node_set(acc_feature_node=acc_feature)  # ==2. Update self.cur_node_set
                self._updata_reachable_feature()  # ==3. Update reachable_feature
                self._remove_asked_reachable_fea()  # -- 3.1 remove asked features from reachable feature
                self.item_score_compute_flag = True  # Item scores are calculated when using the weighting entropy method

                if self.command in [1, 2, 6, 7]:  # == update feature's entropy
                    self._update_feature_entropy(item_score_compute=self.item_score_compute_flag)

            elif len(rej_feature):  # update reachable feature
                self._remove_asked_reachable_fea()  # --  remove asked features from reachable feature
                for rej_fea_id in rej_feature:  # update feature's entropy
                    self.attr_ent[rej_fea_id] = 0
                print(f'--> User ignore(reject) feature : {rej_feature}{tuple(map(self.fea_map, rej_feature))}')

            if self.reachable_feature != []:
                self._mini_sort_reachable_feature()  # move the top-k (k=ask_num) reachable feature to the front

        elif action == 1:  #recommend items
            print('-->action: recommend items')
            # ===  Get items which sort by predictional model ===

            if self.conver_his[self.cur_conver_step - 1] in [self.history_dict['rec_fail']]:
                # If the agent's recommendation failed in last turn, there is no need to re-sort items
                pass
            else:
                cand_item_score = self._item_score()
                self.item_score_compute_flag = False  # Item scores has been computed

                item_score_tuple = list(zip(self.cand_items, cand_item_score))
                sort_tuple = sorted(item_score_tuple, key=lambda x: x[1],
                                    reverse=True)  # update cand_items : sorted by score
                self.cand_items, self.cand_item_score = zip(*sort_tuple)

                # -------------------------------------------------------
            # == Get feedback from the user when agent recommend items ==
            reward, done = self._agent_rec_user_response(rec_num=self.rec_num)  # update agent: self.cand_item
            # -------------------------------------------------------

            # == update cand_items & state_vector(self.cand_item_num)
            self._update_cand_items(acc_feature=[], rej_feature=[], rec_suc=bool(done))

            #========================================
            if reward == 1:
                self._graph_path_extend(item_end=True)  # Update graph path
                print('-->Recommend successfully!')
            else:
                if self.command in [1, 2, 6, 7]:  # update attr_ent
                    self._update_feature_entropy(item_score_compute=self.item_score_compute_flag)
                    self._mini_sort_reachable_feature()  # move the top-k (k=ask_num) reachable feature to the front
                print('-->Recommend fail !')

        print('~~~~~~~~ dialog state info ~~~~~~~~~')
        # === print user's profile ===
        print(f"===User accept features: [{self.user_acc_feature}]{tuple(map(self.fea_map, self.user_acc_feature))}")
        print(
            f"===User ignore(weak reject) features: [{self.user_rej_feature}]{tuple(map(self.fea_map, self.user_rej_feature))}")
        # === print agent's info ===
        print(f'===Number of Graph reachable feature: [{len(self.reachable_feature)}]')
        print(f'===Number of candidate items: [{len(self.cand_items)}]')
        self.cur_conver_step += 1
        return self._get_state(), reward, done

    def _graph_path_extend(self, user_start=False, item_end=False, acc_feature_node=None):

        if user_start is True:
            self.graph_path = [f'User node:[{self.user_id}]'] + [f'feature node:{acc_feature_node}{tuple(map(self.fea_map, acc_feature_node))}']
            cprint(f'[Graph Path Jump] User node:[{self.user_id}] -->  feature node:{acc_feature_node}{tuple(map(self.fea_map, acc_feature_node))}')
        elif item_end is True:
            # == perform path jump when user accept item that recommended by agent
            cprint(f'[Graph Path Jump] feature node:{self.cur_node_set}{tuple(map(self.fea_map, self.cur_node_set))} -->  Item node:{self.cand_items}')
            self.graph_path = self.graph_path + [f'Item node:{self.cand_items}']
        else:
            # == perform path jump when user accept new feature that asked by agent
            cprint(f'[Graph Path Jump] feature node:{self.cur_node_set}{tuple(map(self.fea_map, self.cur_node_set))} -->  feature node:{acc_feature_node}{tuple(map(self.fea_map, acc_feature_node))}')
            self.graph_path = self.graph_path + [f'feature node:{acc_feature_node}{tuple(map(self.fea_map, acc_feature_node))}']

    def _update_cur_node_set(self, acc_feature_node):
        # == [latest jump point(set) on the graph] ===
        self.cur_node_set = acc_feature_node  # update current_feature_node set on the graph

    def _user_init_prefer_feature(self, user_init_prefer_fea_num=1):
        # Turn 1: The user initializes the preferred features when start a conversation
        user_like_random_fea = random.sample(self.kg.G['item'][self.target_item]['belong_to'],
                                             k=user_init_prefer_fea_num)
        self.cur_node_set = user_like_random_fea  # update current_feature_node set
        cprint(f'[User init preferred feature] I like [{user_like_random_fea}]{tuple(map(self.fea_map, user_like_random_fea))}!')
        return user_like_random_fea

    def _updata_reachable_feature(self):
        # ===== Graph path reasoning [Turn x : from current node jump to next feature_node in 1-hop]====
        next_reachable_feature = []
        for cur_node in self.cur_node_set:   # update reachable feature ( from current node jump to next feature_node in 1-hop)
            if self.data_name in ['LAST_FM', 'LAST_FM_STAR']:
                # ====[A-U-A  1-hop jump]  co-users: collaborative filtering
                fea_liked_by_users = list(self.kg.G['feature'][cur_node]['like'])  # A-U : users who like cur_feature
                user_friends = self.kg.G['user'][self.user_id]['friends']  #  U-U :  target user's friends
                cand_fea_like_users = list(set(fea_liked_by_users) & set(user_friends))  # co-users: collaborative filtering
                for user_id in cand_fea_like_users:  # A-U-A  # U in [friends & fea_liked_by_user]
                    next_reachable_feature.extend(list(self.kg.G['user'][user_id]['like']))
                next_reachable_feature = list(set(next_reachable_feature))

            # ====[A-I-A  1-hop jump]  co_items: collaborative filtering
            fea_belong_items = list(self.kg.G['feature'][cur_node]['belong_to'])  # A-I : items that have this cur_feature
            cand_fea_belong_items = list(set(fea_belong_items) & set(self.cand_items)) # co_items: collaborative filtering
            for item_id in cand_fea_belong_items:  # A-I-A   I in [cand_items & fea_related_items]
                next_reachable_feature.extend(list(self.kg.G['item'][item_id]['belong_to']))
            next_reachable_feature = list(set(next_reachable_feature))
        # ==== update self.reachable_feature
        self.reachable_feature = next_reachable_feature  # next reachable_feature in 1-hop

    def _mini_sort_reachable_feature(self):
        # Sort reachable features according to the entropy of features
        reach_fea_score = self._feature_score()
        max_ind_list = []
        for k in range(self.ask_num):
            max_score = max(reach_fea_score)
            max_ind = reach_fea_score.index(max_score)
            reach_fea_score[max_ind] = 0
            max_ind_list.append(max_ind)
        max_fea_id = [self.reachable_feature[i] for i in max_ind_list]
        [self.reachable_feature.pop(v - i) for i, v in enumerate(max_ind_list)]
        [self.reachable_feature.insert(0, v) for v in max_fea_id[::-1]]

    def _feature_score(self):
        reach_fea_score = []
        for feature_id in self.reachable_feature:
            score = self.attr_ent[feature_id]
            reach_fea_score.append(score)
        return reach_fea_score

    def _item_score(self):
        cand_item_score = []
        for item_id in self.cand_items:
            item_embed = self.ui_embeds[self.user_length + item_id]
            score = 0
            score += np.inner(np.array(self.user_embed), item_embed)
            prefer_embed = self.feature_emb[self.user_acc_feature, :]  #np.array (x*64)
            for i in range(len(self.user_acc_feature)):
                score += np.inner(prefer_embed[i], item_embed)
            cand_item_score.append(score)
        return cand_item_score

    def _agent_ask_user_response(self, ask_num):
        '''
        :return:
            RL env feedback: reward, done,
            User response in current turn : acc_feature, rej_feature
        '''
        done = 0
        if len(self.reachable_feature) == 0:  #candidate features is empty
            reward = self.reward_dict['cand_none']
            done = 1
            self.conver_his[self.cur_conver_step] = self.history_dict['ask_fail']  # update conver_his
            return reward, done, [], []
        # === 1. Get feature tuple which sort by predictional model
        sort_mini_feas = self.reachable_feature[:ask_num]

        # === 2. User feedback: accept & reject
        acc_feature = list(set(sort_mini_feas) & set(self.feature_groundtrue))
        rej_feature = list(set(sort_mini_feas) - set(acc_feature))

        # === 3. Get action reward & Update state_vector(self.conver_his)
        if len(acc_feature):
            reward = self.reward_dict['ask_suc']
            self.conver_his[self.cur_conver_step] = self.history_dict['ask_suc']   #update conver_his
        else:
            reward = self.reward_dict['ask_fail']
            self.conver_his[self.cur_conver_step] = self.history_dict['ask_fail']  #update conver_his
        return reward, done, acc_feature, rej_feature

    def _remove_asked_reachable_fea(self):
        self.reachable_feature = list(
            set(self.reachable_feature) - set(self.user_acc_feature))  # remove user accept feature
        self.reachable_feature = list(
            set(self.reachable_feature) - set(self.user_rej_feature))  # remove user reject feature

    def _update_user_profile(self, acc_feature, rej_feature, rec_suc=None):
        self.user_acc_feature += acc_feature
        self.user_rej_feature += rej_feature

    def _update_cand_items(self, acc_feature, rej_feature, rec_suc=None):
        """
        ==== CRS takes the attributes accepted by the user as a strong indicator ===
        :param acc_feature:  user accept features when agent asking
        :param rej_feature:  user reject features when agent asking
        :return:
        """
        if rec_suc is None:  # action : Agent asking feature
            if len(acc_feature):    #accept feature
                for feature_id in acc_feature:
                    feature_items = self.kg.G['feature'][feature_id]['belong_to']
                    self.cand_items = set(self.cand_items) & set(feature_items)   #  itersection
                self.cand_items = list(self.cand_items)
            if len(rej_feature):  # Agent only considers all items containing all attributes user accepts
                pass  #
        else: # action : Agent recommend items
            if rec_suc is False:
                self.cand_items = self.cand_items[self.rec_num:]  # update candidate items
                self.cand_item_score = self.cand_item_score[self.rec_num:] # update candidate items score
            elif rec_suc is True:
                self.cand_items = [self.target_item]  # update candidate items
        # update state vector : self.cand_item_num
        self.cand_item_num = [len(self.cand_items) >>d & 1 for d in range(self.bit_length)][::-1]  # binary

    def _agent_rec_user_response(self, rec_num):
        # ===  User feedback: Get action reward & Update state_vector(self.conver_his)
        rec_items = self.cand_items[: rec_num]  # TOP k item to recommend
        print(f'-->Agent recommend items id: {rec_items}')
        # print(f'-->Agent recommend items score: {cand_item_score[: rec_num]}')
        # cprint(f'-->Agent recommend TOP-10 total score:  {sum(cand_item_score[: rec_num])}')
        if self.target_item in rec_items:
            reward = self.reward_dict['rec_suc']
            self.conver_his[self.cur_conver_step] = self.history_dict['rec_scu']  # update state vector: conver_his
            done = 1
        else:
            reward = self.reward_dict['rec_fail']
            self.conver_his[self.cur_conver_step] = self.history_dict['rec_fail']  # update state vector: conver_his
            done = 0
        if self.cand_items == []:  # candidate items is empty
            done = 1
            reward = self.reward_dict['cand_none']
        return reward, done

    def _update_feature_entropy(self, item_score_compute=True):
        if self.ent_way == 'entropy':
            cand_items_fea_list = []
            for item_id in self.cand_items:
                cand_items_fea_list.append(list(self.kg.G['item'][item_id]['belong_to']))
            cand_items_fea_list = list(_flatten(cand_items_fea_list))
            self.attr_count_dict = dict(Counter(cand_items_fea_list))
            self.attr_ent = [0] * self.attr_bit_state  # reset attr_ent
            real_ask_able = list(set(self.reachable_feature) & set(self.attr_count_dict.keys()))
            for fea_id in real_ask_able:
                p1 = float(self.attr_count_dict[fea_id]) / len(self.cand_items)
                p2 = 1.0 - p1
                if p1 == 1:
                    self.attr_ent[fea_id] = 0
                else:
                    ent = (- p1 * np.log2(p1) - p2 * np.log2(p2))
                    self.attr_ent[fea_id] = ent
        elif self.ent_way == 'weight entropy':
            cand_items_fea_list = []
            self.attr_count_dict = {}

            # ====  compute candidate items score ==== high time cost
            if item_score_compute is True:
                self.cand_item_score = self._item_score()
            else:
                pass

            cand_item_score_sig = self.sigmoid(self.cand_item_score)  # sigmoid(score)
            for score_ind, item_id in enumerate(self.cand_items):
                cand_items_fea_list = list(self.kg.G['item'][item_id]['belong_to'])
                for fea_id in cand_items_fea_list:
                    if self.attr_count_dict.get(fea_id) == None:
                        self.attr_count_dict[fea_id] = 0
                    self.attr_count_dict[fea_id] += cand_item_score_sig[score_ind]

            self.attr_ent = [0] * self.attr_bit_state  # reset attr_ent
            real_ask_able = list(set(self.reachable_feature) & set(self.attr_count_dict.keys()))
            sum_score_sig = sum(cand_item_score_sig)

            for fea_id in real_ask_able:
                p1 = float(self.attr_count_dict[fea_id]) / sum_score_sig
                p2 = 1.0 - p1
                if p1 == 1 or p1 <= 0:
                    self.attr_ent[fea_id] = 0
                else:
                    ent = (- p1 * np.log2(p1) - p2 * np.log2(p2))
                    self.attr_ent[fea_id] = ent

    def sigmoid(self, x_list):
        x_np = np.array(x_list)
        s = 1 / (1 + np.exp(-x_np))
        return s.tolist()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Oct 23 09:57:26 2020

@author: noahlefrancois
"""

import numpy as np
import matplotlib.pyplot as plt

"""
Part c) Pick a non-integer value of k and plot your analytic estimate of the DFT.
Show that FFT agrees (to within machine precision) with your analytic estimate.
Normally we think of the FFT of a pure sine wave to be a delta function. Are we close to that?
"""
#Calculate our analytic estimate of the DFT
def sinFT_analytic(x, k, N):
    kp = np.arange(N)
    terms = np.empty(N)
    for i in range(N):
        terms[i] = 1/(2j)*(-np.sum(np.exp(-2j*np.pi*(k-kp[i])*x/N) + np.exp(-2j*np.pi*(k+kp[i])*x/N)))
    terms = np.array(terms)
    return kp, terms

Np = 100
xp = np.arange(Np)
k = 7.5 #Non-integer value of k
f = np.sin(2*np.pi*k*xp/Np) #Define our sine wave

#Calculate our analytic DFT and the numpy fft of our sine wave, and plot the results
kp, f_An = sinFT_analytic(xp, k, Np)
f_Disc = np.fft.fft(f)

plt.figure()
plt.plot(xp, f)
plt.title('Sine Function')

plt.figure()
plt.plot(kp, abs(f_An), label='Analytic')
plt.plot(kp, abs(f_Disc), ':',label='FFT')
plt.legend()
plt.title('Fourier Transform of Sine Function')

plt.figure()
plt.plot(kp, abs(f_Disc)-abs(f_An),'*')
plt.title('Residuals')

print('The standard deviation between analytic and FFT values is ', f'{np.std(abs(f_Disc)-abs(f_An)):.4}')
"""
Output: The standard deviation between analytic and FFT values is  6.021e-14
The two functions agree very closely to machine precision, which is 1e-16.

The output is very close to a delta function at k and N-k, however it is not exactly a sharp/discontinuous
spike and has a bit of spectral leakage causing a widening of each peak. See A4Q5c_plot_FT and A4Q5c_plot_Residuals
"""

"""
Part d) Show that when we multiply by the window function 0.5-0.5cos(2pix/N), the spectral leakage for a
non-integer period sine wave drops dramatically
"""
#Define the window function
def window_func(x, N):
    window = 0.5 - 0.5*np.cos(2*np.pi*x/N)
    return window
#Multiply sine wave by window function and take the FFT
window = window_func(xp, Np)
f_Disc_window = np.fft.fft(f*window)

plt.figure()
plt.plot(kp, abs(f_Disc), label='No window')
plt.plot(kp, abs(f_Disc_window), label='With window')
plt.legend()
plt.yscale('log')
plt.title('Fourier Transform of Sine Function')

"""
We can see from the plot (A4Q5d_plot_FT) that the FT after multiplying by the window function has a
narrower peak at both k and N-k than the FT without the window function. The tradeoff is that the
magnitude of the peak is reduced when we multiply by the window function.
"""

"""
Part e) Show that the FT of the window is [N/2 N/4 0 0 ... 0 N/4].
Use this to show that you can get the windowed FT by appropriate combos of each point in the
unwindowed FT and its immediate neighbours.
"""

window_FT = np.fft.fft(window)
given_windowFT = np.zeros(Np)
given_windowFT[0] = Np/2
given_windowFT[1] = -Np/4
given_windowFT[-1] = -Np/4

print('The standard deviation between given and FFT values is ', f'{np.std(abs(window_FT)-abs(given_windowFT)):.4}')

plt.figure()
plt.plot(kp, window_FT, label='FFT of Window')
plt.plot(kp, given_windowFT, ':', label = 'Given FT of Window')
plt.legend()
plt.title('Fourier Transform of Window Function')

"""
Output: The standard deviation between given and FFT values is  7.387e-16
See A4Q5e_plot_FTofWindow.png
When we flip the sign of the Np/4 entries, the FFT of the window agrees to machine precision with
the array we are given and comparing to.
This means the FFT of the window function is N/2 if k=0, -N/4 if k = +/-1, else 0. Since the multiplication
of the window function by the sine wave is a convolution in Fourier space and the window function in
Fourier space is equal to the sum of these 3 delta functions at k=0, k=+/-1, this operation is just
going to select out 3 values for a given k. All other entries will be zero
"""

def windowed_FFT(F, N):
    terms = np.empty(N)
    for i in range(N):
        terms[i] = F[i]/2 - F[(i-1)%N]/4 - F[(i+1)%N]/4
    return terms

f_Disc_FixedWindow = windowed_FFT(f_Disc, Np)
print('The standard deviation between given and FFT values is ', f'{np.std(abs(f_Disc_window)-abs(f_Disc_FixedWindow)):.4}')

plt.figure()
plt.plot(kp, abs(f_Disc), label='FT of Sine')
plt.plot(kp, abs(f_Disc_window), label='FT of Sine*Window')
plt.plot(kp, abs(f_Disc_FixedWindow), ':', label='FT of Sine*Window using delta functions')
plt.legend()
plt.yscale('log')
plt.title('Fourier Transform of Sine Function')

"""
Output: The standard deviation between given and FFT values is  8.325e-16
See A4Q5e_plot_windowedSineFT.png
This agrees to machine precision with the FT of Sine*Window from Part d). This agreement confirms
that the method given in this question is equivalent to taking the windowed FT.
"""


# Import dependencies
import datetime as dt
import numpy as np
import pandas as pd
import sqlalchemy
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm import Session
from sqlalchemy import create_engine, func
from flask import Flask, jsonify

# Set up database engine to allow us to access and query our SQLite database file
engine = create_engine("sqlite:///hawaii.sqlite")
Base = automap_base()

# Reflect the tables to save our references to each table
Base.prepare(engine, reflect=True)

# Create a variable for each of the classes
Measurement = Base.classes.measurement
Station = Base.classes.station

# Create session link from Python
session = Session(engine)

# Create a new Flask app instance
app = Flask(__name__)

# Define welcome route
@app.route('/')

# Define routes
def welcome():
    return(
    '''
    Welcome to the Climate Analysis API!
    Available Routes:
    /api/v1.0/precipitation
    /api/v1.0/stations
    /api/v1.0/tobs
    /api/v1.0/temp/start/end
    ''')

# Create precipitation route
@app.route("/api/v1.0/precipitation")

# Create precipitation function
def precipitation():
   prev_year = dt.date(2017, 8, 23) - dt.timedelta(days=365)
   precipitation = session.query(Measurement.date, Measurement.prcp).\
    filter(Measurement.date >= prev_year).all()
   precip = {date: prcp for date, prcp in precipitation}
   return jsonify(precip)

# Create stations route
@app.route("/api/v1.0/stations")

# Create stations function
def stations():
    results = session.query(Station.station).all()
    stations = list(np.ravel(results))
    return jsonify(stations=stations)

# Create temperature observations route
@app.route("/api/v1.0/tobs")

# Create temperature observations function
def temp_monthly():
    prev_year = dt.date(2017, 8, 23) - dt.timedelta(days=365)
    results = session.query(Measurement.tobs).\
      filter(Measurement.station == 'USC00519281').\
      filter(Measurement.date >= prev_year).all()
    temps = list(np.ravel(results))
    return jsonify(temps=temps)

# Create summary statistics route
@app.route("/api/v1.0/temp/<start>")
@app.route("/api/v1.0/temp/<start>/<end>")

# Create summary statistics function
def stats(start=None, end=None):
    sel = [func.min(Measurement.tobs), func.avg(Measurement.tobs), func.max(Measurement.tobs)]

    if not end:
        results = session.query(*sel).\
            filter(Measurement.date >= start).all()
        temps = list(np.ravel(results))
        return jsonify(temps)

    results = session.query(*sel).\
        filter(Measurement.date >= start).\
        filter(Measurement.date <= end).all()
    temps = list(np.ravel(results))
    return jsonify(temps)


import numpy as np
from keras.models import Model
from keras.datasets import mnist
from keras.models import load_model
from sklearn.metrics import label_ranking_average_precision_score
import time
import cv2

t0 = time.time()

#(x_train, y_train), (x_test, y_test) = mnist.load_data()
sw_data = np.load('atlas_sw.npz')
x_train = sw_data['images'].astype('float32') / 255.
x_shape = (x_train.shape[0], x_train.shape[1], x_train.shape[2], 1)
x_train = np.reshape(x_train, x_shape)
y_train = sw_data['labels']
print("X_Train: ", x_train.shape)

print("===--- Paxinos/Watson Atlas")
pw_data = np.load('atlas_pw.npz')
pw_y = pw_data['labels']
pw_im = pw_data['images'].astype('float32') / 255.
pw_shape = pw_im.shape[0], pw_im.shape[1], pw_im.shape[2], 1
pw_im = np.reshape(pw_im, pw_shape)

x_test = np.array([pw_im[7], pw_im[10], pw_im[26], pw_im[39]])
y_test = np.array([pw_y[7], pw_y[10], pw_y[26], pw_y[39]])

x_test = pw_im
y_test = pw_y

print("X_Test: ", x_test.shape)

noise_factor = 0.4
x_train_noisy = x_train + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=x_train.shape)
x_test_noisy = x_test + noise_factor * np.random.normal(loc=0.0, scale=1.0, size=x_test.shape)

x_train_noisy = np.clip(x_train_noisy, 0., 1.)
x_test_noisy = np.clip(x_test_noisy, 0., 1.)
t1 = time.time()
print('Dataset loaded in: ', t1-t0)

print('Loading model :')
t0 = time.time()
autoencoder = load_model('autoencoder.h5')
encoder = Model(inputs=autoencoder.input, outputs=autoencoder.get_layer('encoder').output)
t1 = time.time()
print('Model loaded in: ', t1-t0)

scores = []

def retrieve_closest_elements(test_code, test_label, learned_codes):
    distances = []
    for code in learned_codes:
        distance = np.linalg.norm(code - test_code)
        distances.append(distance)
    nb_elements = learned_codes.shape[0]
    distances = np.array(distances)
    learned_code_index = np.arange(nb_elements)
    labels = np.copy(y_train).astype('float32')
    labels[labels != test_label] = -1
    labels[labels == test_label] = 1
    labels[labels == -1] = 0
    distance_with_labels = np.stack((distances, labels, learned_code_index), axis=-1)
    sorted_distance_with_labels = distance_with_labels[distance_with_labels[:, 0].argsort()]

    sorted_distances = 28 - sorted_distance_with_labels[:, 0]
    sorted_labels = sorted_distance_with_labels[:, 1]
    sorted_indexes = sorted_distance_with_labels[:, 2]
    return sorted_distances, sorted_labels, sorted_indexes

def compute_average_precision_score(test_codes, test_labels, learned_codes, n_samples):
    out_labels = []
    out_distances = []
    retrieved_elements_indexes = []
    for i in range(len(test_codes)):
        sorted_distances, sorted_labels, sorted_indexes = retrieve_closest_elements(test_codes[i], test_labels[i], learned_codes)
        out_distances.append(sorted_distances[:n_samples])
        out_labels.append(sorted_labels[:n_samples])
        retrieved_elements_indexes.append(sorted_indexes[:n_samples])

    out_labels = np.array(out_labels)
    out_labels_file_name = 'computed_data/out_labels_{}'.format(n_samples)
    np.save(out_labels_file_name, out_labels)

    out_distances_file_name = 'computed_data/out_distances_{}'.format(n_samples)
    out_distances = np.array(out_distances)
    np.save(out_distances_file_name, out_distances)
    score = label_ranking_average_precision_score(out_labels, out_distances)
    scores.append(score)
    return score

INDEX = 0
def retrieve_closest_images(test_element, test_label, n_samples=10):
    global INDEX
    learned_codes = encoder.predict(x_train)
    learned_codes = learned_codes.reshape(learned_codes.shape[0],
                                          learned_codes.shape[1] * learned_codes.shape[2] * learned_codes.shape[3])

    test_code = encoder.predict(np.array([test_element]))
    test_code = test_code.reshape(test_code.shape[1] * test_code.shape[2] * test_code.shape[3])

    distances = []

    for code in learned_codes:
        distance = np.linalg.norm(code - test_code)
        distances.append(distance)
    nb_elements = learned_codes.shape[0]
    distances = np.array(distances)
    learned_code_index = np.arange(nb_elements)
    labels = np.copy(y_train).astype('float32')
    labels[labels != test_label] = -1
    labels[labels == test_label] = 1
    labels[labels == -1] = 0
    distance_with_labels = np.stack((distances, labels, learned_code_index), axis=-1)
    sorted_distance_with_labels = distance_with_labels[distance_with_labels[:, 0].argsort()]

    sorted_distances = 28 - sorted_distance_with_labels[:, 0]
    sorted_labels = sorted_distance_with_labels[:, 1]
    sorted_indexes = sorted_distance_with_labels[:, 2]
    kept_indexes = sorted_indexes[:n_samples]

    score = label_ranking_average_precision_score(np.array([sorted_labels[:n_samples]]), np.array([sorted_distances[:n_samples]]))

    kept_indexes = kept_indexes.astype(np.uint16)
    result_y = y_train[kept_indexes]
    result_distances = sorted_distances[kept_indexes]

    print("Plate {} - ".format(test_label), end='')
    for i in range(n_samples):
        match_y = result_y[i]
        match_d = result_distances[i]
        print("[{},{:.4f}] ".format(match_y, match_d), end='')

    print("")

    #print("Average precision ranking score for tested element is {}".format(score))

    original_image = test_element
    #cv2.imshow('original_image_' + str(INDEX), original_image)
    retrieved_images = x_train[int(kept_indexes[0]), :]
    for i in range(1, n_samples):
        retrieved_images = np.hstack((retrieved_images, x_train[int(kept_indexes[i]), :]))

    #cv2.imshow('Results_' + str(INDEX), retrieved_images)

    cv2.imwrite('test_results/plate_' + str(test_label) + '.jpg', 255 * cv2.resize(original_image, (0,0), fx=3, fy=3))
    cv2.imwrite('test_results/results' + str(test_label) + '.jpg', 255 * cv2.resize(retrieved_images, (0,0), fx=2, fy=2))

    #import pdb
    #pdb.set_trace()

    INDEX += 1
    return result_y

def test_model(n_test_samples, n_train_samples):
    learned_codes = encoder.predict(x_train)
    learned_codes = learned_codes.reshape(learned_codes.shape[0], learned_codes.shape[1] * learned_codes.shape[2] * learned_codes.shape[3])
    test_codes = encoder.predict(x_test)
    test_codes = test_codes.reshape(test_codes.shape[0], test_codes.shape[1] * test_codes.shape[2] * test_codes.shape[3])
    indexes = np.arange(len(y_test))
    np.random.shuffle(indexes)
    indexes = indexes[:n_test_samples]

    print('Start computing score for {} train samples'.format(n_train_samples))
    t1 = time.time()
    score = compute_average_precision_score(test_codes[indexes], y_test[indexes], learned_codes, n_train_samples)
    t2 = time.time()
    print('Score computed in: ', t2-t1)
    print('Model score:', score)

def plot_denoised_images():
    denoised_images = autoencoder.predict(x_test_noisy.reshape(x_test_noisy.shape[0], x_test_noisy.shape[1], x_test_noisy.shape[2], 1))
    test_img = x_test_noisy[0]
    resized_test_img = cv2.resize(test_img, (280, 280))
    cv2.imshow('input', resized_test_img)
    output = denoised_images[0]
    resized_output = cv2.resize(output, (280, 280))
    cv2.imshow('output', resized_output)
    cv2.imwrite('test_results/noisy_image.jpg', 255 * resized_test_img)
    cv2.imwrite('test_results/denoised_image.jpg', 255 * resized_output)

# To test the whole model
n_test_samples = 1000
n_train_samples = [10, 50, 100, 200, 300, 400, 500, 750, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000,
                   20000, 30000, 40000, 50000, 60000]

#for n_train_sample in n_train_samples:
#    test_model(n_test_samples, n_train_sample)

np.save('computed_data/scores', np.array(scores))

import pylab as plt
plt.xkcd()
plt.figure()
plt.title('SW Matching')
plt.xlabel('PW Plate')
plt.ylabel('SW Plate')
# To retrieve closest images
x = []
y = []
for i in range(len(x_test)):
#for i in range(3):
    x.append(y_test[i]) # Plate #
    predictions = retrieve_closest_images(x_test[i], y_test[i])
    y.append(predictions[0]) # Top Prediction

plt.plot(x, y)
plt.savefig('results.png')
plt.show(block=True)

# To plot a denoised image
#plot_denoised_images()


import pyfiglet,termcolor
def EncryptionVigenere(word,key):
    cipherchar=[]
    if len(key)==len(word):
        for w in range(0,len(word)):
            c=ord(word[w])-97
            ke=ord(key[w])-97
            total=(c+ke)%26
            cipherchar.append(chr(total+97))
        final=''.join(cipherchar)
        return final
    else:
        if len(key)>len(word):
            newKey=list(key[0:len(word)])
        else:
            newKey=list(key)
        cipherchar=[]
        i=0#index char of key
        for editk in range(len(word)-len(key)):
            newKey.append(key[i])
            i=(i+1)%len(key)
        print("the edit key: ".capitalize()+''.join(newKey))
        for w in range(len(word)):
            c=ord(word[w])-97
            k=ord(newKey[w])-97
            total=(c+k)%26
            cipherchar.append(chr(total+97))
        final=''.join(cipherchar)
        return final

def DecryptionVigenere(cipword,key):

    Decrcipherchar=[]
    if len(key)==len(cipword):
        for w in range(0,len(cipword)):
            c=ord(cipword[w])-97
            k=ord(key[w])-97
            total=((c-k)+26)%26
            Decrcipherchar.append(chr(total+97))
        final=''.join(Decrcipherchar)
        return final
    else:
        if len(key)>len(cipword):
            EditKey=list(key[0:len(cipword)])
        else:
            EditKey=list(key)
        i=0#index char of key
        for editk in range(len(cipword)-len(key)):
            EditKey.append(key[i])
            i=(i+1)%len(key)
        print("the orginal key : ".capitalize()+''.join(EditKey))
        for w in range(len(cipword)):
            c=ord(cipword[w])-97
            k=ord(EditKey[w])-97
            total=((c-k)+26)%26
            Decrcipherchar.append(chr(total+97))
        final=''.join(Decrcipherchar)
        return final
def mainVigenere():
    playfair=pyfiglet.figlet_format("PlayFair : )")
    print(termcolor.colored(playfair,color='yellow'))
    choice=True
    while choice==True:
        ChoiceDec=input('A-Encryption\nB-Decryption\nplease enter your choice A OR B: ').upper()
        if ChoiceDec=='A':
            encryption=pyfiglet.figlet_format("Encryption")
            print(termcolor.colored(encryption,color='magenta'))
            text=input('please enter your text: ').lower().replace(' ','')
            Key=input('enter your key: ').lower()
            if text.isalpha() and Key.isalpha():
                Ctext=EncryptionVigenere(text,Key)
                print('the text encryption is: ',Ctext)
            else: print(termcolor.colored(f'please enter a valid text: (alphabet)/Key: (alphabet) with no symbols text: ({text}) , Key: ({Key})'.capitalize(),color="red"))
            choice=input(termcolor.colored('wold you want do try again Y/N : '.capitalize(),color="green")).upper()
            if choice=='Y':
                    choice=True
            else:
                End=pyfiglet.figlet_format('End Vigenere : (')
                print(termcolor.colored(End,color='red'))
                choice=False

        elif ChoiceDec=='B':
            decryption=pyfiglet.figlet_format("Decryption")
            print(termcolor.colored(decryption,color='cyan'))
            ciptext=input('please enter your ciphertext: ').lower().replace(' ','')
            Key=input('enter your key: ').lower()
            if ciptext.isalpha() and Key.isalpha():
                plaintext=DecryptionVigenere(ciptext,Key)
                print("your plaintext is: ",plaintext)
            else:
                print(termcolor.colored(f'please enter a valid text: (alphabet)/Key: (alphabet) with no symbols text: ({text}) , Key: ({Key})'.capitalize(),color="red"))
            choice=input(termcolor.colored('wold you want do try again Y/N : '.capitalize(),color="green")).upper()
            if choice=='Y':
                    choice=True
            else:
                End=pyfiglet.figlet_format('End Vigenere : (')
                print(termcolor.colored(End,color='red'))
                choice=False

        else:
            print(termcolor.colored('worring choice'.upper(),color="red"))
            choice=input(termcolor.colored('This is not valid choice, wold you want do try again Y/N : '.capitalize(),color="green")).upper()
            if choice=='Y':
                    choice=True
            else:
                End=pyfiglet.figlet_format('End Vigenere : (')
                print(termcolor.colored(End,color='red'))
                choice=False


from io import BufferedReader
from typing import List, Tuple

from srcstudiomodel.mdl_enum import MDLFlag

from .util import _struct_unpack
from .type import Matrix3x4, Vector3, Vector4

def _read_name64(buf: BufferedReader) -> str:
    return buf.read(64).decode().rstrip('\0')

def _read_strings(buf: BufferedReader, off: int, num: int = 1) -> List[str]:
    start = buf.tell()
    buf.seek(off)
    result = [''] * num
    for i in range(num):
        s = b''
        while True:
            p = buf.read(1)
            if p == b'\0':
                break
            s += p
        result[i] = s.decode()
    buf.seek(start)
    return result

class MDLMesh:
    material: int
    model_index: int
    num_vertices: int
    vertex_offset: int
    num_flexes: int
    flex_index: int
    material_type: int
    material_params: int
    mesh_id: int
    center: Tuple[float, float, float]

    def __init__(self, buf: BufferedReader):
        (self.material, self.model_index, self.num_vertices,
         self.vertex_offset, self.num_flexes, self.flex_index,
         self.material_type, self.material_params, self.mesh_id) \
            = _struct_unpack('=iiiiiiiii', buf)
        self.center = _struct_unpack('=fff', buf)
        buf.seek(68, 1)  # this need fix in future

class MDLModel:
    name: str
    type: int
    bounding_radius: float
    num_meshes: int
    mesh_index: int
    num_vertices: int
    vertex_index: int
    tangents_index: int
    num_attachments: int
    attachment_index: int
    num_eyeballs: int
    eyeball_index: int

    meshes: List[MDLMesh]

    def __init__(self, buf: BufferedReader):
        start = buf.tell()
        self.name = _read_name64(buf)
        (self.type, self.bounding_radius, self.num_meshes, self.mesh_index,
         self.num_vertices, self.vertex_index, self.tangents_index,
         self.num_attachments, self.attachment_index, self.num_eyeballs,
         self.eyeball_index) = _struct_unpack('=ifiiiiiiiii', buf)
        end = buf.seek(40, 1)
        buf.seek(start + self.mesh_index)
        self.meshes = list(map(MDLMesh, [buf]*self.num_meshes))
        buf.seek(end)

class MDLTexture:
    name: str
    flags: int
    used: int

    def __init__(self, buf: BufferedReader):
        start = buf.tell()
        (name_index, self.flags, self.used) = \
            _struct_unpack('=iii', buf)
        self.name = _read_strings(buf, start + name_index)[0]
        buf.seek(52, 1)

class MDLBodyPart:
    num_models: int
    model_index: int

    name: str
    models: List[MDLModel]

    def __init__(self, buf: BufferedReader):
        start = buf.tell()
        (name_index, self.num_models, _, self.model_index) = \
            _struct_unpack('=iiii', buf)
        end = buf.tell()
        self.name = _read_strings(buf, start + name_index)[0]
        buf.seek(start + self.model_index)
        self.models = list(map(MDLModel, [buf]*self.num_models))
        buf.seek(end)

class MDLBone:
    name: str
    parent_id: int
    parent: 'MDLBone'
    children: List['MDLBone']
    bone_controller: List[int]
    pos: Vector3
    quat: Vector4
    rot: Vector3
    posscale: Vector3
    rotscale: Vector3
    pose_to_bone: Matrix3x4
    q_alignment: Vector4
    flags: int
    proctype: int
    procindex: int
    physics_bone: int
    surface_prop_index: int
    contents: int
    # unused 32 bytes

    def __init__(self, buf: BufferedReader):
        start = buf.tell()
        (name_index, self.parent_id) = _struct_unpack('=ii', buf)
        self.name = _read_strings(buf, start+name_index)[0]
        self.bone_controller = list(_struct_unpack('=iiiiii', buf))
        self.pos = _struct_unpack('=fff', buf)
        self.quat = _struct_unpack('=ffff', buf)
        self.rot = _struct_unpack('=fff', buf)
        self.posscale = _struct_unpack('=fff', buf)
        self.rotscale = _struct_unpack('=fff', buf)
        self.pose_to_bone = (
            _struct_unpack('=ffff', buf),
            _struct_unpack('=ffff', buf),
            _struct_unpack('=ffff', buf),
        )
        self.q_alignment = _struct_unpack('=ffff', buf)
        (self.flags, self.proctype, self.procindex, self.physics_bone,
         self.surface_prop_index, self.contents) \
            = _struct_unpack('=iiiiii', buf)
        self.children = []
        buf.seek(32, 1)

class MDL:
    version: int
    checksum: int
    name: str
    # skipped many entries
    flags: MDLFlag
    # skipped many entries

    bones: List[MDLBone]
    root_bone: MDLBone
    textures: List[MDLTexture]
    skins: List[List[MDLTexture]]
    bodyparts: List[MDLBodyPart]

    def __init__(self, buf: BufferedReader):
        (id, self.version, self.checksum) = _struct_unpack('=III', buf)
        if id != 0x54534449:
            raise Exception('this is not mdl file')
        self.name = _read_name64(buf)
        buf.seek(76, 1)
        self.flags = MDLFlag(_struct_unpack('=I', buf)[0])
        # bone
        (num, off) = _struct_unpack('=ii', buf)
        home = buf.tell()
        buf.seek(off)
        self.bones = list(map(MDLBone, [buf]*num))
        buf.seek(home + 40)
        # texture
        (num, off) = _struct_unpack('=ii', buf)
        home = buf.tell()
        buf.seek(off)
        self.textures = list(map(MDLTexture, [buf]*num))
        buf.seek(home + 8)
        # skins
        (num, fnum, off) = _struct_unpack('=iii', buf)
        home = buf.tell()
        buf.seek(off)
        self.skins = [
            [self.textures[_struct_unpack('=h', buf)[0]]
             for _ in range(num)] for _ in range(fnum)
        ]
        buf.seek(home)

        # bodypart
        (num, off) = _struct_unpack('=ii', buf)
        buf.seek(off)
        self.bodyparts = list(map(MDLBodyPart, [buf]*num))
        # home = buf.tell()
        self._bone_assemble()

    def _bone_assemble(self):
        for bone in self.bones:
            if bone.parent_id < 0:
                self.root_bone = bone
                continue
            parent = self.bones[bone.parent_id]
            bone.parent = parent
            parent.children.append(bone)


from enum import Enum, auto
import json
import logging
import random
import time
import uuid

from . import db
from . import pokedex

from .. import config

logger = logging.getLogger(__name__)

# TODO: load from gameinfo.txt
GAMEINFO = "Final Fantasy 4 (SNES)"
# TODO: command cooldown (per channel)

class BannedException(Exception):
    def __init__(self, channel):
        self.channel = channel

class ResponseEvent(dict):
    """Render our own msgs through the bot."""
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__dict__ = self
        self.nickname = "Ashnasbot"
        cfg = config.Config()
        name = cfg['displayname'] if 'displayname' in cfg else cfg['username']
        self.tags = {
            'display-name': name,
            'badges': [],
            'emotes': [],
            'user-id': cfg["user_id"]
        }
        self.id = str(uuid.uuid4())
        self.extra = ['quoted']
        self.type = 'TWITCHCHATMESSAGE'
        self.priv = PRIV.COMMON

class OrderedEnum(Enum):
    def __ge__(self, other):
        if self.__class__ is other.__class__:
            return self.value >= other.value
        return NotImplemented

    def __gt__(self, other):
        if self.__class__ is other.__class__:
            return self.value > other.value
        return NotImplemented

    def __le__(self, other):
        if self.__class__ is other.__class__:
            return self.value <= other.value
        return NotImplemented

    def __lt__(self, other):
        if self.__class__ is other.__class__:
            return self.value < other.value
        return NotImplemented

class PRIV(str, OrderedEnum):
    COMMON = auto()
    SUB = auto()
    VIP = auto()
    MOD = auto()
    OWNER = auto()
    STAFF = auto()

def handle_command(event):
    etags = event.tags
    raw_msg = event.message
    logger.info(f"{etags['display-name']} COMMAND: {raw_msg}")
    args = raw_msg.split(" ")
    command = args.pop(0).lower()
    cmd = COMMANDS.get(command, None)

    ret_event = ResponseEvent()
    ret_event.channel = event.channel
    ret_event.tags['caller'] = event.tags['display-name']
    ret_event.tags['user-type'] = event.tags['user-type']

    priv_level = PRIV.COMMON
    if 'staff' in event.tags['badges']:
        priv_level = PRIV.STAFF
    elif 'broadcaster' in event.tags['badges']:
        priv_level = PRIV.OWNER
    elif 'moderator' in event.tags['badges']:
        priv_level = PRIV.MOD
    elif 'vip' in event.tags['badges']:
        priv_level = PRIV.VIP
    elif 'subscriber' in event.tags['badges']:
        priv_level = PRIV.SUB

    ret_event.priv = priv_level
    ret_event.tags['response'] = True
    if callable(cmd):
        try:
            ret_event = cmd(ret_event, *args)
            ret_event.priv = ""
            return ret_event
        except Exception:
            return

def handle_other_commands(event):
    try:
        if event._command == "PRIVMSG":
            return

        if event._command == "CLEARMSG":
            logger.debug("CLEAR: %s", event.tags['target-msg-id'])
            return {
                    'nickname': event.tags['login'],
                    'orig_message': event._params,
                    'id': event.tags['target-msg-id'],
                    'type': event._command
                    }
        elif event._command == "CLEARCHAT":
            user = event.tags.get('target-user-id', "")
            logger.debug("CLEAR: %s from %s", user, event.tags['room-id'])
            return {
                    'id': str(uuid.uuid4()),
                    'user': user,
                    'room': event.tags['room-id'],
                    'type': event._command
                    }
        elif event._command == "RECONNECT":
            ret_event = ResponseEvent()
            logger.warn("Twitch chat is going down")
            ret_event['message'] = "Twitch chat is going down"
            return ret_event
        # elif event._command == "HOSTTARGET":
        #     ret_event = ResponseEvent()
        #     if event.message.startswith("- "):
        #         ret_event['message'] = "Stopped hosting"
        #     else:
        #         print(event.message)
        #         logger.debug(event.message)
        #         channel = re.search(r"(\w+)\s[\d-]+", event.message).group(1)
        #         ret_event['message'] = channel
        #         ret_event['type'] = "HOST"
        #     logger.info("HOST %s", ret_event['message'])
        #     return ret_event

    except Exception as e:
        logger.warn(e)
        return

def goaway_cmd(event, *args):
    if event.priv < PRIV.MOD:
        event["message"] = "Only a mod or the broadcaster can remove me"
        return event
    raise BannedException(event["channel"])

def no_cmd(event, who, *args):
    remainder = " ".join(args)
    event["message"] = f"No {who} {remainder}"
    return event

def beta_cmd(event, *args):
    event["message"] = "*Ralph Wiggum voice* I'm in Beta"
    return event

def gameinfo_cmd(event, *args):
    event["message"] = GAMEINFO
    return event

def mantras_cmd(event, *args):
    event["message"] = """Wrong game! this is Final Fantasy,
                          but the mantras are here: https://pad.riseup.net/p/GUZZZVN-xPDnUJv-pEzM-keep"""
    return event

def approve_cmd(event, *args):
    event["message"] = "https://clips.twitch.tv/TrustworthyFaintFalconRlyTho"
    return event

def win_cmd(event, *args):
    val = random.randint(30, 2000)
    caller = event.tags['caller']
    if random.randint(1, 10) == 1:
        event["message"] = f"{caller} looses"
    else:
        event["message"] = f"{caller} wins {val} points"
    return event

def save_cmd(event, *args):
    if random.randint(1, 10) == 1:
        event["message"] = "But did you Dave?"
    else:
        event["message"] = "But did you save?"
    return event

def drink_cmd(event, *args):
    event["message"] = "Every time a character could have explained something but instead says 'nothing', we take a drink."
    return event

def hello_cmd(event, *args):
    who = event.tags['caller']
    event["message"] = who
    return event

def bs_cmd(event, *args):
    event["message"] = """We're experiencing this game together for the first time,
                          please don't spoil it if you already know."""
    return event

def so_cmd(event, who, *args):
    if event.priv < PRIV.VIP:
        return

    if who.lower() == "theadrain":
        event["message"] = f"Shoutout to {who} at https://twitch.tv/{who.lower()} - they are the best egg <3"
    else:
        event["message"] = f"Shoutout to {who} at https://twitch.tv/{who.lower()} - they are a good egg <3"
    return event

def uptime(event, *args):
    if event.tags['caller'].lower() != 'darkshoxx':
        return

    event["message"] = "You're late, darkshoxx!"
    return event

def pokedex_cmd(event, *args):
    if not args:
        player = event["channel"]
        dex = pokedex.get_player_pokedex(player)
        num = len([row for row in dex if row['caught']])
        event["message"] = f"{player} has caught {num}/151 Pokémon"

        return event

    num_or_name = args[0]
    pokemon = pokedex.get_pokemon(num_or_name)
    if pokemon:
        caughtby = json.loads(pokemon["caughtby"])
        caught_text = ""
        found_text = ""
        dex_entry = ""

        if caughtby:
            caught_text = f" - caught by {list(caughtby.keys())}"

        if "found_in" in pokemon and pokemon["found_in"]:
            found_text = f" - found in {pokemon['found_in']}"

        if "dex_entry" in pokemon and pokemon["dex_entry"]:
            dex_entry = f", {pokemon['dex_entry'][:-1]}"

        event["message"] = f'Pokémon #{pokemon["id"]} is {pokemon["name"]}{found_text}{caught_text}{dex_entry}'
    else:
        event["message"] = f"Pokémon '{num_or_name}' not found"

    return event

def catch_pokemon_cmd(event, num_or_name, *args):
    if event.priv < PRIV.VIP:
        return
    event["message"] = "This isn't Pokémon"
    return event

    pokemon = pokedex.get_pokemon(num_or_name)

    try:
        if pokemon:
            pokedex.player_pokedex_catch(event["channel"], pokemon["id"])
            caughtby = json.loads(pokemon["caughtby"])
            caughtby[event["channel"]] = f"{time.time()}"
            pokemon["caughtby"] = json.dumps(caughtby)
            db.update("pokedex", pokemon, ["name"])
            event["message"] = f"{pokemon['name']} was caught by {event['channel']}"
        else:
            event["message"] = f"Pokemon '{num_or_name}' not found"
    except Exception as e:
        print(e)
        event["message"] = ""

    return event

def poke_info_cmd(event, *args):
    event["message"] = """Using a Super Gameboy 2 and a Link-Cable to Internet adaptor,
                          we're getting 151 Pokemon the original way"""
    return event

def uncatch_pokemon_cmd(event, num, *args):
    if event.priv < PRIV.VIP:
        return

    pokedex.player_pokedex_catch(event["channel"], num, False)

def red_cmd(event, *args):
    event["message"] = "twitch.tv/theadrain is playing Red"
    return event

def blue_cmd(event, *args):
    event["message"] = "yes, it is blue"
    return event

def green_cmd(event, *args):
    event["message"] = "Look Dorothy, it's green"
    return event

PRAISE_ENDINGS = [
    "saviour of ages!",
    "beware of false prophets",
    "P R A I S E",
    "GDPR compliant",
    "Euclidian",
    "Non-Euclidian",
    "Tubular",
    "Uninflammable",
    "Hydrate",
    "Lost but not forgotten"
    "mostly hyperbole",
    "has pictures of Spiderman",
    "turing complete",
    "undefeated",
    "gud at speeling",
    "this isn't even their final form",
    "pet friendly",
    "& Knuckles",
    "HTTP Error 418 (Teapot Error)",
    "follows the train, CJ",
    "may contain nuts",
    "Wololo.",
    "lord and saviour",
    "and also CUBE",
    "healer of leopards",
    "a good egg",
    "like and subscribe",
    "'cause why not?",
    "{praise} {praise} {praise}",
    "marginally above average",
    "Rock-Paper-Scissors champion of 1994",
    "accept some substitutes",
    "contains chemicals known to the State of California to ... be safe",
    "great at a barbecue",
    "tell your friends",
    "easy to clean",
    "ＤＥＬＵＸＥ",
    "™",
    "All rights reserved",
    "jack of all trades",
    "'IwlIj jachjaj",
    "all transactions are final",
    "tax-deductable!",
    "likes ice-cream",
    "can't be all bad",
    "in stereo!",
    "now for only 19,99",
    "better than Baby Shark",
    "\"The best thing on the internet.\" - Abraham Lincoln",
    "omnishambles!",
    "'aint afraid of no ghost",
    "12/10",
    "better than a bucket of steam",
    "can be worn as a hat",
    "available in all good toystores"
]

CALM = [
    "Add three drops of orange blossom oil to a cup of mineral water, and spray it from an atomiser when you need to feel relaxed.",
    "Concentrate on silence. when it comes, dwell on what it sounds like. Then strive to carry that quiet with you wherever you go.",
    "Hard-working people never waste time on frivolous, fun-filled activities. Yet, for hard-working people, any time spent this way is far from wasted.",
    "As harsh as it may sound, mixing with highly stressed people will make you feel stressed. on the other hand, mixing with calm people - even for the breifest time - will leave you feeling calm.",
    "When you dwell on the sound of your breathing, when you can really feel it coming and going, peace will not be far behind.",
    "There's always a temptation to lump all your life changes into one masochistic event. Do your stress levels a favour and take on changes one at a time.",
    "The more beautiful your fruit bowl, the better stocked it is, the less likely you are to turn to stress-enhancing snack foods. Eat more fruit, you'll feel more relaxed, it's as sweet as that.",
    "The most important skill in staying calm is not to lose sleep over small issues. The second most important skill is to be able to view ALL issues as small issues.",
    "Start every journey ten minutes early. Not only will you avoid the stress of haste, but if all goes well you'll have ten minutes to relax before your next engagement.",
    "Most worries are future-based. They revolve around things that, in most cases, will never happen. Concentrate on the present and the future will take care of itself.",
    "If you substitute a herbal tea such as peppermint for more stimulating drinks such as coffee and tea, your ability to be calm will be enhanced many times.",
    "If you want to trick your subconscious into helping you feel calm, simply repeat: 'Every moment I feel calmer and calmer.'"
]

def praise_cmd(event, praise, *args):
    ending = random.sample(PRAISE_ENDINGS, 1)[0]
    message = " ".join([praise, *args])
    event["message"] = f"Praise {message} - {ending.format(praise=message)}"
    return event

def calm_cmd(event, *args):
    event["message"] = random.sample(CALM, 1)[0]
    return event

def death_cmd(event, *args):
    if event.priv < PRIV.VIP:
        return

    if not db.exists("channel"):
        db.create("channel", primary="channel")
    try:
        data = db.find("channel", channel=event["channel"])
        if data is None:
            data = {"channel": event["channel"], "deaths": 0}
        DEATHS = data["deaths"]
    except Exception:
        DEATHS = 0

    plus = " ".join(args)
    update = True
    if plus == "":
        update = False
    elif plus == "++":
        DEATHS += 1
    elif plus == "--":
        DEATHS -= 1
    else:
        try:
            DEATHS = int(plus)
        except ValueError:
            update = False
            event["message"] = f"/me unknown deaths value: {plus}"
            return event

    if update:
        data["deaths"] = DEATHS
        # TODO: logger
        db.update("channel", data, ["channel"])

    times = "times"
    if DEATHS == 1:
        times = "time"
    event["message"] = f"/me Our illustrious strimmer has died {DEATHS} {times}"
    return event

def proffer_cmd(event, *args):
    proffered = " ".join(args)
    event["message"] = f"!add {proffered}"
    return event

def discord_cmd(event, *args):
    event["message"] = "Ashnas has one too! https://discord.gg/2xR2fxr"
    return event

COMMANDS = {
    '!goawayashnasbot': goaway_cmd,
    '!no': no_cmd,
    '!so': so_cmd,
    '!bs': bs_cmd,
    '!dq': discord_cmd,
    '!discord': discord_cmd,
    '!ashnasbot': hello_cmd,
    '!backseat': bs_cmd,
    '!praise': praise_cmd,
    '!calm': calm_cmd,
    '!deaths': death_cmd,
    '!uptime': uptime,
    '!proffer': proffer_cmd,
    # Poke
    '!pokedex': pokedex_cmd,
    '!catch': catch_pokemon_cmd,
    '!uncatch': uncatch_pokemon_cmd,
    '!151': poke_info_cmd,
    '!red': red_cmd,
    '!blue': blue_cmd,
    '!green': green_cmd,
    # meme
    '!gameinfo': gameinfo_cmd,
    '!mantras': mantras_cmd,
    '!approve': approve_cmd,
    '!beta': beta_cmd,
    '!win': win_cmd,
    '!save': save_cmd,
    '!drink': drink_cmd,
}


from rest_framework import serializers

from apps.churchs.models import Church

class ChurchInfoSerializer(serializers.ModelSerializer):
    church_id = serializers.SerializerMethodField()
    denomination = serializers.SerializerMethodField()
    denomination_list = serializers.SerializerMethodField()
    directions_parking = serializers.SerializerMethodField()
    directions_own_car = serializers.SerializerMethodField()
    directions_public_transport = serializers.SerializerMethodField()
    directions_shuttle_bus = serializers.SerializerMethodField()

    class Meta:
        model = Church
        fields = (
            "church_id",
            "name",
            "contact_number",
            "denomination",
            "denomination_list",
            "introduction_title",
            "introduction_content",
            "is_exposure",
            "address",
            "detail_address",
            "thumbnail",
            "logo",
            "directions_parking",
            "directions_own_car",
            "directions_public_transport",
            "directions_shuttle_bus",
        )

    def get_church_id(self, obj):
        try:
            return obj.id
        except:
            return ""

    def get_denomination(self, obj):
        try:
            return obj.denomination.name
        except:
            return ""

    def get_denomination_list(self, obj):
        return self.context.get("denomination_list", [])

    def get_directions_parking(self, obj):
        try:
            return obj.churchdirections.parking
        except:
            return ""

    def get_directions_own_car(self, obj):
        try:
            return obj.churchdirections.own_car
        except:
            return ""

    def get_directions_public_transport(self, obj):
        try:
            return obj.churchdirections.public_transport
        except:
            return ""

    def get_directions_shuttle_bus(self, obj):
        try:
            return obj.churchdirections.shuttle_bus
        except:
            return ""


from fastapi import Depends, HTTPException, status, WebSocket, Query
from jose import jwt, JWTError
from sqlalchemy.orm import Session

from database.db import SessionLocal
from database import models
from security import security_token, schemas
from services import get_user_by_username_or_email

def get_db():
    """Returns a database `Session`.

    Yields:
        `Iterator[SessionLocal]`: A database connection.
    """

    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

def get_user(user_token: str, db: Session):
    """Returns the current user if he passes authentication.

    Args:
        `user_token` (str, optional): User's token.
        `db` (Session, optional): Database connection.

    Raises:
        `HTTPException`: If there's no key `sub` in a given token.
        `HTTPException`: In case of JWTError.
        `HTTPException`: If there's no user with this username.

    Returns:
        models.User: A current user.
    """

    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(user_token, security_token.SECRET_KEY, algorithms=[security_token.ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        token_data = schemas.TokenData(username=username)
    except JWTError:
        raise credentials_exception

    user = get_user_by_username_or_email(db=db, username=token_data.username)

    if user is None:
        raise credentials_exception
    return user

async def get_current_user(
        user_token: str = Depends(security_token.oauth2_scheme),
        db: Session = Depends(get_db)
) -> models.User:
    return get_user(user_token, db)

async def get_token_in_query(
    websocket: WebSocket,
    token: str = Query(...),
):
    return token.replace('Bearer ', '')

async def get_current_user_by_query(
        user_token: str = Depends(get_token_in_query),
        db: Session = Depends(get_db)
) -> models.User:
    return get_user(user_token, db)


import HtmlTestRunner
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.service import Service as FirefoxService
from selenium.webdriver.support.wait import WebDriverWait
from webdriver_manager.firefox import GeckoDriverManager

import time
import unittest

class AddProductTest(unittest.TestCase):
    global driver

    @classmethod
    def setUpClass(cls):
        cls.driver = webdriver.Firefox(service=FirefoxService(GeckoDriverManager().install()))
        cls.driver.get("https://demo.nopcommerce.com/")
        cls.driver.implicitly_wait(10)
        cls.wait = WebDriverWait(cls.driver, 10)

    def test_login(self):
        click_login = self.driver.find_element(By.LINK_TEXT, "Log in")
        click_login.click()

        # check condition
        expected_title = "nopCommerce demo store. Login"
        self.assertEqual(expected_title, self.driver.title)
        assert True
        time.sleep(2)
        # select located element
        email_field = self.driver.find_element(By.ID, "Email")
        password_filed = self.driver.find_element(By.ID, "Password")
        login = self.driver.find_element(By.XPATH, "//button[normalize-space()='Log in']")

        # input data
        self.assertEqual(email_field.is_enabled(), email_field.is_displayed())
        email_field.send_keys("hello@gmail.com")
        self.assertTrue(password_filed.is_displayed(), password_filed.is_enabled())
        try:
            password_filed.send_keys("123456")
        except:
            self.driver.get_screenshot_as_file("G:\\SQA\\NopCommerce\\ScreenShoot\\login1.png")
        login.click()

    def test_remove_product(self):
        # click shopping cart
        click_shopping_cart = self.driver.find_element(By.CSS_SELECTOR, ".cart-label")
        click_shopping_cart.click()

        # check condition after click shopping cart
        actual_text = self.driver.find_element(By.XPATH, "//h1[normalize-space()='Shopping cart']").text
        expected_text = "Shopping cart"
        self.assertTrue(actual_text, expected_text)
        assert True

        # remove button click
        time.sleep(2)
        click_remove_icon = self.driver.find_element(By.XPATH, "//button[@class='remove-btn']")
        if click_remove_icon.is_enabled() and click_remove_icon.is_displayed():
            try:
                click_remove_icon.click()
            except:
                self.driver.get_screenshot_as_file("G:\\SQA\\NopCommerce\\ScreenShoot\\removeproduct.png")

        # single product have added and remove after check conditon
        expected_text_after_remove = self.driver.find_element(By.XPATH, "//div[@class='no-data']")
        actual_text_after_remove = "Your Shopping Cart is empty!"
        self.assertTrue(expected_text_after_remove, actual_text_after_remove)
        try:
            print("Remove succesfull.....")
        except:
            self.driver.get_screenshot_as_file("G:\\SQA\\NopCommerce\\ScreenShoot\\removeproduct1.png")

    @classmethod
    def tearDownClass(cls):
        cls.driver.quit()

if __name__ == "__main__":
    unittest.main(
        testRunner=HtmlTestRunner.HTMLTestRunner(output="G://SQA//NopCommerce//Report//removeproduct"))


"""
This file is part of AcurusTrack.

    AcurusTrack 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 3 of the License, or
    (at your option) any later version.

    AcurusTrack 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 AcurusTrack.  If not, see <https://www.gnu.org/licenses/>.
"""

import json
import multiprocessing
import os
import random

import numpy as np

import FCS.fixed_coordinate_system as fixu
import utils.processing.meta_processing_utils as mpu
import utils.utils_ as util
import utils.utils_pandas_df as pdu
from config import MetaProcessingParams, SystemParams, AcceptanceParams, LogicParams
from processing.dataframe_processing import DataframeProcessing
from processing.file_processing import FileProcessing
from processing.meta_processing import MetaPartition, MetaPreparation
from track.tracker_merger import TrackerMerger

"""Main components for running algorithm"""

def choose_best_csv_final_last(path_meta):
    folders_ = os.listdir(path_meta)
    folders = [f for f in folders_ if
               not (f.endswith('.png') or f.endswith('.сsv') or f.endswith('.txt')) and f != 'utils']
    folder = folders[0]
    final_folder_path = os.path.join(path_meta, folder)
    json_files = os.listdir(final_folder_path)
    best_json = [f for f in json_files if f.endswith("LAST_TRUE.сsv")]
    if not best_json:
        ratio_json = [f for f in json_files if f.endswith("BEST_RATIO.csv")]
        choice = ratio_json[0]
    else:
        choice = best_json[0]
    path_to_best_json = os.path.join(final_folder_path, choice)
    return path_to_best_json

def make_new_numeration_dict(func):
    def decorator(ref):
        meta, hom = func(ref)
        if MetaProcessingParams.renumbering:
            meta = mpu.change_meta_numeration(
                meta)
        return meta, hom

    return decorator

def get_original_coordinates(func):
    def decorator(ref):
        meta, homography_dict = func(ref)
        if MetaProcessingParams.fixed_coordinate_system:
            meta = fixu.fixed_to_original_coordinate_system(
                meta, homography_dict, int(os.environ.get('fixed_coordinate_resize_h')),
                int(os.environ.get('fixed_coordinate_resize_w')), int(os.environ.get('img_h')),
                int(os.environ.get('img_w'))
            )
        return meta, homography_dict

    return decorator

def make_new_numeration_pandas(func):
    def decorator(ref):
        meta = func(ref)
        if MetaProcessingParams.renumbering:
            new_indexes = pdu.get_update_indexes_rule(
                meta)  # renumbering by quantity
            meta = meta.replace({
                'id': new_indexes})
        return meta

    return decorator

class MainAlgo:
    def __init__(self, detections, homography_dict,
                 global_start_frame=None, global_end_frame=None):
        os.environ['PYTHONHASHSEED'] = str(SystemParams.seed)
        random.seed(SystemParams.seed)
        np.random.seed(SystemParams.seed)

        self.res_dir = os.environ.get('RES_DIR')
        self.homography = homography_dict

        self.start_frame, self.end_frame = self.determine_start_end(global_start_frame, global_end_frame, detections)
        self.full_meta = self.initialise_meta(detections, 'full')  # change detections inside
        self.__windows = self.get_windows()
        self.wind_objects = self.prepare_objs_for_each_window()

    @staticmethod
    def determine_start_end(start, end, detections):
        if not end:
            end = int(sorted(list(detections.keys()))[-1])
        else:
            a = detections.get(end, None)
            if not a:
                raise ValueError('there is no such end in meta')
        if not start:
            start = int(sorted(list(detections.keys()))[0])
        else:
            a = detections.get(start, None)
            if not a:
                raise ValueError('there is no such start in meta')
        return start, end

    @property
    def windows(self):
        return self.__windows

    @make_new_numeration_pandas
    def analysis(self):

        self.process_windows_separately()
        overlapped_windows = make_best_windows(
            self.res_dir)  # do merge by overlapped windows
        if len(self.wind_objects) > 1 and LogicParams.use_final_merge:
            final_meta = self.final_merge_single(overlapped_windows)
        else:
            final_meta = overlapped_windows

        return final_meta

    @make_new_numeration_dict
    @get_original_coordinates
    def get_meta(self):
        final_meta = self.analysis()
        final_meta_dict = pdu.from_dataframe_to_dict(
            final_meta)
        return final_meta_dict, self.homography

    def run_analyser(self):
        final_meta_dict, hom = self.get_meta()
        final_meta_dir = os.path.join(os.environ.get('EXP_DIR'), 'result')
        if not os.path.exists(final_meta_dir):
            os.makedirs(final_meta_dir)
        final_meta_path = os.path.join(final_meta_dir, 'result.json')
        with open(final_meta_path, 'w') as final_meta:
            json.dump(final_meta_dict, final_meta)

    def get_windows(self):
        """ Choose windows for processing according to density of the tracks"""
        ids = []
        curr_start = self.start_frame
        curr_window_len = 0
        windows = {}
        segment = {}
        frame_no = curr_start
        while True:
            if frame_no not in self.full_meta.data:
                frame_no += 1
                if frame_no == self.end_frame:
                    break
                continue
            frame_info = self.full_meta.data[frame_no]
            segment[frame_no] = frame_info
            for elem in frame_info:
                data = elem.get('index', None)
                if data is not None:
                    if elem['index'] not in ids:
                        ids.append(elem['index'])
                        curr_window_len += 1
            if curr_window_len > MetaProcessingParams.max_tracks_number_at_window or frame_no == self.end_frame:
                windows[str(curr_start) + '_' + str(frame_no)] = segment
                segment = {}
                curr_window_len = 0
                frame_no -= MetaProcessingParams.overlap
                curr_start = frame_no
                if frame_no + MetaProcessingParams.overlap >= self.end_frame:
                    break
            frame_no += 1
        assert windows
        return windows

    def prepare_objs_for_each_window(self):
        wind_objects = []
        for name, window in self.windows.items():
            meta_object = MetaPartition(window, pdu.dataframe_from_dict(window), self.homography, name)
            files_work = FileProcessing(meta_object, '{}'.format(name))
            meta_object.add_observer(files_work)
            processed_meta = DataframeProcessing(meta_object)
            meta_object.add_observer(processed_meta)
            tracker_obj = TrackerMerger(processed_meta, meta_object, files_work)
            # tracker_obj = TrackerMergerSpliter(processed_meta, meta_object, files_work)
            wind_objects.append(tracker_obj)
        return wind_objects

    def initialise_meta(self, meta, name):
        meta_start_end = util.fill_and_format(meta, self.start_frame, self.end_frame)
        meta_object = MetaPartition(meta_start_end, None, self.homography,
                                    name)  # do not have dataframe form, so pass None
        preparation = MetaPreparation()
        meta_object.apply(preparation)
        with open(os.path.join(self.res_dir, 'initialised.json'), 'w') as json_to_save:
            json.dump(meta_object.data, json_to_save)
        meta_object.data_df.to_csv(os.path.join(self.res_dir,
                                                '{}.csv'.format('initialised')))

        return meta_object

    def process_windows_separately(self):
        """ Process in parallel windows with algorithm"""
        if not SystemParams.use_multiprocessing:
            for single_obj in self.wind_objects:
                window_processing(single_obj)
        else:
            num_cores = multiprocessing.cpu_count()
            pool = multiprocessing.Pool(processes=num_cores)
            self.wind_objects = [(i, None) for i in self.wind_objects]
            pool.starmap(window_processing, self.wind_objects)
            pool.close()

    @staticmethod
    def update_config_for_final():
        AcceptanceParams.acc = 0.0
        os.environ['experiment_name_final'] = os.environ.get('exp_name') + '_merged_processed_final'
        os.environ['RES_DIR'] = os.path.join(
            os.environ.get('EXP_DIR'),
            os.environ.get('experiment_name_final'))
        if not os.path.exists(os.environ.get('RES_DIR')):
            os.makedirs(os.environ.get('RES_DIR'))

    def final_merge_single(self, overlapped_windows):
        """ Final merge, optional"""
        self.update_config_for_final()
        name = 'final'
        meta_object = MetaPartition(None, overlapped_windows, self.homography, name)
        file_dir = FileProcessing(meta_object, name)
        file_dir.create_dir()
        meta_object.add_observer(file_dir)
        processed_meta = DataframeProcessing(meta_object)
        meta_object.add_observer(processed_meta)
        tracker_obj = TrackerMerger(processed_meta, meta_object, file_dir)

        window_processing(tracker_obj, final_merge=True)
        final_json = util.choose_best_csv_final_last(os.environ.get('RES_DIR'))
        final_json_meta = pdu.read_multiindex_pd(final_json)
        return final_json_meta

def load_and_clean_csv(csv_path):
    curr_meta = pdu.read_multiindex_pd(csv_path)
    curr_meta = curr_meta[~curr_meta['id'].isin(MetaProcessingParams.false_indexes)]
    new_indexes = pdu.get_update_indexes_rule(curr_meta)
    curr_meta = curr_meta.replace({'id': new_indexes})
    curr_meta.to_csv(csv_path)
    return curr_meta

def make_best_windows(path_to_meta_folder):
    chosen_files = util.choose_csv_from_dir(path_to_meta_folder)
    curr_meta = load_and_clean_csv(chosen_files[0])
    all_info = curr_meta
    indexes_curr = pdu.get_current_meta_indexes(curr_meta)
    counter_curr = len(indexes_curr) + 1

    for i in range(1, len(chosen_files)):
        next_json_info = load_and_clean_csv(chosen_files[i])
        all_info, counter_curr = pdu.merge_two_consecutive_windows(all_info, next_json_info,
                                                                   counter_curr)
    all_info.to_csv(
        os.path.join(
            path_to_meta_folder,
            'final_processing_merged_MCMC.csv'))
    return all_info

def window_processing(wind_obj, final_merge=None):
    """ Processing of single window."""

    wind_obj.final_merge = final_merge
    wind_obj.algo_iteration()


import cv2 as cv
import numpy as np
from package.Rectangle import Rectangle

class CatchObject:

     def __init__(self, color_bgr, name, needle_img_path, scale_percent):
         self.line_color = color_bgr
         self.name = name
         self.method = cv.TM_CCOEFF_NORMED
         #self.method = cv.TM_SQDIFF_NORMED
         self.needle_img = cv.imread(needle_img_path, cv.IMREAD_COLOR)
         self.needle_w = self.needle_img.shape[1]  # Collim width
         self.needle_h = self.needle_img.shape[0]  # Collim heigth

         self.scale_percent = scale_percent  # %
         self.needle_width_resized = int(self.needle_img.shape[1] * self.scale_percent / 100)
         self.needle_height_resized = int(self.needle_img.shape[0] * self.scale_percent / 100)
         self.dim = (self.needle_width_resized, self.needle_height_resized)
         self.need_img_resized = cv.resize(self.needle_img, self.dim, interpolation=cv.INTER_AREA)

     def findPosition(self, haystack_img, threshold=0.5):
        """
        Donada una imatge, retorna les posicions de l'objecte.
        :param haystack_img: Imatge on es vol cercar
        :param threshold: Percentatge acceptat
        :return: Llista de objecte Rectangle.
        """

        # Si la imatge on volem cercar l'objecte és més petita que la del propi objecte. Retornam directament.
        if haystack_img.shape[0] <= self.needle_w or haystack_img.shape[1] <= self.needle_h:
            return []

        result = cv.matchTemplate(haystack_img, self.needle_img, self.method)

        locations = np.where(result >= threshold)
        #locations = np.where(result <= threshold)

        locations = list(zip(*locations[::-1])) # list(): convert an iterator to list
                                                # zip():  returns an iterator of tuples based on the iterable objects.
                                                # The * operator can be used in conjunction with zip() to unzip the list
                                                # [::-1]: Sintaxis iterable[inicio:fin:paso] Devuelve una lista al revés

        # Cream la llista dels rectangles [pos_x, pos_y, width, height]
        rectangles = []
        for loc in locations:
            rect = [int(loc[0]), int (loc[1]), self.needle_w, self.needle_h]
            rectangles.append(rect)

        # Agrupam els rectangles en un. Ja que un mateix objecte pot estar més d'una vegada rectangulat.
        rectangles, weights = cv.groupRectangles(rectangles, groupThreshold=1,
                                                 eps=0.5)   # El darrer paràmetre ens diu com de aprop estan els
                                                            # rectangles que volem agrupar

        if len(rectangles) > 10:
            rectangles = rectangles[:10]

        rectangle_object = []
        for (x, y, w, h) in rectangles:
            rectangle_object.append(Rectangle(x, y, w, h, self.line_color, self.name))

        return rectangle_object

     def findPosition_reduce(self, haystack_img, threshold=0.5):
         """
         Donada una imatge, retorna les posicions de l'objecte. Internament duu a terme una reducció de la imatge
         de cerca per tal d'aconseguir major rendiment de l'algoritme.
         :param haystack_img: Imatge on es vol cercar
         :param threshold: Percentatge acceptat
         :return: Llista de objecte Rectangle.
         """
         haystack_width = haystack_img.shape[1] * self.scale_percent / 100
         haystack_height = haystack_img.shape[0] * self.scale_percent / 100
         haystack_dim = (int(haystack_width), int(haystack_height))

         # Redimensionam la imatge
         haystack_resized = cv.resize(haystack_img, haystack_dim, interpolation=cv.INTER_AREA)

         # Si la imatge on volem cercar l'objecte és més petita que la del propi objecte. Retornam directament
         if haystack_width <= self.needle_width_resized or haystack_height <= self.needle_height_resized:
             return []

         result = cv.matchTemplate(haystack_resized, self.need_img_resized, self.method)

         locations = np.where(result >= threshold)
         locations = list(zip(*locations[::-1]))

         # Cream la llista dels rectangles [pos_x, pos_y, width, height]
         rectangles = []
         for loc in locations:
             rect = [int(loc[0])+1, int(loc[1])+1, self.needle_width_resized, self.needle_height_resized]
             rectangles.append(rect)

         # Agrupam els rectangles en un. Ja que un mateix objecte pot estar més d'una vegada rectangulat.
         rectangles, weights = cv.groupRectangles(rectangles, groupThreshold=1,
                                                  eps=0.5)  # El darrer paràmetre ens diu com de aprop estan
                                                            # els rectangles

         if len(rectangles) > 10:
             rectangles = rectangles[:10]

         rectangle_object = []
         for (x, y, w, h) in rectangles:
             rectangle_object.append(Rectangle(int(x * (1/(self.scale_percent/100))),
                                               int(y * (1/(self.scale_percent/100))),
                                               int(w * (1/(self.scale_percent/100))),
                                               int(h * (1/(self.scale_percent/100))), self.line_color, self.name))

         return rectangle_object


from gql import Client
from gql.transport.requests import RequestsHTTPTransport
from rest_framework import status
from rest_framework.response import Response
from rest_framework.status import HTTP_200_OK
from rest_framework.views import APIView
from .mutations import orderCreateFromCheckoutMutation
from .settings import SALEOR_API, SALEOR_API_TOKEN

#Order Pro
from OrderPro.models import OrderCheckoutTasks

transport = RequestsHTTPTransport(
    url=SALEOR_API,
    headers={'Authorization': f"Bearer {SALEOR_API_TOKEN}"},
    verify=True,
    retries=3,
)

client = Client(transport=transport, fetch_schema_from_transport=True)

class CreateOrderFromCheckout(APIView):

    def post(self, request):

        checkout_id = request.data.get('checkoutID')
        execution_time = request.data.get('DateTime')

        if not execution_time :

            remove_checkout = True
            data = client.execute(orderCreateFromCheckoutMutation, variable_values={
                "id": checkout_id,
                "removeCheckout": remove_checkout
            })

            return Response(data, status=status.HTTP_200_OK)

        else:

            OrderPro = OrderCheckoutTasks(checkoutID = checkout_id, execution_time = execution_time )
            OrderPro.save()
            #   - Quantité her
            return Response({"status": "Order is reserved"}, status=status.HTTP_200_OK)


from django.urls import path
from .import views

urlpatterns = [
    path('',views.index,name='index'),
    path('main',views.main,name='main'),
     path('adminpage',views.adminpage,name='adminpage'),
     path('adminlogin',views.adminlogin,name='adminlogin'),
     path('admin',views.admin,name='admin'),
     path('signout',views.signout,name='signout'),
     path('insertcontact',views.insertcontact,name='insertcontact'),
     path('admincontact',views.admincontact,name='admincontact'),
     path('adminvideo',views.adminvideo,name='adminvideo'),
     path('insertvideo',views.insertvideo,name='insertvideo'),
     path('deletecontact/<int:pk>',views.deletecontact,name='deletecontact')
]