import numpy as np
import cv2

def disparity_ncorr(L, R, block_size=5, disparity_range=30, lambda_factor=0):
    """Compute disparity map D(y, x) such that: L(y, x) = R(y, x + D(y, x))

    Params:
    L: Grayscale left image
    R: Grayscale right image, same size as L

    Returns: Disparity map, same size as L, R
    """

    im_rows, im_cols = L.shape
    tpl_rows = tpl_cols = block_size
    D_L = np.zeros(L.shape, dtype=np.float32)

    for r in range(tpl_rows/2, im_rows-tpl_rows/2):
        tr_min, tr_max = max(r-tpl_rows/2, 0), min(r+tpl_rows/2+1, im_rows)
        for c in range(tpl_cols/2, im_cols-tpl_cols/2):
            # get template
            tc_min = max(c-tpl_cols/2, 0)
            tc_max = min(c+tpl_cols/2+1, im_cols)
            tpl = L[tr_min:tr_max, tc_min:tc_max].astype(np.float32)
            # get R strip in a window with width=disparity_range
            rc_min = max(c-disparity_range/2, 0)
            rc_max = min(c+disparity_range/2+1, im_cols)
            R_strip = R[tr_min:tr_max, rc_min:rc_max].astype(np.float32)
            # find best match of template in strip
            error = cv2.matchTemplate(R_strip, tpl, method=cv2.TM_CCORR_NORMED)
            c_tf = max(c-rc_min-tpl_cols/2, 0)
            dist = np.arange(error.shape[1]) - c_tf
            cost = error - np.abs(dist * lambda_factor)
            _,_,_,max_loc = cv2.minMaxLoc(cost)
            D_L[r, c] = dist[max_loc[0]]
    return D_L


import pandas as pd
from scipy import stats
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import seaborn as sns
import sys
#from mpl_toolkits.mplot3d import Axes3D

df = pd.read_csv('sentimentdata/'+sys.argv[1]+'sentiment.csv',index_col=0,encoding='latin-1')
dfold = df
df = df.drop('text', 1)
print(df.head())
df = df.drop('date', 1)
print(df.head())

df_tr = df
# select proper number of clusters
'''
Y = df[['followers']]
X = df[['polarity']]

Nc = range(1, 20)
kmeans = [KMeans(n_clusters=i) for i in Nc]
score = [kmeans[i].fit(Y).score(Y) for i in range(len(kmeans))]
plt.plot(Nc,score)
plt.xlabel('Number of Clusters')
plt.ylabel('Score')
plt.title('Elbow Curve')
plt.show()
'''

# elbow plot showed the point of dropoff to be around 5 clusters

#Standardize

clmns = ['followers', 'polarity', 'sentiment_confidence']

df_tr_std= stats.zscore(df_tr[clmns])

#Clustering

kmeans = KMeans(n_clusters=5, random_state=0).fit(df_tr_std)
labels = kmeans.labels_

#Glue back to original data
df_tr['clusters']=labels
dfold['clusters']=labels

clmns.extend(['clusters'])

print(df_tr[clmns].groupby(['clusters']).mean())

#Scatter plot of Wattage and Duration
sns.lmplot('polarity', 'sentiment_confidence',
           data=df_tr,
           fit_reg=False,
           hue="clusters",
           scatter_kws={"marker": "D",
                        "s": 20})

dfold.to_csv('clusterdata/'+sys.argv[1]+'cluster.csv')
plt.title('tweets grouped by polarity and sentiment_confidence')
plt.xlabel('polarity')
plt.ylabel('sentiment_confidences')
plt.show()


from locust import HttpUser, task
from PIL import Image

from nos.server.http._utils import encode_dict
from nos.test.utils import NOS_TEST_IMAGE

data = {
    "model_id": "noop/process-images",
    "inputs": {
        "images": Image.open(NOS_TEST_IMAGE),
    },
}
encoded_data = encode_dict(data)

class InferenceServiceUser(HttpUser):
    @task
    def embed(self):
        self.client.post(
            "/infer",
            headers={"Content-Type": "application/json"},
            json=encoded_data,
        )


from django.shortcuts import render
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework import viewsets
from rest_framework import generics

from rest_framework.decorators import api_view

from .models import Employee
from .serealizers import EmployeeSerializer
from .models import Category
from .serealizers import CategorySerializer

# class EmployeeList(APIView):
#     def get(self, request):
#         employee1=Employee.objects.all()
#         serializer=EmployeeSerializer(employee1,many=True)
#         return Response(serializer.data)

class EmployeeList(generics.ListAPIView):
    queryset=Employee.objects.all()
    serializer_class=EmployeeSerializer

class EmployeeViewSet(viewsets.ModelViewSet):
    queryset=Employee.objects.all()
    serializer_class=CategorySerializer

# class CategoryList(APIView):
#     def get(self, request):
#         category1=Category.objects.all()
#         serializer=CategorySerializer(category1,many=True)
#         return Response(serializer.data)
class CategoryList(generics.ListAPIView):
    queryset=Category.objects.all()
    serializer_class=CategorySerializer

@api_view(['GET','POST'])
def category_list(request, format=None):
    # list of categories or update
    if request.method=='GET':
        categories=Category.objects.all()
        serializer=CategorySerializer(categories,many=True)
        return Response(serializer.data)
    elif request.method=='POST':
        serializer=CategorySerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data,status=status.HTTP_201_CREATED)
        return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET','PUT','DELETE'])
def category_detail(request,pk,format=None):
    #retrieve, update or delete
    try:
        category=Category.objects.get(pk=pk)
    except Category.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method=='GET':
        serializer=CategorySerializer(category)
        return Response(serializer.data)
    elif request.method=='PUT':
        serializer=CategorySerializer(category,data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
    elif request.method=='DELETE':
        category.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)


#coding:utf-8
#from music_category import svm
#from music_category import feature
import feature
import pandas as pd
import numpy as np
from sklearn.externals import joblib#保存模型模块
import sys
import time
# svm.cross_validation(data_percentage=0.99)
def 加载模型(model_f = None):#load_model
    if not model_f:
        model_f = 模型保存路径
    clf = joblib.load(model_f)
    return clf

def 歌曲标签数值化():#fetch_index_label
    """
     从文件中读取index和label之间的映射关系，并返回dict
    """
    data = pd.read_csv(数值化标签路径, header=None, encoding='utf-8')
    name_label_list = np.array(data).tolist()
    index_label_dict = dict(map(lambda t: (t[1], t[0]), name_label_list))
    return index_label_dict
def 预测(clf, X):
    label_index = clf.predict([X])
    label = index_lable_dict[label_index[0]]
    return label
if __name__ == '__main__':
    数值化标签路径 = './data/music_index_label.csv'
    模型保存路径 = './data/music_model.pkl'
    index_lable_dict = 歌曲标签数值化()
    clf = 加载模型()
    #svm.多次训练并保存模型(train_percentage = 0.9, fold = 1000)
    # path = './data/test/50 Cent - Ready For War.mp3'#兴奋
    # path = './data/test/A＊Teens - Floorfiller.mp3'#流行
#     path = './data/test/Beyond - 大地.mp3'#怀旧
    path = "./data/music/光良 - 烟火.mp3"
    music_feature = feature.获取单首歌曲特征(path)
    print(music_feature.shape)
    label = 预测(clf, music_feature)
    print('预测标签为：%s'% label)

feature.获取单首歌曲特征


#!/usr/bin/env python

import argparse
import glob
import time
import os
import pointing_camera.pc_proc as pipeline
import numpy as np

default_data_dir = '/global/cfs/cdirs/desi/users/ameisner/pointing_camera/el_nino'

default_outdir = '/global/cfs/cdirs/desi/users/ameisner/pointing_camera/summer_2021/v0006'

def _proc_many_exp(flist, indstart, nproc, outdir=default_outdir):
    n_all = len(flist)

    assert(indstart < n_all)

    indend = min(indstart + nproc, n_all)

    for i in range(indstart, indend):
        f = flist[i]

        assert(os.path.exists(f))

        try:
            pipeline.pc_proc(f, outdir=outdir, dont_write_detrended=True,
                             skip_checkplot=False, nightly_subdir=True,
                             send_redis=False, one_aper=True)
        except:
            print('PROCESSING FAILURE: ' + f)

def _load_flist():
    fname = '/global/homes/a/ameisner/pointing_camera/bin/summer_2021_wcs_solutions.txt'

    flist = np.genfromtxt(fname, dtype='U')

    flist = [f.replace('.wcs', '.fits') for f in flist]

    seed = 99
    np.random.seed(seed)

    np.random.shuffle(flist)

    return flist

if __name__ == "__main__":
    descr = 'process a chunk of the summer 2021 El Nino data set'

    parser = argparse.ArgumentParser(description=descr)

    parser.add_argument('indstart', type=int, nargs=1,
                        help="starting index within list of files")

    parser.add_argument('nproc', type=int, nargs=1,
                        help="number of files to process")

    parser.add_argument('--outdir', type=str, default=default_outdir,
                        help="base output directory")

    args = parser.parse_args()

    indstart = args.indstart[0]
    nproc = args.nproc[0]

    flist = _load_flist()

    _proc_many_exp(flist, indstart, nproc, outdir=args.outdir)


from time import time
import unittest
from collections import defaultdict
a = time()

def readFile():
    # On renvoie le fichier d'entrée sous forme d'une liste de string
    with open(__file__[:-3] + "-" + "input.txt", "r") as f:
        return [[int(it) for it in i.replace(' -> ', ',').split(',')] for i in f.readlines()]

def part1(scheme: list):
    positions = defaultdict(int)
    for wind in scheme:
        if wind[0] == wind[2]:
            min_y, max_y = min(wind[1], wind[3]), max(wind[1], wind[3]) + 1
            for y in range(min_y, max_y):
                positions[(wind[0], y)] += 1
        elif wind[1] == wind[3]:
            min_x, max_x = min(wind[0], wind[2]), max(wind[0], wind[2]) + 1
            for x in range(min_x, max_x):
                positions[(x, wind[1])] += 1

    return sum(1 for v in positions.values() if v > 1)

def part2(scheme: list):
    positions = defaultdict(int)
    for wind in scheme:
        if wind[0] == wind[2]:
            min_y, max_y = min(wind[1], wind[3]), max(wind[1], wind[3]) + 1
            for y in range(min_y, max_y):
                positions[(wind[0], y)] += 1
        elif wind[1] == wind[3]:
            min_x, max_x = min(wind[0], wind[2]), max(wind[0], wind[2]) + 1
            for x in range(min_x, max_x):
                positions[(x, wind[1])] += 1
        elif abs(wind[0] - wind[2]) == abs(wind[1] - wind[3]):
            diff_for_range = abs(wind[0] - wind[2]) + 1
            for it in range(diff_for_range):
                positions[(wind[0] + (1 if wind[2] > wind[0] else -1) * it, wind[1] + (1 if wind[3] > wind[1] else -1) * it)] += 1

    return sum(1 for v in positions.values() if v > 1)

class Tests(unittest.TestCase):

    def testP1(self):
        pass

if __name__ == "__main__":
    # unittest.main()
    scheme = readFile()
    p1 = part1(scheme)
    print(f"Part 1: {p1}")
    p2 = part2(scheme)
    print(f"Part 2: {p2}")
    print(time()-a)


'''
This file contains utility functions for training the model

Functions:
    `display(display_list, to_file, root_dir, file_name, count)`
        Display a list of images and their masks
    `ensure_directory_exists(dir)`
        Ensure that a directory exists
    `normalize_example(image, mask)`
        Normalize an image and its mask
    `show_predictions(dataset, model, num)`
        Show predictions for a dataset
    `create_mask(pred_mask)`
        Create a mask from a prediction
    `save_model(model, model_name)`
        Save a model
    `load_model(model_name)`
        Load a model
    `get_tensorboard_callback(model_name)`
        Get a TensorBoard callback for a model
    `plot_history(history, model_name, aspects, height, length, to_file, root_dir)`
        Plot the history of a model

Author: Tuvya Macklin

Date: 07/17/2023

Version: 1.0.0

'''

import matplotlib.pyplot as plt
import tensorflow as tf
import os
import datetime

# Display an example with its mask
def display(display_list, to_file = True, root_dir = ".tuvya_stuff/plots/display", file_name = "img_and_mask", count = None, color_bar = False):
    '''
    Display a list of images and their masks

    Args:
        display_list (list): A list of images and their masks. The image and mask should be numpy arrays.
        to_file (bool): Whether to save the image to a file or not
        root_dir (str): The root directory to save the image to
        file_name (str): The name of the file to save the image to
        count (int): The number of the image to save

    Returns:
        None

    Side Effects:
        Saves the image to a file
    '''

    ensure_directory_exists(root_dir)

    plt.figure(figsize=(10, 10 * len(display_list)))

    title = ["Input Image", "True Mask", "Predicted Mask"]

    for i in range(len(display_list)):
        plt.subplot(1, len(display_list), i + 1)
        plt.title(title[i])
        plt.imshow(display_list[i].numpy())
        plt.axis("off")

        if i > 0 and color_bar:
            plt.colorbar()

    if to_file:
        path = os.path.join(root_dir, file_name)
        if not count is None:
            path += "-" + str(count)

        path += ".png"
        plt.savefig(path)
    else:
        plt.show()

def ensure_directory_exists(dir):
    '''
    Ensure that a directory exists

    Args:
        dir (str): The directory to ensure exists

    Returns:
        None

    Side Effects:
        Creates the directory if it does not exist
    '''
    if not os.path.exists(dir):
        os.makedirs(dir)

def normalize_example(image, mask):
    '''
    Normalize an image

    Args:
        image (tf.Tensor): The image to normalize
        mask (tf.Tensor): The mask to normalize

    Returns:
        image (tf.Tensor): The normalized image
        mask (tf.Tensor): The normalized mask

    Notes:
        The image and mask are normalized by dividing by 255.0
    '''

    image = tf.cast(image, tf.float32) / 255.0

    return image, mask

def create_mask(prediction):
    # Get the maximum value along the last axis
    # To elaborate:
    #   The prediction tensor has shape (batch_size, height, width, num_classes)
    #   The last axis is the axis with the num_classes
    #   The maximum value along this axis is the predicted class
    #   The predicted class is the mask
    prediction = tf.argmax(prediction, axis = -1)
    prediction = prediction[..., tf.newaxis]

    return prediction[0]

def show_predictions(dataset, model, num = None, root_dir = ".tuvya_stuff/plots/predictions", default_file_name = "prediction", color_bar = False):
    '''
    Display a list of images and their masks

    Args:
        dataset (tf.data.Dataset): The dataset to get the images and masks from
        num (int): The number of images to display
        model (tf.keras.Model): The model to use to predict the masks
        root_dir (str): The root directory to save the image to
        file_name (str): The name of the file to save the image to

    Returns:
        None

    Side Effects:
        Saves the image to a file
    '''

    multiple = True
    if num is None:
        num = 1
        multiple = False

    for index, (image, mask) in enumerate(dataset.take(num)):
        pred_mask = model.predict(image)

        file_name = default_file_name
        if multiple:
            file_name = default_file_name + "-" + str(index)

        display(
            [image[0], mask[0], create_mask(pred_mask)],
            root_dir = root_dir,
            file_name = file_name,
            color_bar = color_bar
        )

def save_model(model, model_name, root_model_dir = ".tuvya_stuff/models"):
    '''
    Saves a model to the given directory.

    Parameters:
        model (tf.keras.Model): The model to save.
        model_name (str): The name of the model.
        root_model_dir (str): The root directory for the models.

    Raises:
        FileNotFoundError: If the model directory doesn't exist.

    Returns:
        None

    Notes:
        The model is saved to root_model_dir/model_name/model.keras.
        The root_model_dir is "segmentation_model/models" by default.
    '''
    # Ensure that a directory exists for the model
    # The path should be model_dir/model_name
    ensure_directory_exists(os.path.join(root_model_dir, model_name))

    # Save the model
    model.save(os.path.join(root_model_dir, model_name, "model.keras"))

def load_model(model_name, root_model_dir = ".tuvya_stuff/models", path_to_model = None):
    '''
    Loads a model from the given directory.

    Parameters:
        model_name (str): The name of the model.
        root_model_dir (str): The root directory for the models.
        path_to_model (str): The path to the model file. If None, the path is root_model_dir/model_name/model.keras.

    Returns:
        The loaded model.

    Raises:
        FileNotFoundError: If the model file doesn't exist.

    Notes:
        The model is loaded from root_model_dir/model_name/model.keras.
        The root_model_dir is "segmentation_model/models" by default.
    '''
    # Ensure that the model file exists
    path = None
    if path_to_model is None:
        path = os.path.join(root_model_dir, model_name, "model.keras")
    else:
        path = path_to_model

    if not os.path.isfile(path):
        raise FileNotFoundError

    # Load the model
    model = tf.keras.models.load_model(path)
    return model

def get_tensorboard_callback(model_name: str, root_dir = ".tuvya_stuff/tensorboard"):
    '''
    Returns a TensorBoard callback for a model with the given name.

    Parameters:
        model_name (str): The name of the model.

    Returns:
        A TensorBoard callback.

    Notes:
        The TensorBoard callback is saved to root_tensorboard_dir/fit/model_name/current_date_and_time.

        To view the TensorBoard, run the following command in the terminal:
        ```
        tensorboard --logdir root_tensorboard_dir/fit/model_name
        ```
        `root_tensorboard_dir` is the root directory for the TensorBoard callbacks. The default is .tensorboard.
    '''
    log_dir = os.path.join(root_dir, "fit", model_name, datetime.datetime.now().strftime("%Y.%m.%d-%H:%M:%S"))
    tensorboard_callback = tf.keras.callbacks.TensorBoard(log_dir=log_dir, histogram_freq=1)
    return tensorboard_callback

def plot_history(history, model_name = None, aspects = ["accuracy"], height = 7, length = 7, to_file = True, root_dir = ".tuvya_stuff/plots/history"):
    '''
    Plots the history of a model.

    Parameters:
        history (tf.keras.callbacks.History): The history of the model.
        model_name (str): The name of the model.
        aspects (list): The aspects of the model to plot. The default is ["accuracy"].
        height (int): The height of the plot. The default is 7.
        length (int): The length of the plot. The default is 7.
        to_file (bool): Whether to save the plot to a file or not. The default is True.
        root_dir (str): The root directory to save the plot to. The default is "segmentation_model/train_model/plots/history".

    Returns:
        None

    Side Effects:
        Saves the plot to a file.

    Notes:
        The plot is saved to root_dir/history-model_name.png if to_file is True.
        If no model name is given, the plot is saved to root_dir/history-HH:MM.png, where HH:MM is the current time.
    '''

    plt.figure(figsize = (height, length))

    for count, aspect in enumerate(aspects):
        plt.subplot(1, len(aspects), count + 1)
        plt.plot(history.history[aspect], label = "Training " + aspect)
        plt.plot(history.history["val_" + aspect], label = "Validation " + aspect)

        plt.xlabel("Epoch")
        plt.ylabel(aspect)

        plt.title("Training and Validation " + aspect)

        if aspect == "loss":
            plt.legend(loc = "upper right")
        else:
            plt.legend(loc = "lower right")

    if to_file:
        file_name = ""
        if not model_name is None:
            file_name += model_name
        else:
            # If there is no model name, add the time in the format HH:MM
            file_name += datetime.datetime.now().strftime("%H:%M")
        file_name += ".png"

        ensure_directory_exists(root_dir)

        plt.savefig(os.path.join(root_dir, file_name))
    else:
        plt.show()

def get_unet_model(output_channels: int, down_stack, up_stack):
    '''
    Returns a U-Net model.

    Parameters:
        output_channels (int): The number of output channels.
        down_stack (list): The down stack of the U-Net.
        up_stack (list): The up stack of the U-Net.

    Returns:
        A U-Net model.

    Notes:
        The down stack should be a model with the following outputs:
        ```
        [
            (batch_size, 64, 64, 64),
            (batch_size, 32, 32, 128),
            (batch_size, 16, 16, 256),
            (batch_size, 8, 8, 512),
            (batch_size, 4, 4, 512)
        ]
        ```
        The up stack should be a model with the following outputs:
        ```
        [
            (batch_size, 8, 8, 1024),
            (batch_size, 16, 16, 512),
            (batch_size, 32, 32, 256),
            (batch_size, 64, 64, 128)
        ]
        ```
    '''
    inputs = tf.keras.layers.Input(shape = [128, 128, 3])

    skips = down_stack(inputs)
    x = skips[-1]
    skips = reversed(skips[:-1])

    for up, skip in zip(up_stack, skips):
        x = up(x)
        concat = tf.keras.layers.Concatenate()
        x = concat([x, skip])

    last = tf.keras.layers.Conv2DTranspose(
        filters = output_channels,
        kernel_size = 3,
        strides = 2,
        padding = "same"
    )

    x = last(x)

    return tf.keras.Model(inputs = inputs, outputs = x)

class AugmentLayer(tf.keras.layers.Layer):
    '''
    Implements a layer that augments the inputs and masks.

    Methods:
        `__init__(self, seed = 42)` -> `None`
        `call(inputs, mask)` -> `(inputs, mask)`
            Augments the inputs and masks.

    Notes:
        The augmentation is a random horizontal flip.

    '''
    def __init__(self, seed = 42):
        super().__init__()

        # Both layers should have the same seed so they augment in tandem
        self.augment_inputs = tf.keras.layers.RandomFlip(mode = "horizontal", seed = seed)
        self.augment_mask = tf.keras.layers.RandomFlip(mode = "horizontal", seed = seed)

        self.augment_inputs = tf.keras.layers.RandomFlip(mode = "vertical", seed = seed)
        self.augment_mask = tf.keras.layers.RandomFlip(mode = "vertical", seed = seed)

    def call(self, inputs, mask):
        inputs = self.augment_inputs(inputs)
        mask = self.augment_mask(mask)

        return inputs, mask


import sys
import itertools
from copy import deepcopy
from uuid import uuid1
import logging

DEBUG = False

def _myprint(text, debug=DEBUG):
    if debug:
        print(text)

def _val_to_str(val):
    if isinstance(val, list):
        val = [v.__name__ if callable(v) else str(v) for v in val]
    elif callable(val):
        val = val.__name__

    return str(val)

def fluff(rep, depth):
    rep += '\n'
    if depth > 0:
        rep += '|    '
        sp = ['     ']
        rep += ''.join(sp*(depth-1)) + '+--- '
    else:
        rep += '+--- '
    return rep

def str_traversal(tree, rep = '', depth = -1):
    val = _val_to_str(tree['val'])
    rep += f'{val}'
    if 'children' in tree and len(tree['children']) > 0:
        depth += 1
        rep = fluff(rep, depth)
        for idx, child in enumerate(tree['children']):
            rep = str_traversal(child, rep, depth)
            if idx+1 != len(tree['children']):
                rep = fluff(rep, depth)
        return rep
    return rep

def _print_trees(trees):
    if not isinstance(trees, list):
        trees = [trees]
    all_str = ''
    for idx, tree in enumerate(trees):
        all_str += str_traversal(tree)
        if idx+1 != len(trees):
            all_str += '\n'
    print(all_str)

def range_permutations(elts, start = 1):
    return [
        x for l in range(start, len(elts)+1) for x in itertools.permutations(elts, l)
    ]

def swaps(elts):
    return list(itertools.permutations(elts))

def leaf2tree(tree, leaf, plan, leaf_bool):
    """
    given a leaf, find it's parent
    if the leaf doesn't have a parent, return empty
    leaf_bool is a list because of the recursion. sets flag '1' if found and
    merged to its parent.
    """
    plan['val'] = tree['val']
    plan['children'] = []
    for idx, child in enumerate(tree['children']):
        if child['val'] == leaf['val'] and leaf not in plan['children']:
            plan['children'].append(leaf)
            leaf_bool.append(1)
        else:
            new_child = {}
            leaf2tree(child, leaf, new_child, leaf_bool)
            plan['children'].append(new_child)
            leaf_bool.append(0)

    return plan if sum(leaf_bool) else []

def umap_insert(umap, val):
    rep = _val_to_str(val)
    unique = f'{rep}_{len(umap.keys())}'
    umap[unique] = val
    return unique

def insert(maps, hint_val, tree_val):
    def vmap_insert(vmap, tree_val, hint_val, unique_hint_val):
        """
        tracks provenance of equals values that can be traced back to the
        original value.
        Helps find if a unique value is a result of a hint or an original tree
        value.
        """
        def _add_val(tree_val, hint_val, unique_hint_val):
            vmap[tree_val]['vals'].append(hint_val)
            vmap[tree_val]['unique_vals'].append(unique_hint_val)

        tree_val = _val_to_str(tree_val)
        # Option 1: this is an original value and has its own track list. Need
        # to check this first because an original value can also show up in
        # another's track list if an EQ hint was already applied to it.
        if tree_val in vmap:
            _add_val(tree_val, hint_val, unique_hint_val)
            return

        # Option 2: check if this value is hint generated and in the track list
        # of original values
        for t in vmap:
            if tree_val in vmap[t]['vals']:
                _add_val(t, hint_val, unique_hint_val)
                return

        # Option 3: this is a new val. init the track list of where values came
        # from.
        if tree_val not in vmap:
            vmap[tree_val] = {
                'vals': [],
                'unique_vals': []
            }
            _add_val(tree_val, hint_val, unique_hint_val)
            return

        logging.error('Planner->vmap_insert: should never happen')
        sys.exit(1)

    unique = umap_insert(maps['umap'], hint_val)
    vmap_insert(maps['vmap'], tree_val, hint_val, unique)
    return unique

def apply_eq_unique(candidates, eqhints, maps, leafs):
    """
    if an equals is found, check it hasn't been found yet and generate it's
    parent to reconstruct the tree.
    executes until no new hints plans are generated
    """
    # mapping A_0 -> A, A_1 -> A
    umap = maps['umap']
    # order: A = [A_0, A_1]
    valid = maps['valid']

    def find_parents(candidates, val, hint_val):
        """
        given a valid eqhint, search candidates where the original value shows up,
        copy and replace with the hint. this creates the relationship to rebuild a
        tree with the hint.
        """
        parents = []
        cand_seen = []
        for cand in candidates:
            swaps = cand['swaps']
            for idx, swap in enumerate(swaps):
                for tidx, tup in enumerate(swap):
                    if tup['val'] == val and cand not in cand_seen:
                        parent = deepcopy(cand)
                        parent['swaps'][idx][tidx]['val'] = hint_val
                        parents.append(parent)
                        cand_seen.append(cand)
        return parents

    def _test_eq_hint(tree_val, unique_val, hint):
        # Can't apply the same hint to a hint generated value
        # Can apply different hints to the same value or non hint generated value
        unique_hint_val = None
        if tree_val == hlc and (hint, unique_val) not in valid:
            hint_val = hrc
            unique_hint_val = insert(maps, hint_val, tree_val)
            # this hint was applied to this unique_value
            valid.append((hint, unique_val))
            # if its hint generated need to add it for the next iteration
            # paired with this hint
            valid.append((hint, unique_hint_val))
        elif tree_val == hrc and (hint, unique_val) not in valid:
            hint_val = hlc
            unique_hint_val = insert(maps, hint_val, tree_val)
            # this hint was applied to this unique_value
            valid.append((hint, unique_val))
            # if its hint generated need to add it for the next iteration
            # paired with this hint
            valid.append((hint, unique_hint_val))

        return unique_hint_val

    all_cands = candidates
    while True:
        done = True
        all_cands.extend(leafs)
        for hint, candidate in itertools.product(eqhints, all_cands):
            hlc, hrc = hint.get_args()
            unique_val = candidate['root']['val']
            # tree_val is original tree value
            tree_val = umap[unique_val]
            unique_hint_val = _test_eq_hint(tree_val, unique_val, hint)
            if unique_hint_val == None:
                continue

            _myprint(f'node: {tree_val} hint: {hint} {unique_val}->{unique_hint_val}')
            leaf = {
                'root': {
                    'val': unique_hint_val,
                    'children': []
                },
                'swaps': deepcopy(candidate['swaps'])
            }
            if leaf not in all_cands+leafs:
                leafs.append(leaf)
                all_cands.extend([
                    p for p in find_parents(all_cands, unique_val, unique_hint_val)
                    if p not in all_cands+leafs
                ])
                done = False
        # no more options
        if done:
            return leafs

def _gen_parent(cand, unique_hint_val):
    parent = deepcopy(cand)
    parent['root']['children'] = []
    parent['swaps'] = [
        ({'val': unique_hint_val, 'children': list(swap)},)
        for swap in parent['swaps']
    ]
    return parent

def _ss_follow_hint(all_cands, vmap, hint, unique_hint_val, valid):
    """
    UPDATE DS
    """

    hlc, hrc = hint.get_args()

    generated = []
    for cand in all_cands:
        if unique_hint_val in [tup['val'] for swap in cand['swaps'] for tup in swap]:
            # hint value already in the tuples, don't need to generate it again
            continue

        unique_val = cand['root']['val']
        # the supersetted value is an original value that has a list in the vmap
        if hrc in vmap and \
           unique_val in vmap[hrc]['unique_vals'] and \
           (hint, unique_val) not in valid and \
           unique_val not in generated:
            parent = _gen_parent(cand, unique_hint_val)
            if parent not in all_cands:
                all_cands.append(parent)
                _myprint(f'  found: {hrc}->{unique_hint_val} SS {unique_val}')
                valid.append((hint, unique_val))
                valid.append((hint, unique_hint_val))
            generated.append(unique_val)
        else:
            # the supersetted value (hrc) is generated from a hint so it's
            # in one of the lists in vmap
            for k in vmap:
                if hrc in vmap[k]['vals'] and \
                   unique_val in vmap[k]['unique_vals'] and \
                   (hint, unique_val) not in valid and \
                   unique_val not in generated:
                    parent = _gen_parent(cand, unique_hint_val)
                    if parent not in all_cands:
                        all_cands.append(parent)
                        _myprint(f'  found: {hrc}->{unique_hint_val} SS {unique_val}')
                        valid.append((hint, unique_val))
                        valid.append((hint, unique_hint_val))
                    generated.append(unique_val)

def apply_insert_unique(candidates, hints, maps, leafs):
    """
    find LC SS/PX RC where RC in plan.
    Inserts LC into plan and points original node to the new child.
    """

    # mapping A_0 -> A, A_1 -> A
    umap = maps['umap']
    valid = maps['valid']
    vmap = maps['vmap']
    # pprint(umap)
    # pprint(vmap)
    # _myprint(valid)

    all_cands = candidates
    while True:
        done = True
        all_cands.extend(leafs)
        for hint, candidate in itertools.product(hints, all_cands):
            hlc, hrc = hint.get_args()
            unique_val = candidate['root']['val']
            tree_val = umap[unique_val]
            if tree_val == hrc and tree_val != hlc and (hint, unique_val) not in valid:
                hint_val = hlc
                unique_hint_val = umap_insert(umap, hint_val)
                # this hint was applied to this unique_value
                valid.append((hint, unique_val))
                # if its hint generated need to add it for the next iteration
                # paired with this hint
                valid.append((hint, unique_hint_val))
                leaf = {
                    'root': {
                        'val': unique_hint_val,
                        'children': []
                    },
                    'swaps': deepcopy(candidate['swaps'])
                }
                if leaf not in all_cands:
                    _myprint(f'node: {tree_val} hint: {hint} {unique_val}->{unique_hint_val}')
                    leafs.append(leaf)
                    parent = _gen_parent(candidate, unique_hint_val)

                    if parent not in all_cands:
                        all_cands.append(parent)
                    _ss_follow_hint(all_cands, vmap, hint, unique_hint_val, valid)
                    done = False

        # no more options
        if done:
            return leafs

def collect(tree, hints, maps, plan, candidates):
    """
    recursively traverse and generate permuations at each branch point
    traverse and find equivalent operations defined by eqhints.
    traverse and insert superset hints
    """
    eqhints = hints.get('equals', [])
    inserts = hints.get('supersets', []) + hints.get('proxys', [])

    plan['val'] = tree['val']
    plan['children'] = []
    candidates.append({
        'root': plan,
        'swaps': swaps(tree['children'])
    })

    # apply hints until no change
    cur_len = len(candidates)
    while True:
        candidates.extend(apply_eq_unique(candidates, eqhints, maps, []))
        candidates.extend(apply_insert_unique(candidates, inserts, maps, []))
        new_len = len(candidates)
        if new_len == cur_len:
            break
        cur_len = new_len

    for child in tree['children']:
        new_child = {}
        collect(child, hints, maps, new_child, candidates)
        plan['children'].append(new_child)

    return candidates

def uniqueify(tree, maps, plan = {}):
    plan['val'] = insert(maps, tree['val'], tree['val'])
    plan['children'] = []
    for child in tree['children']:
        new_child = {}
        uniqueify(child, maps, new_child)
        plan['children'].append(new_child)

    return plan

def _deuniqueify(tree, plan, umap):
    plan['val'] = umap[tree['val']]
    plan['children'] = []
    for child in tree['children']:
        new_child = {}
        _deuniqueify(child, new_child, umap)
        plan['children'].append(new_child)

    return plan

def deuniqueify(trees: list, umap):
    """
    replace values back into trees
    """
    orig = []
    uniquestr = set()
    for tree in trees:
        unique_tree = _deuniqueify(tree, {}, umap)
        unique_str_tree = str_traversal(unique_tree)
        if unique_str_tree not in uniquestr:
            uniquestr.add(unique_str_tree)
            orig.append(unique_tree)
    return orig


# encoding: utf-8

import os, sys, json
sys.path.append('.')

import glob
import cv2
import numpy as np
import argparse
import shutil

from chi_lib.library import *
from collections import defaultdict
from frozendict import frozendict
from normalizing.normalize_text import normalize_text
from chi_lib.Multiprocessor import Multiprocessor
from chi_lib.ProgressBar import ProgressBar
from copy import deepcopy

def get_overlap_infos(target_ocr_textline, qa_textlines):
	if len(qa_textlines) == 0:
		return None, None
	cur_loc = get_topleft_bottomright(target_ocr_textline)
	locs = [get_topleft_bottomright(tl) for tl in qa_textlines]
	cur_area = get_rectangle_area(cur_loc)
	infos = []
	for i, loc in enumerate(locs):
		intersection_area = get_intersection_area(cur_loc, loc)
		overlapping = intersection_area / cur_area if cur_area > 1e-5 else 0
		temp_area = get_rectangle_area(loc)
		being_overlapped = intersection_area / temp_area if temp_area > 1e-5 else 0
		infos.append({
			'inner_overlap' : overlapping,
			'outer_overlap' : being_overlapped,
			'textline'		: qa_textlines[i]})
	return infos

def get_best_match_textline(target_ocr_textline, infos, corpus):
	for info in infos:
		info['common_bow'] = 0
	if not corpus is None:
		cur_bow = set(normalize_text(target_ocr_textline['text'])).intersection(corpus)
		for info in infos:
			qa_textline = info['textline']
			common_bow = set(normalize_text(qa_textline['text'])).intersection(cur_bow)
			info['common_bow'] = len(common_bow)

	best_match = max(infos, key=lambda x: (x['common_bow'], x['inner_overlap'], x['outer_overlap']))
	return best_match

def generate_labeled_ocr_sample(ocr_textlines, qa_textlines, required_classes=None, corpus=None):
	def is_match(info):
		label_info = info['textline']['label_info']
		if not label_info['key_type'] in ['value', 'key']:
			return False
		if required_classes != None:
			if not label_info['formal_key'] in required_classes:
				return False
		vals = [info['inner_overlap'], info['outer_overlap']]
		return min(vals) > 0.05 and max(vals) > 0.7

	if not corpus is None:
		corpus = set(corpus)

	qa_textlines = list(qa_textlines)
	ocr_textlines = deepcopy(ocr_textlines)
	for ocr_textline in ocr_textlines:
		infos = get_overlap_infos(ocr_textline, qa_textlines)
		infos = [info for info in infos if is_match(info)]
		if len(infos) > 0:
			best_match = get_best_match_textline(ocr_textline, infos, corpus)
			best_textline = best_match['textline']
			new_info = dict(best_textline['label_info'])
			new_info['text_gt'] = best_textline['text']
			new_info['inner_overlap'] = round(best_match['inner_overlap'], 4)
			new_info['outer_overlap'] = round(best_match['outer_overlap'], 4)
			ocr_textline['label_info'] = new_info
		else:
			ocr_textline['label_info'] = {'formal_key' : 'None', 'key_type' : 'other', 'inner_overlap' : 0, 'outer_overlap' : 0}
	return ocr_textlines

def label_ocr_samples(label_json_paths, ocr_json_paths, res_dir_path, required_classes, corpus):
	createDirectory(res_dir_path)
	qa_file_map = get_json_file_map_from_file_paths(label_json_paths)
	logTracker.log('Loaded ' + str(len(qa_file_map)) + ' QA samples')

	ocr_file_map = get_json_file_map_from_file_paths(ocr_json_paths)
	logTracker.log('Loaded ' + str(len(ocr_file_map)) + ' OCR samples')

	logTracker.log('Labeling for ' + str(len(ocr_file_map)) + ' OCR-Linecut samples')
	logTracker.log('Exporting to ' + res_dir_path)
	count = 0
	progress = ProgressBar(name='Labeling', maxValue=len(ocr_file_map))
	for f_name in ocr_file_map:
		progress.increase()
		if not f_name in qa_file_map:
			logTracker.log('Warning: Not found QA label file for "' + str(f_name) + '" with QA name "' + str(f_name) + '"')
			continue

		ocr_textlines = ocr_file_map[f_name]
		qa_textlines = qa_file_map[f_name]
		ocr_textlines = generate_labeled_ocr_sample(ocr_textlines, qa_textlines, required_classes, corpus)
		res_name = f_name.replace('.json', '_lc.json')
		with open(os.path.join(res_dir_path, res_name), 'w+', encoding='utf8') as f:
			f.write(json.dumps(ocr_textlines, ensure_ascii=False, indent=4))
			count += 1
	progress.done()
	logTracker.log('Total labeled files: ' + str(count) + ' / ' + str(len(ocr_file_map)))

if __name__ == '__main__':
	parser = argparse.ArgumentParser()
	parser.add_argument('--qa', help='Standard QA samples directory path', required=True)
	parser.add_argument('--ocr', help='Standard OCR-Linecut samples directory path', required=True)
	parser.add_argument('--classes', help='Classes file path', default=None)
	parser.add_argument('--corpus', help='Corpus file path', default=None)
	parser.add_argument('--selected', nargs='+', help='Selected file names .lst paths', default=[])
	args = parser.parse_args()

	label_dir_path = args.qa
	ocr_dir_path = args.ocr
	classes = None
	if args.classes:
		classes = load_json(args.classes)

	corpus = None
	if args.corpus:
		corpus = load_json(args.corpus)

	res_dir_path = os.path.join(getParentPath(ocr_dir_path), getBasename(ocr_dir_path) + '-labeled-' + getTodayDatetimeString())

	label_json_paths = loadValidFiles(label_dir_path, 'json', keepFilePath=True)
	ocr_json_paths = loadValidFiles(ocr_dir_path, 'json', keepFilePath=True)

	label_json_paths = filter_file_paths_from_path_lists(label_json_paths, args.selected)
	ocr_json_paths = filter_file_paths_from_path_lists(ocr_json_paths, args.selected)
	label_ocr_samples(label_json_paths, ocr_json_paths, res_dir_path, classes, corpus)


# coding: utf-8
from PyQt5.QtCore import Qt, QUrl, QSize
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication

from qmaterialwidgets import (NavigationItemPosition, MaterialWindow, SplashScreen, Theme,
                              setTheme, isDarkTheme, OutlinedToolButton, ToolTipFilter, toggleTheme)
from qmaterialwidgets import FluentIcon as FIF

from .gallery_interface import GalleryInterface
from .home_interface import HomeInterface
from .basic_input_interface import BasicInputInterface
from .date_time_interface import DateTimeInterface
from .dialog_interface import DialogInterface
from .text_interface import TextInterface
from .status_info_interface import StatusInfoInterface
from .navigation_view_interface import NavigationViewInterface
from .view_interface import ViewInterface
from ..common.icon import Icon
from ..common.signal_bus import signalBus
from ..common.translator import Translator
from ..common import resource

class MainWindow(MaterialWindow):

    def __init__(self):
        super().__init__()
        self.initWindow()

        # create sub interface
        self.homeInterface = HomeInterface(self)
        self.basicInputInterface = BasicInputInterface(self)
        self.dateTimeInterface = DateTimeInterface(self)
        self.dialogInterface = DialogInterface(self)
        self.navigationViewInterface = NavigationViewInterface(self)
        self.statusInfoInterface = StatusInfoInterface(self)
        self.textInterface = TextInterface(self)
        self.viewInterface = ViewInterface(self)

        # initialize layout
        self.connectSignalToSlot()

        # add items to navigation interface
        self.initNavigation()
        self.splashScreen.finish()

    def connectSignalToSlot(self):
        signalBus.switchToSampleCard.connect(self.switchToSample)
        signalBus.toggleThemeSignal.connect(self.toggleTheme)

    def initNavigation(self):
        # add navigation items
        t = Translator()
        self.addSubInterface(self.homeInterface, FIF.HOME, self.tr('Home'))

        pos = NavigationItemPosition.SCROLL
        self.addSubInterface(self.basicInputInterface, FIF.CHECKBOX, t.basicInput, position=pos)
        self.addSubInterface(self.dateTimeInterface, FIF.DATE_TIME, t.dateTime, position=pos)
        self.addSubInterface(self.dialogInterface, FIF.MESSAGE, t.dialogs, position=pos)
        self.addSubInterface(self.navigationViewInterface, FIF.MENU, t.navigation, position=pos)
        self.addSubInterface(self.statusInfoInterface, FIF.CHAT, t.statusInfo, position=pos)
        self.addSubInterface(self.textInterface, Icon.TEXT, t.text, position=pos)
        self.addSubInterface(self.viewInterface, Icon.GRID, t.view, position=pos)

        self.themeButton = OutlinedToolButton(self)
        self.themeButton.setFixedSize(50, 50)
        self.themeButton.setIconSize(QSize(20, 20))
        self.themeButton.installEventFilter(ToolTipFilter(self.themeButton, 500))
        self.themeButton.setToolTip(self.tr('Toggle theme'))
        self.navigationInterface.addWidget(
            'themeButton', self.themeButton, self.toggleTheme, NavigationItemPosition.BOTTOM)

        self._updateThemeButtonIcon()

    def initWindow(self):
        self.resize(990, 780)
        self.setMinimumWidth(760)
        self.setWindowIcon(QIcon(':/qmaterialwidgets/images/logo.png'))
        self.setWindowTitle('PySide-Material-Widgets')

        # create splash screen
        self.splashScreen = SplashScreen(self.windowIcon(), self)
        self.splashScreen.setIconSize(QSize(120, 120))
        self.splashScreen.raise_()

        desktop = QApplication.screens()[0].availableGeometry()
        w, h = desktop.width(), desktop.height()
        self.move(w//2 - self.width()//2, h//2 - self.height()//2)
        self.show()
        QApplication.processEvents()

    def toggleTheme(self):
        toggleTheme(save=True)
        self._updateThemeButtonIcon()

    def _updateThemeButtonIcon(self):
        if not isDarkTheme():
            self.themeButton.setIcon(FIF.QUIET_HOURS)
        else:
            self.themeButton.setIcon(FIF.BRIGHTNESS)

    def switchToSample(self, routeKey, index):
        """ switch to sample """
        interfaces = self.findChildren(GalleryInterface)
        for w in interfaces:
            if w.objectName() == routeKey:
                self.stackedWidget.setCurrentWidget(w, False)
                w.scrollToCard(index)


import webbrowser

import PySimpleGUI as sg
import numpy as np

from original_game import LogicUtils, OriginalGame
from implemented_Game import ImplementationUtils, ImplementedGame
from implemented_NeuralNet import NNetWrapper
from MCTS import MCTS
from exceptions import GameException
from main import args

MODEL_FOLDER = 'model ver22'
MODEL_FILENAME = 'best.pth.tar'

X_CHAR = '\u274C'
O_CHAR = '\u26AB'
X_COLOR = 'red'
O_COLOR = 'blue'
BACKGROUND_COLOR = 'white'
X_WON_BACKGROUND_COLOR = 'lightpink'
O_WON_BACKGROUND_COLOR = 'skyblue'
DRAW_BACKGROUND_COLOR = 'darkgrey'
WIDTH = 3
FONT_SIZE = 22
NEXT_AREA_COLOR = 'lightgreen'
X_TURN_STR = 'Player 1 turn as X'
O_TURN_STR = 'Player 2 turn as O'
DEFAULT_TEXT_EXCEPTION = 'None'
PAD = 11

PROJECT_NAME = 'An AlphaZero Implementation of Ultimate Tic-Tac-Toe'
PROJECT_TEXT = \
"""Hoang Tran Nhat Minh\t\t\tData Science and Artificial Intelligence - K65
Project 1 of Semester 2022.1\t\tHanoi University of Science and Technology
Instructed by Dr. Tran Nguyen Ngoc\t\tSchool of Information and Communication Technology"""

class GraphicInterface():
    def __init__(self, mode='Human vs AI',
                 start_window=True, start_event_loop=True,
                 full_gui=True, theme='DarkGrey6', debug_printing=False):

        sg.theme(theme)
        self.mode = mode
        # if set full_gui to False, shows a simpler GUI for report
        self.full_gui = full_gui
        self.debug_printing = debug_printing

        self.original_game = OriginalGame()
        self.layout = self.create_layout()

        if start_window:
            self.window = self.create_window('Ultimate Tic-Tac-Toe')

        if start_event_loop:
            self.event_loop()

    def create_layout(self):
        layout = [[[[None for j in range(3)] for i in range(3)] for y in range(3)] for x in range(3)]

        for x in range(3):
            for y in range(3):
                for i in range(3):
                    for j in range(3):
                        layout[x][y][i][j] = sg.Button(
                            '',
                            size=WIDTH,
                            font=f'None {FONT_SIZE}',
                            key=f'cell{x}{y}{i}{j}',
                            button_color=(None, BACKGROUND_COLOR)
                        )
                layout[x][y] = sg.Frame('', layout[x][y], pad=PAD, border_width=0)

        layout.append([
            sg.Frame('Turn', [[
                sg.Text(X_TURN_STR, key='textPlayerTurn', visible=self.full_gui, size=25)]], visible=self.full_gui),
            sg.Frame('Information', [[
                sg.Text(DEFAULT_TEXT_EXCEPTION, key='textException', visible=self.full_gui, size=53)]], visible=self.full_gui)
        ])

        layout.insert(0,
            [
                sg.Frame('', [[
                sg.Button('Human vs Human', visible=self.full_gui, focus=self.mode=='Human vs Human'),
                sg.Button('Human vs AI', visible=self.full_gui, focus=self.mode=='Human vs AI'),
                sg.Button('AI vs Human', visible=self.full_gui, focus=self.mode=='AI vs Human'),
                sg.Button('AI vs AI', visible=self.full_gui, focus=self.mode=='AI vs AI'),
                sg.Button('Open GitHub repository...', visible=self.full_gui),
                sg.Button('Exit', visible=self.full_gui),
            ]], border_width=0, element_justification='center', size=(670, 30)
        , visible=self.full_gui)])
        # a temporary fix for a bug where the first button of a layout is highlighted
        # no matter what button the user clicks
        layout.insert(0, [sg.Frame('', [[sg.Button()]], visible=False)])

        layout.insert(0, [
            sg.Frame(
                PROJECT_NAME, [[sg.Text(PROJECT_TEXT, visible=self.full_gui, size=(82, 3))]],
                title_location='n', visible=self.full_gui)
        ])

        return layout

    def create_window(self, window_name):
        return sg.Window(window_name, self.layout, finalize=True)

    def update_button_color(self, xyij, text_color, bkg_color):
        x, y, i, j = xyij
        self.window[f'cell{x}{y}{i}{j}'].update(
            button_color=(text_color, bkg_color))

    def update_button_text(self, xyij, text):
        x, y, i, j = xyij
        self.window[f'cell{x}{y}{i}{j}'].update(text)

    def update_area_color(self, xy, text_color, bkg_color):
        x, y = xy
        for i in range(3):
            for j in range(3):
                self.update_button_color(
                    (x, y, i, j),
                    text_color,
                    bkg_color
                )

    def update_all_areas_colors(self):
        for x in range(3):
            for y in range(3):
                state_of_area = self.original_game.area[x, y]
                if state_of_area == 0:
                    self.update_area_color((x, y), None, BACKGROUND_COLOR)
                elif np.isnan(state_of_area):
                    self.update_area_color((x, y), None, DRAW_BACKGROUND_COLOR)
                else:
                    bkg_color_won = X_WON_BACKGROUND_COLOR if state_of_area == 1 else O_WON_BACKGROUND_COLOR
                    self.update_area_color((x, y), None, bkg_color_won)

        # change next area color
        if self.original_game.curr_area is not None:
            self.update_area_color(self.original_game.curr_area, None, NEXT_AREA_COLOR)
        else:
            for x in range(3):
                for y in range(3):
                    state_of_area = self.original_game.area[x, y]
                    if state_of_area == 0:
                        self.update_area_color((x, y), None, NEXT_AREA_COLOR)

    def play(self, xyij, update_window_elements=True):
        x, y, i, j = xyij
        self.original_game.execute_move((x, y, i, j))

        if self.debug_printing:
            cell_state = self.original_game.cell_state
            print(ImplementationUtils().cell_state_4d_to_2d(cell_state).astype('int'))
            print(ImplementationUtils().cell_state_4d_to_2d(
                self.original_game._get_valid_moves(cell_state, self.original_game.curr_player, self.original_game.curr_area)).astype('int')
            )
            print()

        if update_window_elements:
            # update played cell
            self.update_button_text(
                xyij, X_CHAR if self.original_game.curr_player==-1 else O_CHAR)
            self.update_button_color(
                xyij, X_COLOR if self.original_game.curr_player==-1 else O_COLOR, None)
            self.update_all_areas_colors()
            # update texts
            self.window['textPlayerTurn'].update(X_TURN_STR if self.original_game.curr_player==1 else O_TURN_STR)
            self.window['textException'].update(DEFAULT_TEXT_EXCEPTION)

    def event_loop(self):
        # for AI only
        game = ImplementedGame()
        net = NNetWrapper(game)
        net.load_checkpoint(MODEL_FOLDER, MODEL_FILENAME)
        mtcs = MCTS(game=game, nnet=net, args=args)
        human_valid_move = True

        while True:
            event, values = self.window.read()

            if event == sg.WIN_CLOSED or event == 'Exit':
                self.window.close()
                break

            elif event == 'Open GitHub repository...':
                webbrowser.open(r'https://github.com/htnminh/AlphaZero-Ultimate-TicTacToe')

            elif event == 'Human vs Human':
                self.window.close()
                self.__init__(mode='Human vs Human')

            elif event == 'Human vs AI':
                self.window.close()
                self.__init__(mode='Human vs AI')

            elif event == 'AI vs Human':
                self.window.close()
                self.__init__(mode='AI vs Human', start_event_loop=False)

                state = ImplementationUtils().cell_state_4d_to_2d(self.original_game.cell_state)
                if game.getGameEnded(state, 1, self.original_game.curr_area) == 0:
                    action = np.argmax(mtcs.getActionProb(
                        *game.getCanonicalForm(state, 1, self.original_game.curr_area),
                        temp=0
                    ))
                    self.play(LogicUtils().k_to_xyij(action))

                self.event_loop()

            elif event == 'AI vs AI':
                self.window.close()
                self.__init__(mode='AI vs AI', start_window=True, start_event_loop=False)

                while True:
                    state = ImplementationUtils().cell_state_4d_to_2d(self.original_game.cell_state)
                    if game.getGameEnded(state, 1, self.original_game.curr_area) == 0:
                        action = np.argmax(mtcs.getActionProb(
                            *game.getCanonicalForm(state, 1, self.original_game.curr_area),
                            temp=0
                        ))
                        self.play(LogicUtils().k_to_xyij(action), update_window_elements=True)
                        self.window.refresh()
                    else:
                        try:
                            self.play((0,0,0,0))
                        except GameException as e:
                            self.window['textException'].update(e)
                        break

                    state = ImplementationUtils().cell_state_4d_to_2d(self.original_game.cell_state)
                    if game.getGameEnded(state, -1, self.original_game.curr_area) == 0:
                        action = np.argmax(mtcs.getActionProb(
                            *game.getCanonicalForm(state, -1, self.original_game.curr_area),
                            temp=0
                        ))
                        self.play(LogicUtils().k_to_xyij(action), update_window_elements=True)
                        self.window.refresh()
                    else:
                        try:
                            self.play((0,0,0,0))
                        except GameException as e:
                            self.window['textException'].update(e)
                        break

                self.event_loop()

            if self.mode == 'Human vs Human':
                if event.startswith('cell'):
                    xyij = tuple(map(int, event[-4:]))
                    try:
                        self.play(xyij)
                    except GameException as e:
                        self.window['textException'].update(e)

            elif self.mode == 'Human vs AI':

                if event.startswith('cell'):
                    xyij = tuple(map(int, event[-4:]))
                    try:
                        self.play(xyij)
                        human_valid_move = True
                    except GameException as e:
                        self.window['textException'].update(e)
                        human_valid_move = False

                    if human_valid_move:
                        state = ImplementationUtils().cell_state_4d_to_2d(self.original_game.cell_state)
                        if game.getGameEnded(state, -1, self.original_game.curr_area) == 0:
                            action = np.argmax(mtcs.getActionProb(
                                *game.getCanonicalForm(state, -1, self.original_game.curr_area),
                                temp=0
                            ))
                            self.play(LogicUtils().k_to_xyij(action))
                        else:
                            try:
                                self.play((0,0,0,0))
                            except GameException as e:
                                self.window['textException'].update(e)

            elif self.mode == 'AI vs Human':

                if event.startswith('cell'):
                    xyij = tuple(map(int, event[-4:]))
                    try:
                        self.play(xyij)
                        human_valid_move = True
                    except GameException as e:
                        self.window['textException'].update(e)
                        human_valid_move = False

                    if human_valid_move:
                        state = ImplementationUtils().cell_state_4d_to_2d(self.original_game.cell_state)
                        if game.getGameEnded(state, 1, self.original_game.curr_area) == 0:
                            action = np.argmax(mtcs.getActionProb(
                                *game.getCanonicalForm(state, 1, self.original_game.curr_area),
                                temp=0
                            ))
                            self.play(LogicUtils().k_to_xyij(action))

            elif self.mode == 'AI vs AI':

                if event.startswith('cell'):
                    xyij = tuple(map(int, event[-4:]))
                    try:
                        self.play(xyij)
                        human_valid_move = True
                    except GameException as e:
                        self.window['textException'].update(e)

if __name__ == '__main__':
    GraphicInterface()