# Copyright (C) 2021 Victor Soupday
# This file is part of CC3_Blender_Tools <https://github.com/soupday/cc3_blender_tools>
#
# CC3_Blender_Tools 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.
#
# CC3_Blender_Tools 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 CC3_Blender_Tools.  If not, see <https://www.gnu.org/licenses/>.

import bpy
from . import nodeutils
from . import utils
from . import vars

# when updating linked materials, attempt to update the properties in all the material types in the same list:
LINKED_MATERIALS = [
    ["SKIN_HEAD", "SKIN_BODY", "SKIN_ARM", "SKIN_LEG"],
    ["EYE_RIGHT", "CORNEA_RIGHT", "OCCLUSION_RIGHT", "TEARLINE_RIGHT",
     "EYE_LEFT", "CORNEA_LEFT", "OCCLUSION_LEFT", "TEARLINE_LEFT"],
    ["TEETH_UPPER", "TEETH_LOWER"],
    ["HAIR", "SMART_HAIR", "SCALP", "EYELASH"]
]

# These material types must be updated together as they share the same properties:
PAIRED_MATERIALS = [
    ["EYE_RIGHT", "CORNEA_RIGHT"],
    ["EYE_LEFT", "CORNEA_LEFT"],
]

# These properties must be updated as linked as they are duplicated across the linked materials and must be kept in sync:
FORCE_LINKED_PROPS = ["skin_blend", "skin_normal_blend", "skin_mouth_ao", "skin_nostril_ao", "skin_lips_ao",
                      "skin_head_micronormal", "skin_body_micronormal", "skin_arm_micronormal", "skin_leg_micronormal",
                      "skin_head_tiling", "skin_body_tiling", "skin_arm_tiling", "skin_leg_tiling",]

# TODO will need to work something out to separate left from right eye materials...
FORCE_LINKED_TYPES = []
#["EYE", "CORNEA", "OCCLUSION", "TEARLINE"]

PROP_DEPENDANTS = {
    "eye_shadow_radius": ["eye_shadow_hardness"],
    "eye_iris_radius": ["eye_iris_hardness"],
}

PROP_MATRIX = [
    # Base Color groups
    {   "start": "(color_",
        "end": "_mixer)",
        "groups": [

            {   "name": "_skin_",
                "inputs": [
                    ["AO Strength", "skin_ao"],
                    ["Blend Strength", "skin_blend"],
                    ["Mouth AO", "skin_mouth_ao"],
                    ["Nostril AO", "skin_nostril_ao"],
                    ["Lips AO", "skin_lips_ao"],
                ],
            },

            {   "name": "_eye_",
                "inputs": [
                    ["AO Strength", "eye_ao"],
                    ["Blend Strength", "eye_blend"],
                    ["Shadow Radius", "eye_shadow_radius"],
                    ["Shadow Hardness", "eye_shadow_hardness", "parameters.eye_shadow_hardness * parameters.eye_shadow_radius * 0.99"],
                    ["Corner Shadow", "eye_shadow_color"],
                    ["Sclera Brightness", "eye_sclera_brightness"],
                    ["Iris Brightness", "eye_iris_brightness"],
                    ["Sclera Hue", "eye_sclera_hue"],
                    ["Iris Hue", "eye_iris_hue"],
                    ["Sclera Saturation", "eye_sclera_saturation"],
                    ["Iris Saturation", "eye_iris_saturation"],
                    ["Limbus Color", "eye_limbus_color"],
                ],
            },

            {   "name": "_hair_",
                "inputs": [
                    ["AO Strength", "hair_ao"],
                    ["Blend Strength", "hair_blend"],
                    ["Diffuse Bright", "hair_brightness"],
                    ["Diffuse Contrast", "hair_contrast"],
                    ["Diffuse Hue", "hair_hue"],
                    ["Diffuse Saturation", "hair_saturation"],
                    ["Aniso Color", "hair_aniso_color"],
                    ["Aniso Strength", "hair_aniso_strength"],
                    ["Aniso Strength Cycles", "hair_aniso_strength_cycles"],
                    ["Base Color Strength", "hair_vertex_color_strength"],
                    ["Base Color Strength", "hair_vertex_color_strength"],
                    ["Base Color Map Strength", "hair_base_color_map_strength"],
                    ["Depth Blend Strength", "hair_depth_strength"],
                    ["Diffuse Strength", "hair_diffuse_strength"],
                    ["Global Strength", "hair_global_strength"],
                    ["Root Color", "hair_root_color", "gamma_correct(parameters.hair_root_color)"],
                    ["End Color", "hair_end_color", "gamma_correct(parameters.hair_end_color)"],
                    ["Root Color Strength", "hair_root_strength"],
                    ["End Color Strength", "hair_end_strength"],
                    ["Invert Root and End Color", "hair_invert_strand"],
                    ["Highlight A Start", "hair_a_start"],
                    ["Highlight A Mid", "hair_a_mid"],
                    ["Highlight A End", "hair_a_end"],
                    ["Highlight A Strength", "hair_a_strength"],
                    ["Highlight A Overlap End", "hair_a_overlap"],
                    ["Highlight Color A", "hair_a_color", "gamma_correct(parameters.hair_a_color)"],
                    ["Highlight B Start", "hair_b_start"],
                    ["Highlight B Mid", "hair_b_mid"],
                    ["Highlight B End", "hair_b_end"],
                    ["Highlight B Strength", "hair_b_strength"],
                    ["Highlight B Overlap End", "hair_b_overlap"],
                    ["Highlight Color B", "hair_b_color", "gamma_correct(parameters.hair_b_color)"],
                ],
            },

            {   "name": "_eyelash_",
                "inputs": [
                    ["AO Strength", "hair_ao"],
                ],
            },

            {   "name": "_teeth_",
                "inputs": [
                    ["AO Strength", "teeth_ao"],
                    ["Front", "teeth_front"],
                    ["Rear", "teeth_rear"],
                    ["Teeth Brightness", "teeth_teeth_brightness"],
                    ["Teeth Saturation", "teeth_teeth_desaturation", "1 - parameters.teeth_teeth_desaturation"],
                    ["Gums Brightness", "teeth_gums_brightness"],
                    ["Gums Saturation", "teeth_gums_desaturation", "1 - parameters.teeth_gums_desaturation"],
                ],
            },

            {   "name": "_tongue_",
                "inputs": [
                    ["AO Strength", "tongue_ao"],
                    ["Front", "tongue_front"],
                    ["Rear", "tongue_rear"],
                    ["Brightness", "tongue_brightness"],
                    ["Saturation", "tongue_desaturation", "1 - parameters.tongue_desaturation"],
                ],
            },

            {   "name": "_nails_",
                "inputs": [
                    ["AO Strength", "nails_ao"],
                ],
            },

            {   "name": "_default_",
                "inputs": [
                    ["AO Strength", "default_ao"],
                    ["Blend Strength", "default_blend"],
                ],
            },
        ],
    },

    # Subsurface groups
    {   "start": "(subsurface_",
        "end": "_mixer)",
        "groups": [

            {   "name": "_skin_",
                "inputs": [
                    ["Radius", "skin_sss_radius", "parameters.skin_sss_radius * vars.UNIT_SCALE"],
                    ["Falloff", "skin_sss_falloff"],
                ],
            },

            {   "name": "_eye_",
                "inputs": [
                    ["Radius1", "eye_sss_radius", "parameters.eye_sss_radius * vars.UNIT_SCALE"],
                    ["Radius2", "eye_sss_radius", "parameters.eye_sss_radius * vars.UNIT_SCALE"],
                    ["Falloff1", "eye_sss_falloff"],
                    ["Falloff2", "eye_sss_falloff"],
                ],
            },

            {   "name": "_hair_",
                "inputs": [
                    ["Radius", "hair_sss_radius", "parameters.hair_sss_radius * vars.UNIT_SCALE"],
                    ["Falloff", "hair_sss_falloff"],
                ],
            },

            {   "name": "_teeth_",
                "inputs": [
                    ["Radius1", "teeth_sss_radius", "parameters.teeth_sss_radius * vars.UNIT_SCALE"],
                    ["Radius2", "teeth_sss_radius", "parameters.teeth_sss_radius * vars.UNIT_SCALE"],
                    ["Falloff1", "teeth_sss_falloff"],
                    ["Falloff2", "teeth_sss_falloff"],
                    ["Scatter1", "teeth_gums_sss_scatter"],
                    ["Scatter2", "teeth_teeth_sss_scatter"],
                ],
            },

            {   "name": "_tongue_",
                "inputs": [
                    ["Scatter", "tongue_sss_scatter"],
                    ["Radius", "tongue_sss_radius", "parameters.tongue_sss_radius * vars.UNIT_SCALE"],
                    ["Falloff", "tongue_sss_falloff"],
                ],
            },

            {   "name": "_nails_",
                "inputs": [
                    ["Radius", "nails_sss_radius", "parameters.nails_sss_radius * vars.UNIT_SCALE"],
                    ["Falloff", "nails_sss_falloff"],
                ],
            },

            {   "name": "_default_",
                "inputs": [
                    ["Radius", "default_sss_radius", "parameters.default_sss_radius * vars.UNIT_SCALE"],
                    ["Falloff", "default_sss_falloff"],
                ],
            },
        ],
    },

    # MSR groups
    {   "start": "(msr_",
        "end": "_mixer)",
        "groups": [

            {   "name": ["_skin_head_", "_skin_body_", "_skin_arm_", "_skin_leg_"],
                "inputs": [
                    ["Roughness Power", "skin_roughness_power"],
                    ["Roughness Remap", "skin_roughness"],
                    ["Specular", "skin_specular"],
                ],
            },

            {   "name": "_eye_",
                "inputs": [
                    ["Specular1", "eye_specular"],
                    ["Roughness1", "eye_sclera_roughness"],
                ],
            },

            {   "name": "_cornea_",
                "inputs": [
                    ["Specular1", "eye_specular"],
                    ["Specular2", "eye_specular"],
                    ["Roughness1", "eye_sclera_roughness"],
                    ["Roughness2", "eye_iris_roughness"],
                ],
            },

            {   "name": "_hair_",
                "inputs": [
                    ["Roughness Remap", "hair_roughness"],
                    ["Specular", "hair_specular"],
                ],
            },

            {   "name": "_scalp_",
                "inputs": [
                    ["Roughness Remap", "hair_scalp_roughness"],
                    ["Specular", "hair_scalp_specular"],
                ],
            },

            {   "name": "_eyelash_",
                "inputs": [
                    ["Roughness Remap", "hair_eyelash_roughness"],
                    ["Specular", "hair_eyelash_specular"],
                ],
            },

            {   "name": "_teeth_",
                "inputs": [
                    ["Specular2", "teeth_specular"],
                ],
            },

            {   "name": "_tongue_",
                "inputs": [
                    ["Specular2", "tongue_specular"],
                    ["Roughness2", "tongue_roughness"],
                ],
            },

            {   "name": "_nails_",
                "inputs": [
                    ["Roughness Remap", "nails_roughness"],
                    ["Specular", "nails_specular"],
                ],
            },

            {   "name": "_default_",
                "inputs": [
                    ["Roughness Remap", "default_roughness"],
                ],
            },
        ],
    },

    # Normal groups
    {   "start": "(normal_",
        "end": "_mixer)",
        "groups": [

            {   "name": "_skin_head_",
                "inputs": [
                    ["Normal Blend Strength", "skin_normal_blend"],
                    ["Micro Normal Strength", "skin_head_micronormal"],
                ],
            },

            {   "name": "_skin_body_",
                "inputs": [
                    ["Normal Blend Strength", "skin_normal_blend"],
                    ["Micro Normal Strength", "skin_body_micronormal"],
                ],
            },

            {   "name": "_skin_arm_",
                "inputs": [
                    ["Normal Blend Strength", "skin_normal_blend"],
                    ["Micro Normal Strength", "skin_arm_micronormal"],
                ],
            },

            {   "name": "_skin_leg_",
                "inputs": [
                    ["Normal Blend Strength", "skin_normal_blend"],
                    ["Micro Normal Strength", "skin_leg_micronormal"],
                ],
            },

            {   "name": "_eye_",
                "inputs": [
                    ["Micro Normal Strength", "eye_sclera_normal", "1 - parameters.eye_sclera_normal"],
                    ["Sclera Normal Strength", "eye_sclera_normal", "1 - parameters.eye_sclera_normal"],
                    ["Blood Vessel Height", "eye_blood_vessel_height", "parameters.eye_blood_vessel_height/1000"],
                    ["Iris Bump Height", "eye_iris_bump_height", "parameters.eye_iris_bump_height/1000"],
                ],
            },

            {   "name": ["_hair_", "_scalp_", "_eyelash_"],
                "inputs": [
                    ["Bump Map Height", "hair_bump", "parameters.hair_bump / 1000"],
                    ["Bump Map Midpoint", "hair_fake_bump_midpoint"],
                ],
            },

            {   "name": "_teeth_",
                "inputs": [
                    ["Micro Normal Strength", "teeth_micronormal"],
                ],
            },

            {   "name": "_tongue_",
                "inputs": [
                    ["Micro Normal Strength", "tongue_micronormal"],
                ],
            },

            {   "name": "_nails_",
                "inputs": [
                    ["Micro Normal Strength", "nails_micronormal"],
                ],
            },

            {   "name": "_default_",
                "inputs": [
                    ["Normal Blend Strength", "default_normal_blend"],
                    ["Micro Normal Strength", "default_micronormal"],
                    ["Bump Map Height", "default_bump", "parameters.default_bump / 1000"],
                ],
            },
        ],
    },

    # Tiling groups
    {   "start": "(tiling_",
        "end": "_mapping)",
        "groups": [

            {   "name": "_skin_head_",
                "inputs": [
                    ["Tiling", "skin_head_tiling"],
                ],
            },

            {   "name": "_skin_body_",
                "inputs": [
                    ["Tiling", "skin_body_tiling"],
                ],
            },

            {   "name": "_skin_arm_",
                "inputs": [
                    ["Tiling", "skin_arm_tiling"],
                ],
            },

            {   "name": "_skin_leg_",
                "inputs": [
                    ["Tiling", "skin_leg_tiling"],
                ],
            },

            # TODO: this could be part of the iris mask group and linked...
            {   "name": "_normal_sclera_",
                "inputs": [
                    ["Tiling", "eye_sclera_tiling"],
                ],
            },

            # TODO: this could be part of the iris mask group and linked...
            {   "name": "_color_sclera_",
                "inputs": [
                    ["Tiling", "eye_sclera_scale", "1.0 / parameters.eye_sclera_scale"],
                ],
            },

            {   "name": "_teeth_",
                "inputs": [
                    ["Tiling", "teeth_tiling"],
                ],
            },

            {   "name": "_tongue_",
                "inputs": [
                    ["Tiling", "tongue_tiling"],
                ],
            },

            {   "name": "_nails_",
                "inputs": [
                    ["Tiling", "nails_tiling"],
                ],
            },

            {   "name": "_default_",
                "inputs": [
                    ["Tiling", "default_tiling"],
                ],
            },
        ],
    },

    # Mask groups
    {   "start": "(",
        "end": "_mask)",
        "groups": [

            {   "name": "(iris_mask)",
                "inputs": [
                    ["Scale", "eye_iris_scale", "1.0 / parameters.eye_iris_scale"],
                    ["Radius", "eye_iris_radius"],
                    ["Hardness", "eye_iris_hardness", "parameters.eye_iris_radius * parameters.eye_iris_hardness * 0.99"],
                    ["Limbus Radius", "eye_limbus_radius"],
                    ["Limbus Hardness", "eye_limbus_hardness"],
                ],
            },

            {   "name": "(eye_occlusion_mask)",
                "inputs": [
                    ["Strength", "eye_occlusion"],
                    ["Hardness", "eye_occlusion_hardness"],
                ],
            },

            {   "name": "(eye_occlusion_adv_mask)",
                "inputs": [
                    ["Strength", "eye_occlusion_strength"],
                    ["Strength2", "eye_occlusion_2nd_strength"],
                    ["Power", "eye_occlusion_power"],
                    ["Top Min", "eye_occlusion_top_min"],
                    ["Top Max", "eye_occlusion_top_max"],
                    ["Top Curve", "eye_occlusion_top_curve"],
                    ["Bottom Min", "eye_occlusion_bottom_min"],
                    ["Bottom Max", "eye_occlusion_bottom_max"],
                    ["Bottom Curve", "eye_occlusion_bottom_curve"],
                    ["Inner Min", "eye_occlusion_inner_min"],
                    ["Inner Max", "eye_occlusion_inner_max"],
                    ["Outer Min", "eye_occlusion_outer_min"],
                    ["Outer Max", "eye_occlusion_outer_max"],
                    ["Top2 Min", "eye_occlusion_2nd_top_min"],
                    ["Top2 Max", "eye_occlusion_2nd_top_max"],
                    ["Tear Duct Position", "eye_occlusion_tear_duct_position"],
                    ["Tear Duct Width", "eye_occlusion_tear_duct_width"],
                ],
            },
        ],
    },

    # Shaders
    {   "start": "(rl_",
        "end": "_shader)",
        "groups": [

            {   "name": "eye_occlusion_shader",
                "inputs": [
                    ["Color", "eye_occlusion_color"],
                    ["Strength", "eye_occlusion_strength"],
                    ["Strength2", "eye_occlusion_2nd_strength"],
                    ["Power", "eye_occlusion_power"],
                    ["Top Min", "eye_occlusion_top_min"],
                    ["Top Max", "eye_occlusion_top_max"],
                    ["Top Curve", "eye_occlusion_top_curve"],
                    ["Bottom Min", "eye_occlusion_bottom_min"],
                    ["Bottom Max", "eye_occlusion_bottom_max"],
                    ["Bottom Curve", "eye_occlusion_bottom_curve"],
                    ["Inner Min", "eye_occlusion_inner_min"],
                    ["Inner Max", "eye_occlusion_inner_max"],
                    ["Outer Min", "eye_occlusion_outer_min"],
                    ["Outer Max", "eye_occlusion_outer_max"],
                    ["Top2 Min", "eye_occlusion_2nd_top_min"],
                    ["Top2 Max", "eye_occlusion_2nd_top_max"],
                    ["Tear Duct Position", "eye_occlusion_tear_duct_position"],
                    ["Tear Duct Width", "eye_occlusion_tear_duct_width"],
                ],
            },
        ],
    },

    # Individual nodes
    {   "start": "",
        "end": "",
        "groups": [

            {   "name": "teeth_roughness",
                "inputs": [
                    [1, "teeth_roughness"],
                ],
            },

            {   "name": "eye_tearline_shader",
                "inputs": [
                    ["Alpha", "tearline_alpha"],
                    ["Roughness", "tearline_roughness"],
                ],
            },

            {   "name": "eye_occlusion_shader",
                "inputs": [
                    ["Base Color", "eye_occlusion_color"],
                ],
            },

            {   "name": "eye_occlusion_adv_shader",
                "inputs": [
                    ["Base Color", "eye_occlusion_color"],
                ],
            },

            {   "name": "hair_opacity",
                "inputs": [
                    [1, "hair_opacity"],
                ],
            },

            {   "name": "scalp_opacity",
                "inputs": [
                    [1, "hair_scalp_opacity"],
                ],
            },

            {   "name": "eyelash_opacity",
                "inputs": [
                    [1, "hair_eyelash_opacity"],
                ],
            },

            {   "name": "default_opacity",
                "inputs": [
                    [1, "default_opacity"],
                ],
            },

            # dummy entry so that 'eye_iris_depth_radius' wil update linked materials...
            {   "name": "_CC3IID_DO_NOT_MATCH_",
                "inputs": [
                    [0, "eye_iris_depth_radius"],
                ],
            },
        ],
    },
]

# property_name, material_type, modifer_type, modifer_name, eval_code
MODIFIER_MATRIX = [
    [ "eye_iris_depth", "EYE_RIGHT", "DISPLACE", "Eye_Displace_R", "mod.strength = parameters.eye_iris_depth"],
    [ "eye_pupil_scale", "EYE_RIGHT", "UV_WARP", "Eye_UV_Warp_R", "mod.scale = (1.0 / parameters.eye_pupil_scale, 1.0 / parameters.eye_pupil_scale)" ],

    [ "eye_iris_depth", "EYE_LEFT", "DISPLACE", "Eye_Displace_L", "mod.strength = parameters.eye_iris_depth"],
    [ "eye_pupil_scale", "EYE_LEFT", "UV_WARP", "Eye_UV_Warp_L", "mod.scale = (1.0 / parameters.eye_pupil_scale, 1.0 / parameters.eye_pupil_scale)" ],

    [ "eye_occlusion_displace", "OCCLUSION_RIGHT", "DISPLACE", "Occlusion_Displace_All_R", "mod.strength = parameters.eye_occlusion_displace"],
    [ "eye_occlusion_inner", "OCCLUSION_RIGHT", "DISPLACE", "Occlusion_Displace_Inner_R", "mod.strength = parameters.eye_occlusion_inner"],
    [ "eye_occlusion_outer", "OCCLUSION_RIGHT", "DISPLACE", "Occlusion_Displace_Outer_R", "mod.strength = parameters.eye_occlusion_outer"],
    [ "eye_occlusion_top", "OCCLUSION_RIGHT", "DISPLACE", "Occlusion_Displace_Top_R", "mod.strength = parameters.eye_occlusion_top"],
    [ "eye_occlusion_bottom", "OCCLUSION_RIGHT", "DISPLACE", "Occlusion_Displace_Bottom_R", "mod.strength = parameters.eye_occlusion_bottom"],

    [ "eye_occlusion_displace", "OCCLUSION_LEFT", "DISPLACE", "Occlusion_Displace_All_L", "mod.strength = parameters.eye_occlusion_displace"],
    [ "eye_occlusion_inner", "OCCLUSION_LEFT", "DISPLACE", "Occlusion_Displace_Inner_L", "mod.strength = parameters.eye_occlusion_inner"],
    [ "eye_occlusion_outer", "OCCLUSION_LEFT", "DISPLACE", "Occlusion_Displace_Outer_L", "mod.strength = parameters.eye_occlusion_outer"],
    [ "eye_occlusion_top", "OCCLUSION_LEFT", "DISPLACE", "Occlusion_Displace_Top_L", "mod.strength = parameters.eye_occlusion_top"],
    [ "eye_occlusion_bottom", "OCCLUSION_LEFT", "DISPLACE", "Occlusion_Displace_Bottom_L", "mod.strength = parameters.eye_occlusion_bottom"],

    [ "tearline_displace", "TEARLINE_RIGHT", "DISPLACE", "Tearline_Displace_All_R", "mod.strength = -parameters.tearline_displace"],
    [ "tearline_inner", "TEARLINE_RIGHT", "DISPLACE", "Tearline_Displace_Inner_R", "mod.strength = -parameters.tearline_inner"],

    [ "tearline_displace", "TEARLINE_LEFT", "DISPLACE", "Tearline_Displace_All_L", "mod.strength = -parameters.tearline_displace"],
    [ "tearline_inner", "TEARLINE_LEFT", "DISPLACE", "Tearline_Displace_Inner_L", "mod.strength = -parameters.tearline_inner"],

]

BASIC_PROPS = [
    ["OUT", "Value",    "", "skin_ao"],
    ["OUT", "Value",    "", "skin_basic_specular"],
    ["IN", "To Min",    "", "skin_basic_roughness"],
    ["OUT", "Value",    "", "eye_specular"],
    ["OUT", "Value",    "", "eye_basic_roughness"],
    ["OUT", "Value",    "", "eye_basic_normal"],
    ["IN", "Strength",  "", "eye_occlusion"],
    ["IN", "Value",     "eye_basic_hsv", "eye_basic_brightness"],
    ["OUT", "Value",    "", "teeth_specular"],
    ["IN", 1,           "", "teeth_roughness"],
    ["OUT", "Value",    "", "tongue_specular"],
    ["IN", 1,           "", "tongue_roughness"],
    ["OUT", "Value",    "", "nails_specular"],
    ["OUT", "Value",    "", "hair_ao"],
    ["OUT", "Value",    "", "hair_specular"],
    ["OUT", "Value",    "", "hair_scalp_specular"],
    ["OUT", "Value",    "", "hair_bump", "parameters.hair_bump / 1000"],
    ["OUT", "Value",    "", "default_ao"],
    ["OUT", "Value",    "", "default_bump", "parameters.default_bump / 1000"],
    ["IN", "Alpha",     "eye_tearline_shader", "tearline_alpha"],
    ["IN", "Roughness", "eye_tearline_shader", "tearline_roughness"],
]

def get_prop_matrix(prop_name):
    matrix = []
    for mixer in PROP_MATRIX:
        for group in mixer["groups"]:
            for input in group["inputs"]:
                if input[1] == prop_name:
                    matrix.append([mixer, group, input])
    return matrix

def get_prop_matrix_group(group_name):
    group_name = "(" + group_name + ")"
    for mixer in PROP_MATRIX:
        if mixer["start"] in group_name:
            if mixer["end"] in group_name:
                for group in mixer["groups"]:
                    if type(group["name"]) is list:
                        for name in group["name"]:
                            if name in group_name:
                                return group
                    else:
                        if group["name"] in group_name:
                            return group
    return None

def set_from_prop_matrix(node, cache, group_name):
    props = bpy.context.scene.CC3ImportProps
    parameters = cache.parameters
    scope = locals()

    group = get_prop_matrix_group(group_name)

    for input in group["inputs"]:
        try:
            if len(input) == 3:
                prop_eval = input[2]
            else:
                prop_eval = "parameters." + input[1]

            prop_value = eval(prop_eval, None, scope)

            nodeutils.set_node_input(node, input[0], prop_value)

        except Exception as e:
                utils.log_error("set_from_prop_matrix(): Unable to evaluate or set: " + prop_eval, e)


"""
******************************************************************************
* Purpose: Commercial data processing using queue
*
* @author:  Ramnath Patro
* @version: 1.0
* @since:   29-3-2019
*
******************************************************************************
"""

from Data_Structure_Programs.Datastructure_Operations import queue_anagram
qe = queue_anagram()
import json

from oops.Commercial_data_processing import buy,sell

sell_call = sell
buy_call = buy

class information:
    def customer_company_file_read(file_name, mode):
        fr = open(file_name, mode)
        file_data = json.load(fr)
        fr.close()
        return file_data

    def customer_company_file_write(file_name, customer_details):
        # fw = open(file_name, mode)
        # update_data = json.dump(file_name, fw)
        # fw.close()
        with open(file_name, "w") as file:
            json.dump(customer_details, file)
        return customer_details

    def new_acc(self):
        # customer_details = []
        # company_details = []
        customer_details = information.customer_company_file_read("customer_details.json", "r")
        # company_details = information.customer_company_file_read("company_details.json", "r")

        for element in customer_details:
            qe.enqueue(element)

        while True:
            try:
                inp = int(input("Enter the ID"))
                break
            except:
                print("Invalid ID")
                continue
        while True:
            try:
                for itr in customer_details:
                    # print(itr["customer_id"])
                    if itr["customer_id"] == inp:
                        raise ValueError
                break
            except:
                print("ID present please enter another id")
                inp = input("Enter the another ID")

        inp_name = input("Enter your name")

        while True:
            try:
                inp_bal = int(input("Enter your balance"))
                break
            except:
                print("Invalid balance")
                continue

        new_user = {
            'customer_id': inp,
            'customer_name': inp_name,
            'balance': inp_bal
        }
        # customer_details.append(new_user)
        # customer_details = []
        data = []
        qe.enqueue(new_user)
        for i in range(len(customer_details ) + 1):
            data.append(qe.dequeue())
        customer_details = information.customer_company_file_write("customer_details.json", data)

class search():
    def ser(self):
        customer_details = information.customer_company_file_read("customer_details.json", "r")
        company_details = information.customer_company_file_read("company_details.json", "r")

        while True:
            x = 0
            try:
                inp = int(input("Enter the ID"))
                for itr in customer_details:
                    # print(itr["customer_id"])
                    if itr["customer_id"] == inp:
                        print("Valid ID")
                        x = 1
                        break
                    # if x == 0:
                    #     inp = int(input("Enter the another ID"))
                    # if inp not in itr["customer_id"] and x != 1:
                    #     print("+++++++++++qqqqqqqq")
                    #     raise ValueError
                break
            except:
                print("Invalid ID")
                continue
        print(inp)

        while True:
            try:
                companyname = input("Enter the company Name:").lower()
                if not companyname.isalpha():
                    raise ValueError
                x = 0
                for itr in company_details:
                    for com_name in itr:
                        # print(j)
                        if com_name == companyname:
                            print("Valid ID")
                            x = 1
                            break
                if x == 1:
                    break
            except:
                print("Invalid company name")
                continue
        return inp, companyname
class buy(search):
    def buy_shar(self):
        transaction_name, customer_id, company_name, number_of_share, time_details = buy_call.buy_shar(self)
        try:
            qe = queue_anagram()

            with open('queue_transaction.json', 'r') as myfile:
                details = json.load(myfile)

            for element in details:
                qe.enqueue(element)

            details1 = {"transaction_name ": transaction_name,
                        "customer_id": customer_id,
                        "company_name": company_name,
                        "number_of_share": number_of_share,
                        "time details": time_details
                        }

            qe.enqueue(details1)
            temp = []
            size = qe.size()
            for i in range(size):
                temp.append(qe.dequeue())
            with open('queue_transaction.json', 'w') as data:
                json.dump(temp, data)

        except IOError as e:
            print("File Not Found")
            print(e)
        except Exception as e:
            print(e)

class sell(search):

    def sell_shar(self):
        transaction_name, customer_id, company_name, number_of_share, time_details = sell_call.sell_shar(self)

        try:
            qe = queue_anagram()

            with open('queue_transaction.json', 'r') as myfile:
                details = json.load(myfile)

            for element in details:
                qe.enqueue(element)

            details1 = {"transaction_name ": transaction_name,
                        "customer_id": customer_id,
                        "company_name": company_name,
                        "number_of_share": number_of_share,
                        "time details": time_details
                        }

            qe.enqueue(details1)
            temp = []
            size = qe.size()
            for i in range(size):
                temp.append(qe.dequeue())
            with open('queue_transaction.json', 'w') as data:
                json.dump(temp, data)

        except IOError as e:
            print("File Not Found")
            print(e)
        except Exception as e:
            print(e)

if __name__ == '__main__':

    while True:
        print()
        print("1.create New Account")
        print("2.Buy the share")
        print("3.Sell the share")
        print("4.Exit the program")
        inp = input("Enter the option ")

        while not inp.isdigit():
            print("Invalid input")
            inp = input("Enter the option ")
        inp = int(inp)

        if inp == 1:
            print("create a new Account")
            i = information()
            i.new_acc()

        elif inp == 2:
            print("How much share you buy")
            b = buy()
            b.buy_shar()

        elif inp == 3:
            print("How much share you Sell")
            s = sell()
            s.sell_shar()

        elif inp == 4:
            print("Exit the program")
            exit()

        else:
            print("Invalid Input")


from typing import List

import numpy as np
import torch

from ...utils.record import AverageMeter
from ..base import BaseMetric

IGNORE_INDEX_NONE_VALUE = -100

class SegmentationMetric(BaseMetric):
    metric_names: List[str] = ['iou', 'pixel_acc']
    primary_metric: str = 'iou'

    def __init__(self, num_classes=None, ignore_index=IGNORE_INDEX_NONE_VALUE):
        super().__init__()
        self.ignore_index = ignore_index if ignore_index is not None else IGNORE_INDEX_NONE_VALUE
        self.K = num_classes

    def intersection_and_union_gpu(self, output, target):

        # 'K' classes, output and target sizes are N or N * L or N * H * W, each value in range 0 to K - 1.
        assert (output.dim() in [1, 2, 3])

        assert output.shape == target.shape
        output = output.view(-1)
        target = target.view(-1)
        output[target == self.ignore_index] = self.ignore_index
        intersection = output[output == target]
        area_intersection = torch.histc(intersection, bins=self.K, min=0, max=self.K-1)
        area_output = torch.histc(output, bins=self.K, min=0, max=self.K-1)
        area_target = torch.histc(target, bins=self.K, min=0, max=self.K-1)
        area_union = area_output + area_target - area_intersection

        intersection, union, target, output = \
            area_intersection.cpu().numpy(), area_union.cpu().numpy(), area_target.cpu().numpy(), area_output.cpu().numpy()

        return {
            'intersection': intersection,
            'union': union,
            'target': target,
            'output': output
        }

    def calibrate(self, pred, target, **kwargs):
        result_dict = {k: AverageMeter(k) for k in self.metric_names}
        B = pred.size(0)

        metrics = self.intersection_and_union_gpu(pred, target)
        result_dict['iou'].update(sum(metrics['intersection']) / (sum(metrics['union']) + 1e-10), n=B)
        result_dict['pixel_acc'].update(sum(metrics['intersection']) / (sum(metrics['target']) + 1e-10), n=B)

        return {k: v.avg for k, v in result_dict.items()}


from django.urls import path

from . import views

app_name = '{{cookiecutter.project_slug.lower()}}'
urlpatterns = [
    path('', views.cover, name='cover'),
    path('home/', views.home, name='home'),
    path('sobre/', views.sobre, name='sobre'),
]


import cv2

imgpath='data/images/20.jpg'
txtpath = 'data/labels/20.txt'
img = cv2.imread(imgpath)
with open(txtpath, 'r') as f:
    p = f.readlines()[0]

pix = [float(h) for h in p.split()]
print(pix)

for k in range(9):
    i,j = int(pix[2*k]), int(pix[2*k+1])
    cv2.circle(img, (i,j), 3,(255,0,0), -1)
cv2.imshow('test', img)
cv2.waitKey(0)


'''
Demonstrates two numerical integration methods (trapezoid and Simpson) and compares
them to values calculated with SymPy.
'''
from math import pi
import sympy as sym
import numpy as np
import matplotlib.pyplot as plt

def integrate_trapezoid(f, a, b, n):
    '''
    Numerical integration of definite integral from a to b of function f.
    Uses trapezoid rule (große Trapezregel), dividing the range [a, b]
    into n equal intervals.

    Parameters
    ----------
    f : function
        The function to be integrated.
    a : number
        Lower bound of integral.
    b : number
        Upper bound of integral.
    n : number
        Count of intervals into which [a, b] is divided for approximation.

    Returns
    -------
    number
        Integral as approximated value.
    '''
    x_sigma_sum = np.arange(a, b, (b-a)/n)[1:]
    sigma_sum = sum(list(map(f, x_sigma_sum)))
    return ((f(a) + f(b))/2 + sigma_sum) * (b - a) / n

def integrate_simpson(f, a, b, n):
    '''
    Numerical integration of definite integral from a to b of function f.
    Uses Simpson formula (große Simpsonformel), dividing the range [a, b]
    into n*2 equal intervals.

    Parameters
    ----------
    f : function
        The function to be integrated.
    a : number
        Lower bound of integral.
    b : number
        Upper bound of integral.
    n : number
        Controls precision of approximation. The range [a, b] is divided into 2*n intervals.

    Returns
    -------
    number
        Integral as approximated value.
    '''
    x_odd_indexes = np.arange(a, b, (b-a)/(2*n))[1::2]
    sum_x_odd_indexes = sum(list(map(f, x_odd_indexes)))

    x_even_indexes = np.arange(a, b, (b-a)/(2*n))[0::2][1:]
    sum_x_even_indexes = sum(list(map(f, x_even_indexes)))

    return (f(a) + f(b) + 2*sum_x_even_indexes + 4*sum_x_odd_indexes) * (b - a) / (6*n)

def plot_f(title, f_sympy, f, lower_bound, upper_bound, margin=0):
    '''
    Helper function for plotting.
    '''
    x_axis = np.linspace(lower_bound - margin,
                         upper_bound + margin, int(margin * 100))
    plt.title(title)
    plt.plot(x_axis, f(x_axis), color='blue', linewidth=2, label=str(f_sympy))
    plt.grid(True)
    section = np.linspace(lower_bound, upper_bound, 100)
    plt.fill_between(section, f(section), color='red', alpha=0.2)
    plt.legend()
    return plt

def input_number(prompt, default=1, minimum=0, maximum=1000):
    '''
    Helper function for reading a number from standard input.
    '''
    number_incomplete = True
    while number_incomplete:
        number_string = input(prompt + ' [' + str(default) + ']: ').strip()
        if not number_string:
            number_string = str(default)
        try:
            number_value = float(number_string)
            if number_value < minimum:
                print(number_value, 'is below minimum', minimum)
            elif number_value > maximum:
                print(number_value, 'is above maximum', maximum)
            else:
                number_incomplete = False
        except (ValueError) as bad_number_string:
            print(bad_number_string)
            continue
    return number_value

def parabola(comment, f_integrate, n, f_parabola):
    '''
    Helper function for calculating and printing the definite integral of a parabola.
    '''
    print('{0} {1}: '.format(comment, n),
          f_integrate(f_parabola, -1, 1, n))

def torus(comment, f_integrate, n, f_concave, f_convex):
    '''
    Helper function for calculating and printing the volume of a torus.
    '''
    print('{0} {1}: '.format(comment, n),
          pi * f_integrate(f_concave, -r, r, n)
          - pi * f_integrate(f_convex, -r, r, n))

x = sym.symbols('x')

########################################################################
# A parabola as a simple case for testing.
########################################################################
f_parabola_sympy = -x**2 + 1
f_parabola = sym.lambdify(x, f_parabola_sympy, "numpy")

parabola_integral_sympy = sym.integrate(f_parabola_sympy, (x, -1, 1))

title = 'Area under curve {0} from -1 to 1'.format(f_parabola_sympy)
print(' ')
print(title)
print('-'*len(title))

print('SymPy: ', parabola_integral_sympy)

n_values_trapezoid = [4, 10, 80, 400]
list(map(lambda x: parabola('Parabola Trapezoid', integrate_trapezoid, x, f_parabola),
         n_values_trapezoid))

n_values_simpson = [2, 5, 40, 200]
list(map(lambda x: parabola('Parabola Simpson', integrate_simpson, x, f_parabola),
         n_values_simpson))

plot_f('Area under curve with SymPy: ' +
       str(parabola_integral_sympy), f_parabola_sympy, f_parabola, -1, 1, 0.5)
plt.show()

########################################################################
# Torus volume (solid of revolution)
########################################################################
print(' ')
r = input_number('Enter Torus Minor Radius r', 0.5, 0.1, 10)
R = input_number('Enter Torus Major Radius R', 4, 1, 20)

plt.figure('Two Solids of Revolution for Calculating a Torus Volume')

# First solid of revolution is defined by a concave semicircle.
f_semicircle_concave_sympy = sym.sqrt(r**2 - x**2) + R
f_semicircle_concave = sym.lambdify(x, f_semicircle_concave_sympy, "numpy")

volume_semicircle_concave_sympy = pi * \
    sym.integrate(f_semicircle_concave_sympy**2, (x, -r, r))

plt.subplot(1, 2, 1, title='Semicircle Concave')
plt.axis('equal')
plt.ylim(-0.5, R*1.3)
plot_f('Volume: ' + str(volume_semicircle_concave_sympy),
       f_semicircle_concave_sympy, f_semicircle_concave, -r, r)

# Second solid of revolution is defined by a convex semicircle.
f_semicircle_convex_sympy = -sym.sqrt(r**2 - x**2) + R
f_semicircle_convex = sym.lambdify(x, f_semicircle_convex_sympy, "numpy")

volume_semicircle_convex_sympy = pi * \
    sym.integrate(f_semicircle_convex_sympy**2, (x, -r, r))

plt.subplot(1, 2, 2, title='Semicircle Convex')
plt.axis('equal')
plt.ylim(-0.5, R*1.3)
plot_f('Volume: ' + str(volume_semicircle_convex_sympy),
       f_semicircle_convex_sympy, f_semicircle_convex, -r, r)

title = 'Volume of Torus with r={0} R={1}'.format(r, R)
print(' ')
print(title)
print('-'*len(title))

f_semicircle_concave_squared = sym.lambdify(
    x, f_semicircle_concave_sympy**2, "numpy")
f_semicircle_convex_squared = sym.lambdify(
    x, f_semicircle_convex_sympy**2, "numpy")

print('SymPy: ', volume_semicircle_concave_sympy - volume_semicircle_convex_sympy)

# Calculate and print torus volume with trapezoid method for n from 2^2 to 2^15.
n_values_trapezoid = list(map(lambda x: 2**x, np.arange(2, 16)))  # 2^2 to 2^15
list(map(lambda x: torus('Trapezoid', integrate_trapezoid, x,
                         f_semicircle_concave_squared, f_semicircle_convex_squared),
         n_values_trapezoid))

# Calculate and print torus volume with Simpson method for n from 2^1 to 2^14.
n_values_simpson = list(map(lambda x: 2**x, np.arange(1, 15)))  # 2^1 to 2^14
list(map(lambda x: torus('Simpson', integrate_simpson, x,
                         f_semicircle_concave_squared, f_semicircle_convex_squared),
         n_values_simpson))

plt.show()


import pytest
import semver
import warnings

import reframe
import reframe.core.runtime as rt
import reframe.core.warnings as warn
import reframe.utility.color as color
import unittests.fixtures as fixtures

@pytest.fixture(params=['colors', 'nocolors'])
def with_colors(request):
    with rt.temp_runtime(fixtures.BUILTIN_CONFIG_FILE, 'generic',
                         {'general/colorize': request.param == 'colors'}):
        yield request.param == 'colors'

def test_deprecation_warning():
    with pytest.warns(warn.ReframeDeprecationWarning):
        warn.user_deprecation_warning('deprecated')

def test_deprecation_warning_from_version():
    next_version = semver.VersionInfo.parse(reframe.VERSION).bump_minor()
    warn.user_deprecation_warning('deprecated', str(next_version))

def test_deprecation_warning_formatting(with_colors):
    message = warnings.formatwarning(
        'deprecated', warn.ReframeDeprecationWarning, 'file', 10, 'a = 1'
    )
    expected = 'file:10: WARNING: deprecated\na = 1\n'
    if with_colors:
        expected = color.colorize(expected, color.YELLOW)

    assert message == expected

def test_deprecation_warning_formatting_noline(tmp_path, with_colors):
    srcfile = tmp_path / 'file'
    srcfile.touch()

    message = warnings.formatwarning(
        'deprecated', warn.ReframeDeprecationWarning, srcfile, 10
    )
    expected = f'{srcfile}:10: WARNING: deprecated\n<no line information>\n'
    if with_colors:
        expected = color.colorize(expected, color.YELLOW)

    assert message == expected

def test_random_warning_formatting():
    message = warnings.formatwarning(
        'deprecated', UserWarning, 'file', 10, 'a = 1'
    )
    assert message == f'file:10: UserWarning: deprecated\n  a = 1\n'


run0 = False
run1 = False
run2 = False
run3 = False
snakeAgentOn = False
bbAgentOn = False
run4 = True

while True:

    import pygame
    import sys
    import math
    import random
    import torch
    import torch.nn as nn
    import torch.optim as optim
    import torch.nn.functional as F
    import os
    import numpy as np
    import matplotlib.pyplot as plt
    from IPython import display

    ######################################
    # Section for any and all cited code:
    #           cited as per MIT style rules, cited inline as Cit01
    # Citation 01:
    #       https://youtu.be/L8ypSXwyBds retrieved 4/22/2023
    #
    #       Some of the functions: plot, QTrainerSnake, LinearQNet
    #       Some of the run==0 and run==2 While loops
    #
    #           Despite this citation, a fair bit of what is cited is standard as
    #           per pytorch library documentation, such as remember, the memory training and some
    #           other things not listed as cited inline.
    #
    #       End of Citation 01
    ######################################

    # initialize pygame
    pygame.init()

    # defining global variables
    SW, SH = 800, 800
    mainFont = pygame.font.SysFont("calibri", 40)
    snakeButtonText = mainFont.render("Snake!", True, "white")
    bbButtonText = mainFont.render("Brick Breaker", True, "white")
    snakeAIButtonText = mainFont.render("Snake AI", True, "white")
    bbAIButtonText = mainFont.render("Brick Breaker AI", True, "white")

    gameStatePause = False

    # creating window
    screen = pygame.display.set_mode((SW, SH))
    pygame.display.set_caption("Main Menu")

    # functions
    def menuText(text, font, color, x, y):
        img = font.render(text, True, color)
        screen.blit(img, (x, y))

    #classes
    class mainMenuButton:
        def __init__(self, buttonText, x, y):
            self.buttonText = buttonText
            self.rect = self.buttonText.get_rect(center=(x, y))

        def draw(self):
            global run0
            global run1
            global run2
            global run3
            global run4
            # get mouse position
            pos = pygame.mouse.get_pos()

            #check mouseover and click
            if self.rect.collidepoint(pos):
                if pygame.mouse.get_pressed()[0]:
                    if self.buttonText == snakeButtonText:
                        run0 = True
                    elif self.buttonText == bbButtonText:
                        run1 = True
                    elif self.buttonText == snakeAIButtonText:
                        run2 = True
                    elif self.buttonText == bbAIButtonText:
                        run3 = True

            screen.blit(self.buttonText, self.rect)

     #class pauseMenuButton():
     #   def __init__(self, buttonText, buttonFunction, x, y):
     #       self.buttonText = buttonText
     #       self.buttonFile = buttonFile
     #       self.rect =

     #objects

    snakeButton = mainMenuButton(snakeButtonText, 75, 450)
    bbButton = mainMenuButton(bbButtonText, 125, 500)
    snakeAIButton = mainMenuButton(snakeAIButtonText, 88, 550)
    bbAIButton = mainMenuButton(bbAIButtonText, 146, 600)

    plt.ion()

    def plot(scores, meanScores):
        display.clear_output(wait=True)
        display.display(plt.gcf())
        plt.clf()
        plt.title('Training...')  # Cit01
        plt.xlabel('Number of Games')  # Cit01
        plt.ylabel('Score')  # Cit01
        plt.plot(scores)
        plt.plot(meanScores)
        plt.ylim(ymin=0)
        plt.text(len(scores) - 1, scores[-1], str(scores[-1]))
        plt.text(len(meanScores) - 1, meanScores[-1], str(meanScores[-1]))
        plt.show(block=False)
        plt.pause(.1)

    class snakeLinearQNet(nn.Module):  # Cit01, but also largely standard documentation
        def __init__(self, inputSize, hiddenSize, outputSize):
            super().__init__()
            self.linear1 = nn.Linear(inputSize, hiddenSize)
            self.linear2 = nn.Linear(hiddenSize, outputSize)

        def forward(self, x):
            x = F.relu(self.linear1(x))
            x = self.linear2(x)
            return x

        def save(self, fileName="model.pth"):
            modelFolderPath = "./model"
            if not os.path.exists(modelFolderPath):
                os.makedirs(modelFolderPath)

            fileName = os.path.join(modelFolderPath, fileName)
            torch.save(self.state_dict(), fileName)

    class QTrainerSnake:
        def __init__(self, model, lr, gamma):
            self.lr = lr
            self.gamma = gamma
            self.model = model
            self.optimizer = optim.Adam(model.parameters(), lr=self.lr)
            self.criterion = nn.MSELoss()

        def trainStep(self, state, action, reward, stateNext, gameOver):
            state = torch.tensor(state, dtype=torch.float)
            stateNext = torch.tensor(stateNext, dtype=torch.float)
            action = torch.tensor(action, dtype=torch.long)
            reward = torch.tensor(reward, dtype=torch.float)
            # (n, x)

            if len(state.shape) == 1: # Cit01
                    # (1, x)
                state = torch.unsqueeze(state, 0)
                stateNext = torch.unsqueeze(stateNext, 0)
                action = torch.unsqueeze(action, 0)
                reward = torch.unsqueeze(reward, 0)
                gameOver = (gameOver, )

            # 1: predicted Q values in current state
            pred = self.model(state)

            target = pred.clone()
            for idx in range(len(gameOver)): # Cit01
                QNew = reward[idx]
                if not gameOver[idx]:
                    QNew = reward[idx] + self.gamma * torch.max(self.model(stateNext[idx]))

                target[idx][torch.argmax(action).item()] = QNew

            #2: QNew = r + gamma * max(next predicted q value), only if not gameOver
            #pred.clone()
            #preds[argmax(action)] = QNew
            self.optimizer.zero_grad()
            loss = self.criterion(target, pred)
            loss.backward()

            self.optimizer.step()

    while run0 == True:
        import pygame
        import random
        from enum import Enum
        from collections import namedtuple

        pygame.init()
        font = pygame.font.SysFont('calibri', 25)

        class Direction(Enum):
            RIGHT = 1
            LEFT = 2
            UP = 3
            DOWN = 4

        Point = namedtuple('Point', 'x, y')

        # rgb colors
        white = (255, 255, 255)
        red = (200, 0, 0)
        blue1 = (0, 0, 255)  # Cit01 for two colored snake rects idea
        blue2 = (0, 100, 255)  # Cit01 for two colored snake rects idea
        black = (0, 0, 0)

        BLOCK_SIZE = 20
        SPEED = 20  # Cit01 specifically for how this variable works as opposed to fps

        class SnakeGame:

            def __init__(self, w=640, h=480):
                self.w = w  # Cit01
                self.h = h  # Cit01
                # init display
                self.display = pygame.display.set_mode((self.w, self.h))
                pygame.display.set_caption('Snake')
                self.clock = pygame.time.Clock()

                # init game state
                self.direction = Direction.RIGHT

                self.head = Point(self.w / 2, self.h / 2)  # Cit01
                self.snake = [self.head,  # Cit01
                              Point(self.head.x - BLOCK_SIZE, self.head.y),  # Cit01
                              Point(self.head.x - (2 * BLOCK_SIZE), self.head.y)]  # Cit01

                self.score = 0
                self.food = None
                self._placeFood()

            def _placeFood(self):
                x = random.randint(0, (self.w - BLOCK_SIZE) // BLOCK_SIZE) * BLOCK_SIZE
                y = random.randint(0, (self.h - BLOCK_SIZE) // BLOCK_SIZE) * BLOCK_SIZE
                self.food = Point(x, y)
                if self.food in self.snake:
                    self._placeFood()

            def playStep(self):
                # 1. collect user input
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        pygame.quit()
                        quit()
                    if event.type == pygame.KEYDOWN:
                        if event.key == pygame.K_LEFT:
                            self.direction = Direction.LEFT
                        elif event.key == pygame.K_RIGHT:
                            self.direction = Direction.RIGHT
                        elif event.key == pygame.K_UP:
                            self.direction = Direction.UP
                        elif event.key == pygame.K_DOWN:
                            self.direction = Direction.DOWN

                # 2. move
                self._move(self.direction)  # update the head
                self.snake.insert(0, self.head)

                # 3. check if game over
                gameOver = False
                if self._isCollision():
                    gameOver = True
                    return gameOver, self.score

                # 4. place new food or just move
                if self.head == self.food:
                    self.score += 1
                    self._placeFood()
                else:
                    self.snake.pop()

                # 5. update ui and clock
                self._updateUI()
                self.clock.tick(SPEED)
                # 6. return game over and score
                return gameOver, self.score

            def _isCollision(self):  # Cit01
                # hits boundary
                if self.head.x > self.w - BLOCK_SIZE or self.head.x < 0 or self.head.y > self.h - BLOCK_SIZE or self.head.y < 0:
                    return True
                # hits itself
                if self.head in self.snake[1:]:
                    return True

                return False

            def _updateUI(self):
                self.display.fill(black)

                for pt in self.snake:
                    pygame.draw.rect(self.display, blue1, pygame.Rect(pt.x, pt.y, BLOCK_SIZE, BLOCK_SIZE))  # Cit01 two colored snake
                    pygame.draw.rect(self.display, blue2, pygame.Rect(pt.x + 4, pt.y + 4, 12, 12))  # Cit01 two colored snake idea

                pygame.draw.rect(self.display, red, pygame.Rect(self.food.x, self.food.y, BLOCK_SIZE, BLOCK_SIZE))

                self.text = font.render("Score: " + str(self.score), True, white)
                self.display.blit(self.text, [0, 0])
                pygame.display.flip()

            def _move(self, direction):
                x = self.head.x
                y = self.head.y
                if direction == Direction.RIGHT:
                    x += BLOCK_SIZE
                elif direction == Direction.LEFT:
                    x -= BLOCK_SIZE
                elif direction == Direction.DOWN:
                    y += BLOCK_SIZE
                elif direction == Direction.UP:
                    y -= BLOCK_SIZE

                self.head = Point(x, y)

        while run0 == True:
            game = SnakeGame()

            # game loop
            while True:
                gameOver, score = game.playStep()

                if gameOver == True:
                    break

            print('Final Score', score)

            pygame.quit()

    while run1 == True:
        pygame.init()

        # colors
        white = (255, 255, 255)
        black = (0, 0, 0)
        red = (255, 0, 0)
        green = (0, 255, 0)
        blue = (50, 100, 200)
        gray = (220, 220, 220)

        # classes
        class Paddle(pygame.sprite.Sprite):
            def __init__(self):
                super().__init__()
                black = (0, 0, 0)

                self.image = pygame.Surface([120, 20])
                pygame.draw.rect(self.image, black, [0, 0, 120, 20])
                self.rect = self.image.get_rect()

            def moveRight(self):
                self.rect.x += 6
                if self.rect.x >= (SW - 120):
                    self.rect.x = SW - 120

            def moveLeft(self):
                self.rect.x -= 6
                if self.rect.x <= 0:
                    self.rect.x = 0

        class Ball(pygame.sprite.Sprite):
            def __init__(self):
                super().__init__()
                red = (255, 0, 0)

                self.image = pygame.Surface([20, 20])
                pygame.draw.rect(self.image, red, [0, 0, 20, 20])
                self.rect = self.image.get_rect()

                self.velocity = [random.randint(3, 7), random.randint(4, 8)]

            def initPos(self):
                self.rect.x = (SW / 2) - 10
                self.rect.y = SH - 300

            def update(self):
                self.rect.x += self.velocity[0]
                self.rect.y += self.velocity[1]

            def bounceX(self):
                self.velocity[0] = -self.velocity[0]

            def bounceY(self):
                self.velocity[1] = -self.velocity[1]

        class Brick(pygame.sprite.Sprite):
            def __init__(self, color):
                super().__init__()

                self.image = pygame.Surface([100, 20])
                pygame.draw.rect(self.image, color, [0, 0, 100, 20])
                self.rect = self.image.get_rect()

        # functions
        def createBricks(c, r):
            for i in range(c):
                for j in range(r):
                    brick = Brick(blue)
                    brick.rect.x = 20 + i * 110
                    brick.rect.y = 20 + j * 30
                    allBricks.add(brick)
                    allSprites.add(brick)

        def killAllBricks():
            for brick in allBricks:
                brick.kill()

        # display
        screen = pygame.display.set_mode((SW, SH))
        pygame.display.set_caption("Brick Breaker")

        # clock
        FPS = 60
        clock = pygame.time.Clock()

        # create paddle
        paddle = Paddle()
        paddle.rect.x = (SW / 2) - 60
        paddle.rect.y = (SH) - 60

        # create ball
        ball = Ball()
        ball.initPos()

        # group of sprites
        allSprites = pygame.sprite.Group()
        allSprites.add(paddle)
        allSprites.add(ball)

        # bricks
        allBricks = pygame.sprite.Group()

        # create bricks
        createBricks(7, 9)

        score = 0
        lives = 3
        start = False
        while True:
            # event listeners
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    pygame.quit()
                    sys.exit()

            # fill screen
            screen.fill(gray)

            # draw the sprites
            allSprites.draw(screen)

            # handle key events
            keys = pygame.key.get_pressed()
            if keys[pygame.K_SPACE]:
                start = True
            if keys[pygame.K_RIGHT]:
                paddle.moveRight()
            if keys[pygame.K_LEFT]:
                paddle.moveLeft()

            # Score
            font = pygame.font.Font(None, 40)
            text = font.render(f'Score: {score}', 1, black)
            screen.blit(text, (20, SH - 30))
            # Lives
            font = pygame.font.Font(None, 40)
            text = font.render(f'Lives: {lives}', 1, black)
            screen.blit(text, (SW - 120, SH - 30))

            # game over message
            if lives == 0:
                font = pygame.font.Font(None, 40)
                text = font.render(f'GAME OVER', 1, black)
                screen.blit(text, (SW / 2.5, SH / 2))
                font = pygame.font.Font(None, 40)
                text = font.render(f'PRESS SPACE TO REPLAY', 1, black)
                screen.blit(text, (SW / 3.3, SH / 1.5))
            if len(allBricks) == 0:
                font = pygame.font.Font(None, 40)
                text = font.render(f'YOU WIN!!', 1, black)
                screen.blit(text, (SW / 2.5, SH / 2))
                ball.initPos()
                score = 0
                start = False

            if start:
                # update the Sprites
                allSprites.update()
                if lives == 0:
                    lives = 3
                    score = 0
                    killAllBricks()
                    createBricks(7, 9)
                if ball.rect.right >= SW or ball.rect.left < 0:
                    ball.bounceX()
                if ball.rect.top <= 0:
                    ball.bounceY()
                if ball.rect.y >= SH - 40:
                    ball.initPos()
                    lives -= 1
                    start = False

                # collisions with paddle
                if ball.rect.colliderect(paddle.rect):
                    if abs(paddle.rect.top - ball.rect.bottom) < 9:
                        ball.bounceY()
                    if abs(paddle.rect.left - ball.rect.right) < 9 or abs(paddle.rect.right - ball.rect.left) < 9:
                        ball.bounceX()

                # collisions with bricks
                ballHitList = pygame.sprite.spritecollide(ball, allBricks, False)
                for brick in ballHitList:
                    ball.bounceY()
                    brick.kill()
                    score += 1

            # draw a line
            pygame.draw.line(screen, black, [0, SH - 35], [SW, SH - 35], 5)

            # update display
            pygame.display.update()

            # tick clock
            clock.tick(FPS)

    from collections import deque
    MAX_MEMORY = 100_000
    BATCH_SIZE = 1000
    LR = 0.001

    class snakeAgent:

        def __init__(self):
            self.numGames = 0
            self.epsilon = 0  # randomness
            self.gamma = 0.9  # discount rate, must be smaller than 1
            self.memory = deque(maxlen=MAX_MEMORY)  # popleft()
            self.model = snakeLinearQNet(11, 256, 3)
            self.trainer = QTrainerSnake(self.model, lr=LR, gamma=self.gamma)

        def getState(self, game):  # Cit01, this is the one thing I was
                                   # completely lost on without the cited source
            head = game.snake[0]
            pointL = Point(head.x - 20, head.y)
            pointR = Point(head.x + 20, head.y)
            pointU = Point(head.x, head.y - 20)
            pointD = Point(head.x, head.y + 20)

            dirL = game.direction == Direction.LEFT
            dirR = game.direction == Direction.RIGHT
            dirU = game.direction == Direction.UP
            dirD = game.direction == Direction.DOWN

            state = [
                # Danger straight
                (dirR and game.isCollision(pointR)) or
                (dirL and game.isCollision(pointL)) or
                (dirU and game.isCollision(pointU)) or
                (dirD and game.isCollision(pointD)),

                # Danger right
                (dirU and game.isCollision(pointR)) or
                (dirD and game.isCollision(pointL)) or
                (dirL and game.isCollision(pointU)) or
                (dirR and game.isCollision(pointD)),

                # Danger left
                (dirD and game.isCollision(pointR)) or
                (dirU and game.isCollision(pointL)) or
                (dirR and game.isCollision(pointU)) or
                (dirL and game.isCollision(pointD)),

                # Move direction
                dirL,
                dirR,
                dirU,
                dirD,

                # Food location
                game.food.x < game.head.x,  # food left
                game.food.x > game.head.x,  # food right
                game.food.y < game.head.y,  # food up
                game.food.y > game.head.y  # food down
                ]
            return np.array(state, dtype=int)

        def remember(self, state, action, reward, nextState, gameOver):
            self.memory.append((state, action, reward, nextState, gameOver)) # popleft if MAX_MEMORY is reached

        def trainLongMemory(self):
            if len(self.memory) > BATCH_SIZE:
                miniSample = random.sample(self.memory, BATCH_SIZE)  # list of tuples
            else:
                miniSample = self.memory

            states, actions, rewards, nextStates, gameOvers = zip(*miniSample)
            self.trainer.trainStep(states, actions, rewards, nextStates, gameOvers)
        def trainShortMemory(self, state, action, reward, nextState, gameOver):
            self.trainer.trainStep(state, action, reward, nextState, gameOver)

        def getAction(self, state):  # Cit01
            # random moves: tradeoff exploration / exploitation
            self.epsilon = 80 - self.numGames
            finalMove = [0,0,0]
            if random.randint(0, 200) < self.epsilon:
                move = random.randint(0, 2)
                finalMove[move] = 1
            else:
                state0 = torch.tensor(state, dtype=torch.float)
                prediction = self.model(state0)
                move = torch.argmax(prediction).item()
                finalMove[move] = 1

            return finalMove

    def snakeTrain():  # Cit01
        global numGames
        plotScores = []
        plotMeanScores = []
        totalScore = 0
        record = 0
        agent = snakeAgent()
        game = SnakeAI()
        while True:  # Cit01
            # get old state
            stateOld = agent.getState(game)

            # get move
            finalMove = agent.getAction(stateOld)

            # perform move and get new state
            reward, gameOver, score = game.playStep(finalMove)
            stateNew = agent.getState(game)

            # train short term memory
            agent.trainShortMemory(stateOld, finalMove, reward, stateNew, gameOver)

            # remember
            agent.remember(stateOld, finalMove, reward, stateNew, gameOver)

            if gameOver:
                # train long memory
                game.reset()
                agent.numGames += 1
                agent.trainLongMemory()

                if score > record:
                    record = score
                    agent.model.save()

                print("Game", agent.numGames, "Score", score, "Record:", record)

                plotScores.append(score)
                totalScore += score
                meanScore = totalScore / agent.numGames
                plotMeanScores.append(meanScore)
                plot(plotScores, plotMeanScores)

    def turnOnSnakeAgent():
        global snakeAgentOn
        snakeAgentOn = True

    def turnOnbbAgent():
        global bbAgentOn
        bbAgentOn = True

    while run2 == True:
        import pygame
        import random
        from enum import Enum
        from collections import namedtuple

        pygame.init()
        font = pygame.font.SysFont('calibri', 25)

        class Direction(Enum):
            RIGHT = 1
            LEFT = 2
            UP = 3
            DOWN = 4

        Point = namedtuple('Point', 'x, y')

        # rgb colors
        white = (255, 255, 255)
        red = (200, 0, 0)
        blue1 = (0, 0, 255)  # Cit01
        blue2 = (0, 100, 255)  # Cit01
        black = (0, 0, 0)

        BLOCK_SIZE = 20
        SPEED = 120  #snake game for player at 20

        class SnakeAI:

            def __init__(self, w=640, h=480):
                self.w = w  # Cit01
                self.h = h  # Cit01
                # init display
                self.display = pygame.display.set_mode((self.w, self.h))
                pygame.display.set_caption('Snake')
                self.clock = pygame.time.Clock()

                # init game state
                self.direction = Direction.RIGHT

                self.head = Point(self.w / 2, self.h / 2)  # Cit01
                self.snake = [self.head,  # Cit01
                              Point(self.head.x - BLOCK_SIZE, self.head.y),  # Cit01
                              Point(self.head.x - (2 * BLOCK_SIZE), self.head.y)]  # Cit01

                self.score = 0
                self.food = None
                self._placeFood()
                self.frameIteration = 0

            def reset(self):
                # init game state
                self.direction = Direction.RIGHT

                self.head = Point(self.w / 2, self.h / 2)
                self.snake = [self.head,
                              Point(self.head.x - BLOCK_SIZE, self.head.y),
                              Point(self.head.x - (2 * BLOCK_SIZE), self.head.y)]

                self.score = 0
                self.food = None
                self._placeFood()
                self.frameIteration = 0

            def _placeFood(self):
                x = random.randint(0, (self.w - BLOCK_SIZE) // BLOCK_SIZE) * BLOCK_SIZE
                y = random.randint(0, (self.h - BLOCK_SIZE) // BLOCK_SIZE) * BLOCK_SIZE
                self.food = Point(x, y)
                if self.food in self.snake:
                    self._placeFood()

            def playStep(self, action):
                self.frameIteration += 1
                # 1. collect user input
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        pygame.quit()
                        quit()

                # 2. move
                self._move(action)  # update the head
                self.snake.insert(0, self.head)

                # 3. check if game over
                reward = 0
                gameOver = False
                if self.isCollision() or self.frameIteration > 100*len(self.snake):
                    gameOver = True
                    reward = -10
                    return reward, gameOver, self.score

                # 4. place new food or just move
                if self.head == self.food:
                    self.score += 1
                    reward = 10
                    self._placeFood()
                else:
                    self.snake.pop()

                # 5. update ui and clock
                self._updateUI()
                self.clock.tick(SPEED)
                # 6. return game over and score
                return reward, gameOver, self.score

            def isCollision(self, pt=None):  # Cit01
                if pt is None:
                    pt = self.head
                # hits boundary
                if pt.x > self.w - BLOCK_SIZE or pt.x < 0 or pt.y > self.h - BLOCK_SIZE or pt.y < 0:
                    return True
                # hits itself
                if pt in self.snake[1:]:
                    return True

                return False

            def _updateUI(self):
                self.display.fill(black)

                for pt in self.snake:
                    pygame.draw.rect(self.display, blue1, pygame.Rect(pt.x, pt.y, BLOCK_SIZE, BLOCK_SIZE))  # Cit01
                    pygame.draw.rect(self.display, blue2, pygame.Rect(pt.x + 4, pt.y + 4, 12, 12))  # Cit01

                pygame.draw.rect(self.display, red, pygame.Rect(self.food.x, self.food.y, BLOCK_SIZE, BLOCK_SIZE))

                text = font.render("Score: " + str(self.score), True, white)
                self.display.blit(text, [0, 0])
                pygame.display.flip()

            def _move(self, action):
                # [straight, right, left]

                clockwise = [Direction.RIGHT, Direction.DOWN, Direction.LEFT, Direction.UP]  # Cit01
                idx = clockwise.index(self.direction)  # Cit01

                if np.array_equal(action, [1, 0, 0]):  # Cit01
                    newDir = clockwise[idx]
                elif np.array_equal(action, [0, 1, 0]):  # Cit01
                    nextIdx = (idx + 1) % 4 #clockwise turn
                    newDir = clockwise[nextIdx]
                else:  # Cit01
                    nextIdx = (idx - 1) % 4 #counterclockwise turn
                    newDir = clockwise[nextIdx]

                self.direction = newDir

                x = self.head.x
                y = self.head.y
                if self.direction == Direction.RIGHT:
                    x += BLOCK_SIZE
                elif self.direction == Direction.LEFT:
                    x -= BLOCK_SIZE
                elif self.direction == Direction.DOWN:
                    y += BLOCK_SIZE
                elif self.direction == Direction.UP:
                    y -= BLOCK_SIZE

                self.head = Point(x, y)

        turnOnSnakeAgent()
        while snakeAgentOn == True:
            snakeTrain()

    while run3 == True:
        import neat
        import os
        import pickle
        import time

        pygame.init()

        # colors
        white = (255, 255, 255)
        black = (0, 0, 0)
        red = (255, 0, 0)
        green = (0, 255, 0)
        blue = (50, 100, 200)
        gray = (220, 220, 220)

        # group of sprites
        allSprites = pygame.sprite.Group()
        # bricks
        allBricks = pygame.sprite.Group()

        # classes
        class Paddle(pygame.sprite.Sprite):

            def __init__(self):
                super().__init__()
                black = (0, 0, 0)

                self.image = pygame.Surface([200, 20])
                pygame.draw.rect(self.image, black, [0, 0, 120, 20])
                self.rect = self.image.get_rect()

            def moveRight(self):
                self.rect.x += 7
                if self.rect.x >= (SW - 120):
                    self.rect.x = SW - 120

            def moveLeft(self):
                self.rect.x -= 7
                if self.rect.x <= 0:
                    self.rect.x = 0

        class Ball(pygame.sprite.Sprite):
            def __init__(self):
                super().__init__()
                red = (255, 0, 0)

                self.image = pygame.Surface([20, 20])
                pygame.draw.rect(self.image, red, [0, 0, 20, 20])
                self.rect = self.image.get_rect()

                self.velocity = [random.randint(-1, 1) * 5, 5]
                if self.velocity[0] == 0:
                    self.velocity[0] = 1

            def initPos(self):
                self.rect.x = (SW / 2) - 10
                self.rect.y = SH - 300

            def update(self):
                self.rect.x += self.velocity[0]
                self.rect.y += self.velocity[1]

            def bounceX(self):
                self.velocity[0] = -self.velocity[0]

            def bounceY(self):
                self.velocity[1] = -self.velocity[1]

        class Brick(pygame.sprite.Sprite):
            def __init__(self, color):
                super().__init__()

                self.image = pygame.Surface([100, 20])
                pygame.draw.rect(self.image, color, [0, 0, 100, 20])
                self.rect = self.image.get_rect()

        # functions
        def createBricks(c, r):
            global allSprites
            global allBricks
            for i in range(c):
                for j in range(r):
                    brick = Brick(blue)
                    brick.rect.x = 20 + i * 110
                    brick.rect.y = 20 + j * 30
                    allBricks.add(brick)
                    allSprites.add(brick)

        def killAllBricks():
            for brick in allBricks:
                brick.kill()

        class bbGameInfo:
            def __init__(self, score, fail, quit):
                self.score = score
                self.fail = fail
                self.quit = quit

        class bbGame:

            def __init__(self, SW, SH, screen):
                # display
                self.SW = SW
                self.SH = SH
                self.screen = screen
                pygame.display.set_caption("Brick Breaker")

                # create paddle
                self.paddle = Paddle()
                self.paddle.rect.x = (self.SW / 2) - 60
                self.paddle.rect.y = (self.SH) - 60

                # create ball
                self.ball = Ball()
                self.ball.initPos()

                # clock
                self.FPS = 600
                self.clock = pygame.time.Clock()

                # make sprites
                allSprites.add(self.paddle)
                allSprites.add(self.ball)

                # create bricks
                createBricks(7, 9)

                self.score = 0.0
                self.fail = 0
                self.quit = False

                #self.lives = 3

                self.cur = 0
                self.prev = 0

                self.start = True

            def _collisions(self):
                # collisions with paddle
                if self.ball.rect.colliderect(self.paddle.rect):
                    if abs(self.paddle.rect.top - self.ball.rect.bottom) < 9:
                        self.ball.bounceY()
                    if abs(self.paddle.rect.left - self.ball.rect.right) < 9 or abs(
                            self.paddle.rect.right - self.ball.rect.left) < 9:
                        self.ball.bounceX()

                # collisions with bricks
                self.ballHitList = pygame.sprite.spritecollide(self.ball, allBricks, False)
                for brick in self.ballHitList:
                    self.ball.bounceY()
                    brick.kill()
                    self.score += 1

            def resetPos(self):
                killAllBricks()
                createBricks(7, 9)
                self.ball.initPos()

            def loop(self):
                # event listeners
                keys = pygame.key.get_pressed()
                if keys[pygame.K_SPACE]:
                    self.quit = True
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        pygame.quit()
                        sys.exit()

                # fill screen
                self.screen.fill(gray)

                # draw the sprites
                allSprites.draw(screen)

                # handle key events
                #keys = pygame.key.get_pressed()
                #if keys[pygame.K_RIGHT]:
                #    self.paddle.moveRight()
                #if keys[pygame.K_LEFT]:
                #    self.paddle.moveLeft()

                # Score
                font = pygame.font.Font(None, 40)
                text = font.render(f'Score: {self.score}', 1, black)
                screen.blit(text, (20, self.SH - 30))
                # Lives
                #font = pygame.font.Font(None, 40)
                #text = font.render(f'Lives: {self.lives}', 1, black)
                #screen.blit(text, (self.SW - 120, self.SH - 30))

                # game over message
                #if self.lives == 0:
                #    font = pygame.font.Font(None, 40)
                #    text = font.render(f'GAME OVER', 1, black)
                #    self.screen.blit(text, (self.SW / 2.5, self.SH / 2))
                #    font = pygame.font.Font(None, 40)
                #    text = font.render(f'PRESS SPACE TO REPLAY', 1, black)
                #    self.screen.blit(text, (self.SW / 3.3, self.SH / 1.5))
                #if len(allBricks) == 0:
                #    font = pygame.font.Font(None, 40)
                #    text = font.render(f'YOU WIN!!', 1, black)
                #    self.screen.blit(text, (self.SW / 2.5, self.SH / 2))
                #    self.ball.initPos()
                #    self.score = 0
                #    self.start = False

                if self.start:
                    # update the Sprites
                    allSprites.update()
                    allBricks.update()
                    #if self.lives == 0:
                    #    self.lives = 3
                    #    self.score = 0
                    #    killAllBricks()
                    #    createBricks(7, 9)
                    if self.ball.rect.right >= self.SW or self.ball.rect.left < 0:
                        self.ball.bounceX()
                    if self.ball.rect.top <= 0:
                        self.ball.bounceY()
                    if self.ball.rect.y >= self.SH - 40:
                        self.fail = 1
                        killAllBricks()
                        self.paddle.kill()
                        gameInfo = bbGameInfo(self.score, self.fail, self.quit)

                        return gameInfo
                        #self.resetPos()
                        #self.lives -= 1

                    self._collisions()

                # draw a line
                pygame.draw.line(self.screen, black, [0, self.SH - 35], [SW, self.SH - 35], 5)

                # update display
                pygame.display.update()
                allSprites.update()
                allBricks.update()
                allSprites.draw(screen)

                # tick clock
                self.clock.tick(self.FPS)

                gameInfo = bbGameInfo(self.score, self.fail, self.quit)

                return gameInfo

            def moveAI(self, net):
                player = [(self.genome, net, self.paddle)]
                for (genome, net, paddle) in player:
                    output = net.activate(
                        (self.paddle.rect.x, self.ball.rect.x, abs(self.paddle.rect.y - self.ball.rect.y)))
                    decision = output.index(max(output))

                    if decision == 0:  # Don't move
                        self.genome.fitness -= 0.01  # we want to discourage this
                    elif decision == 1:
                        self.paddle.moveRight()
                    else:  # Move down
                        self.paddle.moveLeft()

            def bbGameTrainAI(self, game, genome, config):
                startTime = time.time()

                net = neat.nn.FeedForwardNetwork.create(genome, config)
                self.genome = genome

                while True:

                    gameInfo = game.loop()

                    self.moveAI(net)

                    duration = time.time() - startTime

                    if gameInfo.fail >= 1 or gameInfo.score >= 62 or gameInfo.quit:
                        killAllBricks()
                        self.paddle.kill()
                        self.calcFitness(gameInfo.score, duration)
                        break

            def calcFitness(self, gameInfoScore, duration):
                self.genome.fitness += gameInfoScore + duration

        def evalGenomes(genomes, config):
            #screen = pygame.display.set_mode((SW, SH))
            #game = bbGame(800, 800, screen)

            for i, (genomeID, genome) in enumerate(genomes):
                screen = pygame.display.set_mode((SW, SH))
                killAllBricks()
                game = bbGame(800, 800, screen)
                genome.fitness = 0
                if i == len(genomes) - 1:
                    killAllBricks()
                    game.paddle.kill()
                    break

                game.bbGameTrainAI(game, genome, config)

                #for j, (genomeID1, genome1) in enumerate(genomes):
                #    genome1.fitness = 0
                #    if i == len(genomes) - 1:
                #        break

                #    game.bbGameTrainAI(game, genome1, config)

        def runNeat(config):
            #p = neat.Checkpointer.restore_checkpoint('neat-checkpoint-7')
            p = neat.Population(config)
            p.add_reporter(neat.StdOutReporter(True))
            stats = neat.StatisticsReporter()
            p.add_reporter(stats)
            p.add_reporter(neat.Checkpointer(1))

            winner = p.run(evalGenomes, 50)

            with open("best.pickle", "wb") as f:
                pickle.dump(winner, f)

        bbAgentOn = True
        while bbAgentOn:
            localDir = os.path.dirname(__file__)
            configPath = os.path.join(localDir, "config.txt")

            config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
                                 neat.DefaultSpeciesSet, neat.DefaultStagnation,
                                 configPath)

            runNeat(config)

    run4 = True
    while run4:
        screen.fill("purple")

        snakeButton.draw()
        bbButton.draw()
        snakeAIButton.draw()
        bbAIButton.draw()
        mainMenuText = mainFont.render("Games Played By AI!!", True, "white")
        mainMenuTextRect = mainMenuText.get_rect(center=(377, 200))
        screen.blit(mainMenuText, mainMenuTextRect)

        # event handler
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_SPACE:
                    gameStatePause = True
            if event.type == pygame.QUIT:
                run = False
                pygame.quit()
            # todo add event listeners for click mainMenu buttons
        if run0 + run1 + run2 + run3 != 0:
            run4 = False

        if run4 == True:
            pygame.display.update()


import datetime as dt
import OpenGL.GL as GL
import pygame as pg
import pygame.locals as co
from enum import Enum
from typing import Union, Optional
from contextlib import contextmanager
from easygl.structures import Vec4, Vec2
from .events import *

__all__ = [
    'BlendMode',
    'DisplayError',
    'GLWindow',
    'Multisamples',
    'Projection',
]

class DisplayError(Exception):
    pass

class Multisamples(Enum):
    none = 0
    double = 1
    triple = 2
    quad = 3

class BlendMode(Enum):
    none = 0
    add = 1
    alpha = 2
    multiply = 3

class Projection(Enum):
    custom = 0
    ortho_up = 1
    ortho_down = 2

class GLWindow(object):

    _current = None    # type: GLWindow

    def __init__(self, width, height, title, multisamples, blendmode, projection, **kwargs):
        # type: (int, int, str, Multisamples, BlendMode, Projection) -> None
        if self.__class__._current is not None:
            raise DisplayError("Display already initialized. Call reset() method to change settings.")

        color = kwargs.get('clear_color', (0., 0., 0., 1.))
        size = width, height
        flags = co.OPENGL

        flags |= co.RESIZABLE if kwargs.get('resizable', False) else 0
        flags |= co.DOUBLEBUF if kwargs.get('doublebuf', False) else 0
        flags |= co.FULLSCREEN if kwargs.get('fullscreen', False) else 0
        flags |= co.HWSURFACE if kwargs.get('hwsurface', False) else 0

        pg.init()
        if multisamples is not Multisamples.none:
            samples = {
                Multisamples.double: 2,
                Multisamples.triple: 3,
                Multisamples.quad: 4
            }.get(multisamples, 2)
            pg.display.gl_set_attribute(pg.GL_MULTISAMPLESAMPLES, samples)

        surface = pg.display.set_mode(size, flags)
        # print(surface)
        width, height = surface.get_size()
        pg.display.set_caption(title, title)

        if multisamples is not Multisamples.none:
            GL.glEnable(GL.GL_MULTISAMPLE)
        GL.glEnable(GL.GL_BLEND)
        GL.glClearColor(*color)
        GL.glViewport(0, 0, width, height)

        self._handling_input = False
        self._rendering = False
        self._close_request = False
        self._blend_mode = None
        self.blend_mode = blendmode
        self._projection = projection
        self._flip_time = 0
        self._input_time = 0
        self._render_time = 0
        self._delta = 1

    @property
    def projection(self):
        return self._projection

    @property
    def title(self):
        return pg.display.get_caption()

    @title.setter
    def title(self, value):
        pg.display.set_caption(repr(value), repr(value))

    @property
    def should_close(self):
        # type: () -> bool
        return self._close_request

    @property
    def resolution(self):
        # type: () -> tuple
        return pg.display.get_surface().get_size()

    @property
    def width(self):
        # type: () -> int
        return pg.display.get_surface().get_width()

    @property
    def height(self):
        # type: () -> int
        return pg.display.get_surface().get_height()

    @property
    def blend_mode(self):
        # type: () -> BlendMode
        return self._blend_mode

    @blend_mode.setter
    def blend_mode(self, value):
        # type: (BlendMode) -> None
        if value is not self._blend_mode:
            self._blend_mode = value
            if value is BlendMode.none:
                GL.glBlendFunc(GL.GL_ONE, GL.GL_ZERO)

            elif value is BlendMode.add:
                try:
                    GL.glBlendFuncSeparate(GL.GL_SRC_ALPHA, GL.GL_ONE, GL.GL_ONE, GL.GL_ONE)
                except (NameError, Exception):
                    GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE)

            elif value is BlendMode.alpha:
                try:
                    GL.glBlendFuncSeparate(
                        GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA, GL.GL_ONE, GL.GL_ONE_MINUS_SRC_ALPHA)
                except (NameError, Exception):
                    GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA)

            elif value is BlendMode.multiply:
                try:
                    GL.glBlendFuncSeparate(GL.GL_ALPHA, GL.GL_ONE, GL.GL_ONE, GL.GL_ONE)
                except (NameError, Exception):
                    GL.glBlendFunc(GL.GL_DST_COLOR, GL.GL_ZERO)

    @property
    def mouse_pos(self):
        # type: () -> Vec2
        x, y = pg.mouse.get_pos()
        if self._projection is Projection.ortho_up:
            y = pg.display.get_surface().get_height() - y
        return Vec2(x, y)

    @property
    def mouse_motion(self):
        # type: () -> Vec2
        x, y = pg.mouse.get_rel()
        if self._projection is Projection.ortho_up:
            y = -y
        return Vec2(x, y)

    @property
    def frame_delta(self):
        # type: () -> int
        return self._delta

    @contextmanager
    def input(self, raw=False):
        # type: () -> None
        if self._handling_input:
            raise RuntimeError("Can't nest input processing contexts.")
        self._handling_input = True
        time = pg.time.get_ticks()
        delta = time - self._input_time
        self._input_time = time
        if raw:
            yield delta, pg.event.get(), pg.key.get_pressed(), Vec2(*pg.mouse.get_pos()), Vec2(*pg.mouse.get_rel())
        else:
            events = []
            for event in pg.event.get():
                if event.type == co.ACTIVEEVENT:
                    events.append(Focus(event.gain, event.state, pg.time.get_ticks()))

                elif event.type == co.QUIT:
                    now = dt.datetime.now()
                    ms = pg.time.get_ticks()
                    self._close_request = True
                    events.append(CloseWindow(ms, now))

                elif event.type == co.KEYDOWN:
                    ctrl = event.mod & co.KMOD_ALT != 0
                    shift = event.mod & co.KMOD_SHIFT != 0
                    alt = event.mod & co.KMOD_ALT != 0
                    events.append(KeyDown(event.key, event.unicode, event.mod, ctrl, shift, alt))

                elif event.type == co.KEYUP:
                    ctrl = event.mod & co.KMOD_ALT != 0
                    shift = event.mod & co.KMOD_SHIFT != 0
                    alt = event.mod & co.KMOD_ALT != 0
                    events.append(KeyUp(event.key, event.mod, ctrl, shift, alt))

                elif event.type == co.MOUSEMOTION:
                    height = pg.display.get_surface().get_height()
                    x, y = event.pos
                    mx, my = event.rel
                    if self._projection is Projection.ortho_up:
                        y = height - y
                        my = -my
                    lbutton, mbutton, rbutton = event.buttons
                    events.append(MouseMotion(Vec2(x, y), Vec2(mx, my), lbutton, mbutton, rbutton))

                elif event.type == co.MOUSEBUTTONDOWN:
                    height = pg.display.get_surface().get_height()
                    x, y = event.pos
                    if self._projection is Projection.ortho_up:
                        y = height - y
                    if event.button == 1:
                        events.append(LeftButtonDown(Vec2(x, y), x, y))
                    elif event.button == 2:
                        events.append(MiddleButtonDown(Vec2(x, y), x, y))
                    elif event.button == 3:
                        events.append(RightButtonDown(Vec2(x, y), x, y))
                    elif event.button == 4:
                        events.append(MouseWheelUp(Vec2(x, y), x, y))
                    else:
                        events.append(MouseWheelDown(Vec2(x, y), x, y))

                elif event.type == co.MOUSEBUTTONUP:
                    height = pg.display.get_surface().get_height()
                    x, y = event.pos
                    if self._projection is Projection.ortho_up:
                        y = height - y
                    if event.button == 1:
                        events.append(LeftButtonUp(Vec2(x, y), x, y))
                    elif event.button == 2:
                        events.append(MiddleButtonUp(Vec2(x, y), x, y))
                    else:
                        events.append(RightButtonUp(Vec2(x, y), x, y))

                elif event.type == co.VIDEORESIZE:
                    events.append(VideoResize(event.w, event.h, event.size))

                elif event.type == co.VIDEOEXPOSE:
                    now = dt.datetime.now()
                    ms = pg.time.get_ticks()
                    events.append(VideoExpose(ms, now))

                elif event.type == co.JOYAXISMOTION:
                    events.append(JoyAxis(event.joy, event.axis, event.value))

                elif event.type == co.JOYBALLMOTION:
                    events.append(JoyBall(event.joy, event.ball, event.rel))

                elif event.type == co.JOYHATMOTION:
                    events.append(JoyHat(event.joy, event.hat, event.value))

                elif event.type == co.JOYBUTTONDOWN:
                    events.append(JoyButtonDown(event.joy, event.button))

                elif event.type == co.JOYBUTTONUP:
                    events.append(JoyButtonUp(event.joy, event.button))
            keys = pg.key.get_pressed()
            mouse_pos = Vec2(*pg.mouse.get_pos())
            mouse_rel = Vec2(*pg.mouse.get_rel())
            if self._projection is Projection.ortho_up:
                mouse_pos.y = self.height - mouse_pos.y
                mouse_rel.y = -mouse_rel.y
            yield delta, events, keys, mouse_pos, mouse_rel
        self._handling_input = False

    @contextmanager
    def rendering(self, clear_color=None):
        # type: (Optional[Union[Vec4, tuple, list]]) -> None
        if self._rendering:
            raise RuntimeError("Can't nest GLWindow rendering contexts.")
        self._rendering = True
        time = pg.time.get_ticks()
        delta = time - self._render_time
        self._render_time = time

        yield delta

        pg.display.flip()
        time = pg.time.get_ticks()
        self._delta = time - self._flip_time
        self._flip_time = time
        if clear_color is not None:
            GL.glClearColor(*clear_color)
        GL.glClear(GL.GL_COLOR_BUFFER_BIT)
        self._rendering = False

    def close(self):
        # type: () -> None
        self._close_request = True


from django.conf.urls import patterns, url

from calorietracker import views, import_old_db

urlpatterns = patterns('',
                       url(r'^$', views.index, name='index'),
                       url(r'^jstree.json$', views.jstree_json, name='jstree.json'),
                       url(r'^import_old_db$', import_old_db.import_old_db, name='import_old_db'),
                       url(r'^calories.json$', views.calories_json, name='calories.json'),
                       url(r'^invalidate_cache$', views.invalidate_cache, name='invalidate_cache'),
                       url(r'^food.json$', views.food_json, name='food.json'),
                       url(r'^calorie_graph.js$', views.calorie_graph_js, name='calorie_graph.js'),
                       url(r'^meal_tree.js$', views.meal_tree_js, name='meal_tree.js'),
                       url(r'^add_meal$', views.add_meal, name='add_meal'),
                       url(r'^delete_meal$', views.delete_meal, name='delete_meal'),
                       url(r'^weights.json$', views.weights_json, name='weights.json'),
                       url(r'^weight_graph.js$', views.weight_graph_js, name='weight_graph.js'),
                       url(r'^add_weight', views.add_weight, name='add_weight'),
                       url(r'^mobile',views.mobile,name='mobile'),

)


"""
102. Binary Tree Level Order Traversal
Given the root of a binary tree, return the level order traversal of its nodes' values.
(i.e., from left to right, level by level).

The number of nodes in the tree is in the range [0, 2000].
-1000 <= Node.val <= 1000

"""

# Definition for a binary tree node.
from typing import List

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        if root is None:
            return []

        answer = []
        def levelTraverse(node: TreeNode, level):
            if len(answer) <= level:
                answer.append([node.val])
            else:
                answer[level].append(node.val)

            if node.left is not None:
                levelTraverse(node.left, level+1)
            if node.right is not None:
                levelTraverse(node.right, level + 1)
        # level을 tracking 하면서 없으면 추가해서 넣는거로.

        levelTraverse(root, 0)
        return answer

if __name__ == "__main__":
    s = Solution()
    root = TreeNode(3)
    root.left = TreeNode(9)
    root.right = TreeNode(20)
    root.right.left = TreeNode(15)
    root.right.right = TreeNode(7)
    print(s.levelOrder(root), "[[3],[9,20],[15,7]]")
    root = TreeNode(1)
    print(s.levelOrder(root), "[[1]]")
    root = None
    print(s.levelOrder(root), "[]")

"""
Runtime: 36 ms, faster than 61.92% of Python3 online submissions for Binary Tree Level Order Traversal.
Memory Usage: 15.2 MB, less than 12.86% of Python3 online submissions for Binary Tree Level Order Traversal.
"""