from nisnap.utils.aseg import basal_ganglia_labels, cortical_labels
from nisnap.utils.aseg import amygdala_nuclei, hippocampal_subfields

__format__ = '.png'

def pick_labels(data, labels):
    """Replaces all values by zero in a Numpy array (`data`) except values
    passed in `labels`. Used to select some specific labels in a given
    segmentation."""

    import numpy as np
    data2 = np.where(np.isin(data, labels), data, 0)
    return data2

def _aget_cmap_(labels=[]):
    import json
    import numpy as np
    import nisnap
    import os.path as op
    n_labels = len(labels)
    fp = op.join(op.dirname(nisnap.__file__), 'utils', 'colormap.json')
    LUT = json.load(open(fp))
    LUT = {int(k): v for k, v in list(LUT.items())}

    if n_labels is None:
        n_labels = len(list(LUT.keys()))
    max_label = int(np.max(labels))
    LUT = [LUT.get(i, [0, 0, 0]) for i in range(0, max_label + 1)]
    LUT = np.array(LUT)
    LUT = LUT / 255.0
    return LUT

def _plot_contours_in_slice_(slice_seg, target_axis, labels=None):
    """Plots contour around the data in slice (after binarization)"""
    import numpy as np

    if labels is None:  # if n_labels is not provided then take max from slice
        labels = list(np.unique(slice_seg))

    from matplotlib.colors import ListedColormap
    cmap = ListedColormap(_aget_cmap_(labels))

    num_labels = len(cmap.colors)
    unique_labels = np.arange(num_labels, dtype='int16')

    from matplotlib import cm, colors
    normalize_labels = colors.Normalize(vmin=0, vmax=num_labels, clip=True)
    seg_mapper = cm.ScalarMappable(norm=normalize_labels, cmap=cmap)
    unique_labels_display = np.array(unique_labels)  # np.setdiff1d(unique_labels, 0)

    color_for_label = seg_mapper.to_rgba(unique_labels_display)

    from matplotlib import pyplot as plt
    plt.sca(target_axis)

    for index, label in enumerate(unique_labels_display):
        binary_slice_seg = slice_seg == index
        if not binary_slice_seg.any():
            continue
        plt.contour(binary_slice_seg,
                    levels=[0.5, ],
                    colors=(color_for_label[index],),
                    linewidths=1,
                    alpha=1,
                    zorder=1)

    return

def _snap_contours_(data, slices, axis, bg, figsize=None, bb=None, pbar=None):
    from matplotlib import pyplot as plt
    import numpy as np
    import tempfile
    import os

    plt.style.use('dark_background')

    paths = []
    fd, path = tempfile.mkstemp(suffix='_%s%s' % (axis, __format__))
    os.close(fd)
    paths.append(path)

    same_box = bb is not None
    if not same_box:
        bb = {}

    lambdas = {'x': lambda y, x: y[:, :, x],
               'y': lambda y, x: y[:, x, :],
               'z': lambda y, x: y[x, :, :]}

    labels = list(np.unique(data))

    if figsize is None:
        ratio = len(slices) / float(len(slices[0]))
        figsize = (figsize, figsize * ratio)

    fig = plt.figure(dpi=300, figsize=figsize)

    abs_index = 0
    for a, chunk in enumerate(slices):
        if not same_box:
            bb[a] = []

        for i, slice_index in enumerate(chunk):
            abs_index += 1

            ax = fig.add_subplot(len(slices), len(slices[0]), abs_index,
                                 label='%s_%s' % (axis, slice_index))
            test = np.flip(np.swapaxes(np.abs(lambdas[axis](data,
                                                            int(slice_index))),
                                       0, 1), 0)
            if not same_box:
                xs, ys = np.where(test != 0)
                bb[a].append((xs, ys))
            else:
                xs, ys = bb[a][i]

            if len(xs) == 0:
                continue

            test3 = np.flip(np.swapaxes(np.abs(lambdas[axis](bg,
                                                             int(slice_index))),
                                        0, 1), 0)
            test3 = test3[min(xs):max(xs) + 1, min(ys):max(ys) + 1]

            ax.imshow(test3, interpolation='none', cmap='gray')

            test = test[min(xs):max(xs) + 1, min(ys):max(ys) + 1]

            _plot_contours_in_slice_(test, ax, labels=labels)
            ax.axis('off')

            ax.text(0, 0, '%i' % slice_index,
                    {'color': 'w', 'fontsize': 10}, va="bottom", ha="left")

            if pbar is not None:
                pbar.update(1)

    fig.savefig(path, facecolor=fig.get_facecolor(),
                bbox_inches='tight',
                transparent=True,
                pad_inches=0)
    return paths, bb

def _snap_slices_(data, slices, axis, bb=None, figsize=None, pbar=None):
    from matplotlib import pyplot as plt
    import numpy as np
    import tempfile
    import os

    labels = list(np.unique(data))
    has_bb = bb is not None
    d = data.ravel()
    ratio = len(d[d <= 10]) / len(d)
    # has_orig = len(labels) > 50  # not bb is None
    is_raw = ratio < 0.80

    paths = []
    if not has_bb:
        bb = {}

    fig = plt.figure(dpi=300, figsize=figsize)

    from nisnap.utils.slices import __get_lambdas__
    lambdas = __get_lambdas__(data)

    fd, path = tempfile.mkstemp(suffix='_%s%s' % (axis, __format__))
    os.close(fd)
    paths.append(path)

    abs_index = 0
    for a, chunk in enumerate(slices):

        if not has_bb:
            bb[a] = []

        for i, slice_index in enumerate(chunk):
            abs_index += 1
            ax = fig.add_subplot(len(slices), len(slices[0]), abs_index,
                                 label='%s_%s' % (axis, slice_index))

            test = np.flip(np.swapaxes(np.abs(lambdas[axis](int(slice_index))),
                                       0, 1), 0)
            if not has_bb:
                xs, ys = list(np.where(test != 0))[:2]
                bb[a].append((xs, ys))
            else:
                xs, ys = bb[a][i]

            if len(xs) == 0:
                continue

            if len(data.shape) == 4:  # RGB mode (4D volume)
                test = test[min(xs):max(xs) + 1, min(ys):max(ys) + 1, :]
                ax.imshow((test * 255).astype(np.uint8), interpolation='none',)

            else:  # standard 3D label volume

                if is_raw:
                    vmax, cmap = (None, 'gray')
                else:
                    vmax = np.max(labels)
                    from matplotlib.colors import ListedColormap
                    cmap = ListedColormap(_aget_cmap_(labels))

                test = test[min(xs):max(xs) + 1, min(ys):max(ys) + 1]

                ax.imshow(test, interpolation='none', cmap=cmap,
                          vmin=0, vmax=vmax)

            ax.axis('off')
            ax.text(0, 0, '%i' % slice_index,
                    {'color': 'w', 'fontsize': 10}, va="bottom", ha="left")

            if pbar is not None:
                pbar.update(1)

    fig.savefig(path, facecolor=fig.get_facecolor(),
                bbox_inches='tight', transparent=True, pad_inches=0)
    return paths, bb

def __snap__(data, axes='xyz', bg=None, slices=None, rowsize=None,
             contours=False, figsize=None, samebox=False, margin=5):
    from matplotlib import pyplot as plt
    import logging as log

    plt.rcParams['figure.facecolor'] = 'black'
    plt.rcParams.update({'figure.max_open_warning': 0})

    from nisnap.utils.slices import cut_slices, _fix_rowsize_, _fix_figsize_
    from nisnap.utils.slices import __maxsize__

    rowsize = _fix_rowsize_(axes, rowsize)
    figsize = _fix_figsize_(axes, figsize)

    t = int(__maxsize__(data)/3.0)
    slices = cut_slices(data, axes, slices=slices, rowsize=rowsize,
                        threshold=t)
    n_slices = sum([sum([len(each) for each in slices[e]]) for e in axes])
    if n_slices == 0:
        msg = 'Should provide at least one slice. %s' % slices
        raise Exception(msg)
    has_orig = bg is not None

    if has_orig:
        n_slices = 2 * n_slices

    from tqdm import tqdm
    pbar = tqdm(total=n_slices, leave=False)

    paths, paths_orig = {}, {}

    for axis in axes:
        if samebox:
            from nisnap.utils.slices import __get_abs_minmax
            same_bb = __get_abs_minmax(data, axis, slices[axis], margin=margin)
            log.warning('Using bounding box: %s (axis %s)'
                        % (same_bb[0][0], axis))

        opt = {'slices': slices[axis],
               'axis': axis,
               'figsize': figsize[axis],
               'pbar': pbar}

        if contours:
            # Rendering contours
            if samebox:
                opt['bb'] = same_bb

            path, bb = _snap_contours_(data, bg=bg, **opt)
            paths[axis] = path

        else:
            opt['bb'] = None if not samebox else same_bb
            # Rendering masks
            path, bb = _snap_slices_(data, **opt)
            paths[axis] = path

        if has_orig:
            opt['bb'] = bb if not samebox else same_bb
            path, _ = _snap_slices_(bg, **opt)
            paths_orig[axis] = path

    pbar.update(n_slices)
    pbar.close()
    return paths, paths_orig

def __stack_img__(filepaths):
    import nibabel as nib
    import numpy as np

    stack = [np.asarray(nib.load(e).dataobj) for e in filepaths[:]]

    data = np.stack(stack, axis=-1)

    data2 = np.argmax(data, axis=-1) + 1
    black_pixels_mask = np.all(data == [0, 0, 0], axis=-1)
    data2[black_pixels_mask] = 0
    return data2

def plot_segment(filepaths, axes='xyz', bg=None, opacity=90, slices=None,
                 animated=False, savefig=None, contours=False, rowsize=None,
                 figsize=None, samebox=False, labels=None, margin=5):
    """Plots a set of segmentation maps/masks.

    Parameters
    ----------
    filepaths: a list of str
        Paths to segmentation maps (between 1 and 3). Must be of same
        dimensions and in same reference space.

    axes: string, or a tuple of str
        Choose the direction of the cuts (among 'x', 'y', 'z')

    bg: None or str
        Path to the background image that the masks will be plotted on top of.
        If nothing is specified, the segmentation maps/masks will be plotted
        only. The opacity (in %) of the segmentation maps when plotted over a
        background image. Only used if a background image is provided.
        Default: 10

    slices: None, or a tuple of floats
        The indexes of the slices that will be rendered. If None is given, the
        slices are selected automatically.

    animated: boolean, optional
        If True, the snapshot will be rendered as an animated GIF.
        If False, the snapshot will be rendered as a static PNG image. Default:
        False

    savefig: string, optional
        Filepath where the resulting snapshot will be created. If None is
        given, a temporary file will be created and/or the result will be
        displayed inline in a Jupyter Notebook.

    contours: boolean, optional
        If True, segmentations will be rendered as contoured regions. If False,
        will be rendered as superimposed masks. Default: False

    rowsize: None, or int, or dict
        Set the number of slices per row in the final compiled figure.
        Default: {'x': 9, 'y': 9, 'z': 6}

    figsize: None, or float
        Figure size (in inches) (matplotlib definition). Default: auto

    samebox: boolean, optional
        If True, bounding box will be fixed. If False, adjusted for each slice.

    labels: None or a tuple of int
        If a list of labels is provided, the label volume will be filtered to
        keep these labels only and remove the others.
        (Works with a label volume only, not with RGB mode)

    See Also
    --------
    xnat.plot_segment : To plot segmentation maps directly providing their
        experiment_id on an XNAT instance
    """
    import matplotlib
    import numpy as np
    import logging as log
    import os
    import tempfile
    matplotlib.use('Agg')

    fp = savefig
    if savefig is None:
        if animated:
            f, fp = tempfile.mkstemp(suffix='.gif')
        else:
            f, fp = tempfile.mkstemp(suffix=__format__)
        os.close(f)

    from nisnap.utils.parse import __check_axes__
    axes = __check_axes__(axes)

    # Creating snapshots (along given axes and original if needed)
    log.info('* Creating snapshots...')

    # Loading images
    import nibabel as nib
    if isinstance(filepaths, list):  # RGB mode
        data = __stack_img__(filepaths)
        log.info('* RGB mode')

    elif isinstance(filepaths, str):  # 3D label volume
        log.info('* Label volume')
        if labels is not None:
            from nisnap.utils import aseg
            filepaths = aseg.__picklabel_fs__(filepaths, labels=labels)
        data = np.asarray(nib.load(filepaths).dataobj)

    if bg is not None:
        bg = np.asarray(nib.load(bg).dataobj)

    paths, paths_orig = __snap__(data, axes=axes, bg=bg,
                                 slices=slices, contours=contours,
                                 rowsize=rowsize, figsize=figsize,
                                 samebox=samebox, margin=margin)

    from nisnap.utils.montage import __montage__
    has_orig = bg is not None
    __montage__(paths, paths_orig, axes, opacity, has_orig, animated,
                savefig=fp)

    if savefig is None:
        # Return image
        from IPython.display import Image
        return Image(filename=fp)


#USING BACKTRACKING
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        def isSafe(board, row, col, n):
            # checking the upper elements of that column
            for i in range(row):
                if board[i][col] == 'Q':
                    return False
            # checking the upper left diagonal
            for i, j in zip(range(row, -1, -1), range(col, -1, -1)):
                if board[i][j] == 'Q':
                    return False
            #checking the upper right diagonal
            for i, j in zip(range(row, -1, -1), range(col, n, 1)):
                if board[i][j] == 'Q':
                    return False
            return True

        def NQutil(board, row, n):
            if row >= n:
                printsolution(board, n)
                return
            for i in range(n):
                if isSafe(board, row, i, n):
                    board[row][i] = 'Q'
                    if NQutil(board, row + 1, n):
                        return True
                    board[row][i] = '.'
            return False
        list2=[]
        def printsolution(board, n):
            list1=[]
            for i in board:
                s=''
                for j in i:
                    s=s+j
                list1.append(s)
            list2.append(list1)
        board = [['.'for i in range(n)]for j in range(n)]
        NQutil(board,0,n)
        return list2


import getopt
import math
import os
import sys

import bpy
from typing import List, Optional, Final

import blender_compat
from patches import apply_patches_by_model_name
from data.edit_mode import EditMode
from data.object_shading import ObjectShading
from data.pokemon_render_data import PokemonRenderData
from data.shiny_color import ColorChannel, ShinyColors, ShinyColor
from data.shiny_info import ShinyInfo
from data.texture import Texture
from importer import import_hsd

class PkxIconGenCache(object):
    def __init__(self):
        self.shiny_color1: Final[list] = []
        self.shiny_color2: Final[list] = []
        self.shiny_mix: Final[list] = []
        # Mapping/Mat/Defaults by Blender images - (mat_name, mapping_node, [default_x, default_y])
        self.img_mat_mapping: Final[dict[str, list[(str, any, [float, float])]]] = {}
        # TexImage by Blender materials
        self.mat_tex_image: Final[dict[str, list]] = {}
        # TexImage by Blender images
        self.img_tex_image: Final[dict[str, list]] = {}
        # Name of materials that have been modified after getting imported
        self.processed_mats: Final[list[str]] = []

    def init_mat_cache(self, mat_name: str):
        self.mat_tex_image[mat_name] = []

    def add_mat_mapping(self, tex_node, mat):
        if tex_node.bl_idname != "ShaderNodeTexImage":
            print(f"Tried using {tex_node.bl_idname} to add to Mat/Mapping cache")
            return

        if tex_node.image.name not in self.img_mat_mapping:
            self.img_mat_mapping[tex_node.image.name] = []

        mapping_node = tex_node.inputs[blender_compat.tex_image_in.vector].links[0].from_node
        if mapping_node.bl_idname != "ShaderNodeMapping":
            print(f"Node attached to ShaderNodeTexImage for image {tex_node.image.name} was not ShaderNodeMapping, but {mapping_node.bl_idname} instead.")
        else:
            x = mapping_node.inputs[1].default_value[0]
            y = mapping_node.inputs[1].default_value[1]
            self.img_mat_mapping[tex_node.image.name].append((mat.name, mapping_node, [x, y]))

SHINYCOLOR1_NAME: Final[str] = "PKX_ShinyColor1"
SHINYCOLOR2_NAME: Final[str] = "PKX_ShinyColor2"
SHINYMIXNODE_NAME: Final[str] = "PKX_ShinyMixRGB"

cmd_args = None
assets_path: Optional[str] = None
pkx_cache = PkxIconGenCache()

def attach_debugger(debug_egg: str):
    # https://github.com/sybrenstuvel/random-blender-addons/blob/main/remote_debugger.py
    eggpath = os.path.abspath(debug_egg)

    if not os.path.exists(eggpath):
        print(f'Unable to find debug egg at {eggpath}.')
    else:
        if not any('pycharm-debug' in p for p in sys.path):
            sys.path.append(eggpath)

        import pydevd_pycharm
        pydevd_pycharm.settrace('localhost', port=1090, stdoutToServer=True, stderrToServer=True,
                                suspend=False)

def parse_cmd_args(script_args):
    global cmd_args
    global assets_path

    if cmd_args is None:
        cmd_args, _ = getopt.getopt(script_args, "", ["pkx-debug=", "debug-egg=", "assets-path="])
        for arg, value in cmd_args:
            if arg == "--assets-path" and value != "":
                assets_path = value
                print(f"Assets Path: {assets_path}")

def get_absolute_asset_path(model: str) -> str:
    return model.replace("{{AssetsPath}}", assets_path)

def get_relative_asset_path(model: str) -> str:
    return model.replace("{{AssetsPath}}/", "")

def import_models(prd: PokemonRenderData):
    objs = bpy.data.objects
    normal_imported: bool = False
    shiny_imported: bool = False

    shiny_info: ShinyInfo = prd.shiny

    if objs.find("Armature0") == -1:
        if assets_path is not None:
            true_path = get_absolute_asset_path(prd.render.model)
        else:
            true_path = prd.render.model

        print(f"Importing: {true_path}")
        import_hsd.load(None, bpy.context, true_path, 0, "scene_data", "SCENE", True, True, 1000, True)
        armature = objs["Armature0"]
        armature.hide_select = True
        armature.hide_viewport = True
        normal_imported = True

    if objs.find("Armature1") == -1 and shiny_info.render.model is not None:
        if assets_path is not None:
            shiny_true_path = get_absolute_asset_path(shiny_info.render.model)
        else:
            shiny_true_path = shiny_info.render.model

        print(f"Importing: {shiny_true_path}")
        import_hsd.load(None, bpy.context, shiny_true_path, 0, "scene_data", "SCENE", True, True, 1000, True)
        switch_model(shiny_info, EditMode.NORMAL)  # Hide shiny model on load
        shiny_armature = objs["Armature1"]
        shiny_armature.hide_select = True
        shiny_armature.hide_viewport = True
        shiny_imported = True

    if normal_imported or shiny_imported:
        bpy.ops.wm.save_mainfile()
    bpy.ops.wm.save_as_mainfile(filepath=os.path.join(os.path.dirname(bpy.data.filepath), "edit.blend"))

    # Run patches
    apply_patches_by_model_name(prd.render.model)
    apply_patches_by_model_name(prd.shiny.render.model)

    mats = bpy.data.materials
    for mat in mats:
        if mat.name in pkx_cache.processed_mats:
            break

        tree = mat.node_tree
        if tree is not None:
            #  Rough Materials
            # TODO Vertex Lighting
            bsdf = tree.nodes["Principled BSDF"]
            bsdf.inputs[blender_compat.principled_bsdf_in.metallic].default_value = 0
            bsdf.inputs[blender_compat.principled_bsdf_in.specular].default_value = 0
            bsdf.inputs[blender_compat.principled_bsdf_in.roughness].default_value = 1

            #  Reduce bump map strength
            bump_node_idx = tree.nodes.find("Bump")
            if bump_node_idx != -1:
                node_input = tree.nodes[bump_node_idx].inputs[blender_compat.bump_in.strength]
                if node_input.default_value == 1:  # Patches could change the value, changes only if it's still the same
                    node_input.default_value = 0.05

            # Setup shiny color
            if shiny_info.color1 is not None and shiny_info.color2 is not None and tree.nodes.find(f"{SHINYMIXNODE_NAME}.0") == -1:
                setup_shiny_mats(tree)

            # Cache
            pkx_cache.processed_mats.append(mat.name)

            pkx_cache.init_mat_cache(mat.name)
            for node in tree.nodes:
                if node.bl_idname == "ShaderNodeTexImage":
                    pkx_cache.mat_tex_image[mat.name].append(node)
                    pkx_cache.add_mat_mapping(node, mat)
                elif SHINYCOLOR1_NAME in node.name:
                    pkx_cache.shiny_color1.append(node)
                elif SHINYCOLOR2_NAME in node.name:
                    pkx_cache.shiny_color2.append(node)
                elif SHINYMIXNODE_NAME in node.name:
                    pkx_cache.shiny_mix.append(node)

    if shiny_info.color1 is not None and shiny_info.color2 is not None:
        update_all_shiny_colors(shiny_info.color1, shiny_info.color2)

def setup_shiny_mats(tree):
    uses_bump: bool = tree.nodes.find("Bump") != -1

    tex_number: int = 0
    for node in tree.nodes:
        if node.bl_idname == "ShaderNodeTexImage":
            texture_output_socket = node.outputs[0].links[0].to_socket

            if not uses_bump:
                color1_node = tree.nodes.new("ShaderNodeGroup")
                color1_node.name = f"{SHINYCOLOR1_NAME}.{tex_number}"
                color1_node.node_tree = bpy.data.node_groups['Color1']
            else:
                color1_node = None

            color2_node = tree.nodes.new("ShaderNodeGroup")
            color2_node.name = f"{SHINYCOLOR2_NAME}.{tex_number}"
            color2_node.node_tree = bpy.data.node_groups['Color2']

            mix_node = tree.nodes.new("ShaderNodeMixRGB")
            mix_node.name = f"{SHINYMIXNODE_NAME}.{tex_number}"
            mix_node.blend_type = "MIX"
            mix_node.inputs[0].default_value = 0

            if color1_node is not None:
                tree.links.new(node.outputs[0], color1_node.inputs[0])  # ShaderNodeTexImage.Color -> PKX_ShinyColor1.TexColor
                tree.links.new(node.outputs[1], color1_node.inputs[1])  # ShaderNodeTexImage.Alpha -> PKX_ShinyColor1.TexAlpha

                tree.links.new(color1_node.outputs[0], color2_node.inputs[0])  # PKX_ShinyColor1.TexColor -> PKX_ShinyColor2.Image
                tree.links.new(color1_node.outputs[1], color2_node.inputs[1])  # PKX_ShinyColor1.TexAlpha -> PKX_ShinyColor2.Alpha
            else:
                tree.links.new(node.outputs[0], color2_node.inputs[0])  # ShaderNodeTexImage.Color -> PKX_ShinyColor2.Image
                tree.links.new(node.outputs[1], color2_node.inputs[1])  # ShaderNodeTexImage.Alpha -> PKX_ShinyColor2.Alpha

            tree.links.new(color2_node.outputs[0], mix_node.inputs[2])  # PKX_ShinyColor2.TexColor -> PKX_ShinyMixRGB.Color2
            # TODO Manage Alpha

            tree.links.new(node.outputs[0], mix_node.inputs[1])  # ShaderNodeTexImage.Color -> PKX_ShinyMixRGB.Color1
            tree.links.new(mix_node.outputs[0], texture_output_socket)  # PKX_ShinyMixRGB.Color -> Previous output

            tex_number += 1

def show_shiny_mats():
    for node in pkx_cache.shiny_mix:
        node.inputs[0].default_value = 1

def hide_shiny_mats():
    for node in pkx_cache.shiny_mix:
        node.inputs[0].default_value = 0

def update_shiny_color(color: int, channel: ColorChannel, shiny_color: ShinyColors):
    if shiny_color == ShinyColors.Color1:
        for node in pkx_cache.shiny_color1:
            node.inputs[channel].default_value = color
    elif shiny_color == ShinyColors.Color2:
        for node in pkx_cache.shiny_color2:
            node.inputs[channel].default_value = color

def update_all_shiny_colors(color1: ShinyColor, color2: ShinyColor):
    for node in pkx_cache.shiny_color1:
        node.inputs[ColorChannel.R].default_value = color1.r
        node.inputs[ColorChannel.G].default_value = color1.g
        node.inputs[ColorChannel.B].default_value = color1.b
        node.inputs[ColorChannel.A].default_value = color1.a
    for node in pkx_cache.shiny_color2:
        node.inputs[ColorChannel.R].default_value = color2.r
        node.inputs[ColorChannel.G].default_value = color2.g
        node.inputs[ColorChannel.B].default_value = color2.b
        node.inputs[ColorChannel.A].default_value = color2.a

def show_message_box(message, title, icon='INFO'):
    def draw(self, context):
        self.layout.label(text=message)

    bpy.context.window_manager.popup_menu(draw, title=title, icon=icon)

def switch_model(shiny_info: ShinyInfo, mode: EditMode):
    if shiny_info.color1 is not None and shiny_info.color2 is not None:
        if mode == EditMode.SHINY or mode == EditMode.SHINY_SECONDARY:
            show_shiny_mats()
        else:
            hide_shiny_mats()
    elif shiny_info.render.model is not None:
        objs = bpy.data.objects

        if mode == EditMode.SHINY or mode == EditMode.SHINY_SECONDARY:
            show_armature(objs["Armature0"], False)
            show_armature(objs["Armature1"], True)
        else:
            show_armature(objs["Armature1"], False)
            show_armature(objs["Armature0"], True)
    else:
        raise Exception("PRD had no filter or alt model.")

def get_image_nodes(image_obj):
    if image_obj.name not in pkx_cache.img_tex_image:
        nodes: list = list()
        if image_obj is not None:
            for mat in bpy.data.materials:
                if mat.node_tree is not None:
                    for node in mat.node_tree.nodes:
                        if is_node_teximage_with_image(node, image_obj):
                            nodes.append(node)
        pkx_cache.img_tex_image[image_obj.name] = nodes

    return pkx_cache.img_tex_image[image_obj.name]

def set_custom_image(image_obj, texture_path: str) -> bool:
    success: bool = False

    nodes = get_image_nodes(image_obj)
    new_img = bpy.data.images.load(filepath=texture_path, check_existing=True)
    if image_is_integer_scale(image_obj, new_img):
        success = True
        for node in nodes:
            node.image = new_img
    return success

def image_is_integer_scale(original_img, new_img) -> bool:
    o_width: int = original_img.size[0]
    o_height: int = original_img.size[1]
    n_width: int = new_img.size[0]
    n_height: int = new_img.size[1]

    width_scale_check: bool = n_width % o_width == 0
    height_scale_check: bool = n_height % o_height == 0
    ratio_check: bool = o_width / o_height == n_width / n_height

    return width_scale_check and height_scale_check and ratio_check

def set_textures(textures: List[Texture]):
    for texture in textures:
        original_img = bpy.data.images[texture.name]
        custom_img = None
        if texture.path is not None:
            full_path: str = get_absolute_asset_path(texture.path)
            set_custom_image(bpy.data.images[texture.name], full_path)
            custom_img = bpy.data.images.load(filepath=full_path, check_existing=True)

        for mat in texture.mats:
            set_material_map(custom_img or original_img,
                             bpy.data.materials[mat.name],
                             mat.map.x,
                             mat.map.y)

def reset_texture_images(texture: Texture):
    if texture.path is not None:
        original_img = bpy.data.images[texture.name]
        custom_img = bpy.data.images.load(filepath=get_absolute_asset_path(texture.path), check_existing=True)

        nodes = get_image_nodes(custom_img)

        for node in nodes:
            node.image = original_img

def is_node_teximage_with_image(node, image_obj) -> bool:
    return node.bl_idname == "ShaderNodeTexImage" and node.image.name == image_obj.name

def is_custom_texture_used(texture_path: str, textures: List[Texture]):
    for texture in textures:
        if texture.path is not None and get_absolute_asset_path(texture.path) == texture_path:
            return True

    return False

def set_material_map(image_obj, mat_obj, x: float, y: float):
    for node in pkx_cache.mat_tex_image[mat_obj.name]:
        if is_node_teximage_with_image(node, image_obj):
            img_vector_input_node = node.inputs[blender_compat.tex_image_in.vector].links[0].from_node
            if img_vector_input_node.bl_idname == "ShaderNodeMapping":
                img_vector_input_node.inputs[1].default_value[0] = x
                img_vector_input_node.inputs[1].default_value[1] = y

def reset_materials_maps():
    for img_infos in list(pkx_cache.img_mat_mapping.values()):
        for (_, mapping_node, defaults) in img_infos:
            mapping_node.inputs[1].default_value[0] = defaults[0]
            mapping_node.inputs[1].default_value[1] = defaults[1]

def get_armature(prd: PokemonRenderData, mode: EditMode):
    objs = bpy.data.objects
    shiny_model = prd.shiny.render.model
    if shiny_model is not None and shiny_model != "" and (mode == EditMode.SHINY or mode == EditMode.SHINY_SECONDARY):
        armature = objs["Armature1"]
    else:
        armature = objs["Armature0"]

    return armature

def show_armature(armature_obj, show: bool):
    not_show = not show

    armature_obj.hide_render = not_show

    for child in armature_obj.children:
        child.hide_render = not_show
        child.hide_viewport = not_show

def allow_select_all_armatures(allow_select: bool):
    not_allow_select = not allow_select

    for child in bpy.data.objects["Armature0"].children:
        child.hide_select = not_allow_select

    shiny_armature_idx = bpy.data.objects.find("Armature1")
    if shiny_armature_idx != -1:
        for child in bpy.data.objects[shiny_armature_idx].children:
            child.hide_select = not_allow_select

def remove_objects(removed_objects: List[str]):
    objs = bpy.data.objects

    for obj_name in removed_objects:
        obj = objs[obj_name]
        obj.hide_render = True
        obj.hide_viewport = True

def update_shading(shading: ObjectShading):
    use_smooth = shading == ObjectShading.SMOOTH

    for child in bpy.data.objects["Armature0"].children:
        for polygon in child.data.polygons:
            polygon.use_smooth = use_smooth

    shiny_armature_idx = bpy.data.objects.find("Armature1")
    if shiny_armature_idx != -1:
        for child in bpy.data.objects[shiny_armature_idx].children:
            for polygon in child.data.polygons:
                polygon.use_smooth = use_smooth

def hue2rgb(hue: float) -> List[float]:
    """Input hue is [0, 1]"""
    sat = 1
    val = 1
    hue = convert_range(0, 1, 0, 360, hue)

    c: float = sat * val
    m: float = val - c
    h: float = hue / 60
    x: float = c * (1 - math.fabs(h % 2 - 1))

    rgb: List[float] = []
    if 0 <= h < 1:
        rgb = [c, x, 0]
    elif 1 <= h < 2:
        rgb = [x, c, 0]
    elif 2 <= h < 3:
        rgb = [0, c, x]
    elif 3 <= h < 4:
        rgb = [0, x, c]
    elif 4 <= h < 5:
        rgb = [x, 0, c]
    elif 5 <= h <= 6:
        rgb = [c, 0, x]

    for i in range(len(rgb)):
        rgb[i] += m
    return rgb

def rgb2hue(rgb: List[float]) -> float:
    """Result hue is [0, 1]"""
    r: float = rgb[0]
    g: float = rgb[1]
    b: float = rgb[2]

    max_c: float = max(rgb)
    min_c: float = min(rgb)
    delta: float = max_c - min_c

    hue: float = 0
    if delta == 0:
        hue = 0
    elif max_c == r:
        hue = ((g - b) / delta) % 6
    elif max_c == g:
        hue = ((b - r) / delta) + 2
    elif max_c == b:
        hue = ((r - g) / delta) + 4

    return convert_range(0, 6, 0, 1, hue)

def convert_range(original_start: float, original_end: float, new_start: float, new_end: float, value: float) -> float:
    if original_start > value:
        raise ValueError("Value was smaller than the original range.")

    elif original_end < value:
        raise ValueError("Value was greater than the original range.")

    scale: float = (new_end - new_start) / (original_end - original_start)
    return new_start + (value - original_start) * scale


# -*- coding: utf-8 -*-
"""
Created on Thu Dec  8 08:51:56 2022

@author: Nino
"""

from aoc import *
import numpy as np

trees = read_input('day08')
array = np.array([mapl(int, line) for line in trees])

# part 1
visible_mask = np.ones_like(array)
for i in range(1, array.shape[0]-1):
    for j in range(1, array.shape[1]-1):
        if array[i, j] <= min(array[i, :j].max(),
                              array[i, j+1:].max(),
                              array[:i, j].max(),
                              array[i+1:, j].max()):
            visible_mask[i, j] = 0

print(visible_mask.sum())

# part 2
def score_view(view, height):
    view_len = np.where(view >= height)[0]
    if view_len.size == 0:
        return len(view)
    else:
        return view_len[0] + 1

score = np.zeros_like(array)
for i in range(1, array.shape[0]-1):
    for j in range(1, array.shape[1]-1):
        height = array[i, j]
        left = array[i, :j][::-1]
        right = array[i, j+1:]
        up = array[:i, j][::-1]
        down = array[i+1:, j]
        score[i, j] = (score_view(left, height)
                       * score_view(right, height)
                       * score_view(up, height)
                       * score_view(down, height))

print(score.max())


def rrechelon(mat,copy,obj,rrechelon):
    from ..C_funcs.linalg import Crrechelon

    res = Crrechelon(copy,mat._cMat,mat.dim,rrechelon)

    if mat._cMat:
        dt = complex
    else:
        dt = float

    return (obj(mat.dim[:],res[0],dtype=dt,implicit=True),res[1])


"""Module that contains all Player classes, vars and functions."""

from board import Board
import constants
import functions
import validation

class Player(object):
    """Define the player class."""

    def add_to_ship_list(self, orientation, coordinates, ship_length):
        """Add player ships to list of ships on board.

        Get column number via the index of VALID_LETTERS
        which are equal to the string 'abcefghij'

        If ship is horizontal add ship coordinates by
        incrementing the column value which is a letter

        If ship is vertical add ship incrementing the row
        value which is an integer
        """
        column = constants.VALID_LETTERS.index(coordinates[0])

        if orientation == 'h':
            row = coordinates[1:]
            columns = constants.VALID_LETTERS[column:(column + ship_length)]

            for column in columns:
                self.ships_list \
                    .append(constants.VALID_LETTERS[constants.VALID_LETTERS
                            .index(column)] + row)

        if orientation == 'v':
            row = int(coordinates[1:])
            rows = list(range(row, row + ship_length))

            for row in rows:
                self.ships_list \
                    .append(constants.VALID_LETTERS[column] + str(row))

    def place_ships(self, ships):
        """Place user ships on board object.

        This method allowing players to run through a list of ships
        passed in and place each one simultaneously updating the players board.

        For all ships in constants.SHIP_INFO as user for coordinates and
        orientation and feed that info to the board's update method to place
        the ship on the players board.
        """
        for ship_name, ship_length in ships:

            while True:
                coordinates = functions \
                    .get_ship_coordinates(ship_name, ship_length,
                                          self.name, self.board)

                orientation = functions.get_ship_orientation(self.board)

                ship = {
                    "orientation":  orientation,
                    "coordinates": coordinates,
                    "ship_length": ship_length
                    }

                if validation.collision(self.board, ship_name,
                                        self.board.get_board(), **ship):
                    continue

                if validation.out_of_bounds(self.board, **ship):
                    continue

                break  # if we get here ship placement is valid so break out.

            self.add_to_ship_list(orientation, coordinates, ship_length)
            self.board.update_board(**ship)

            functions.clear_screen()
            self.board.display()

        input('{}, your ships have been placed!\n'
              'Press Enter to continue.'.format(self.name))

    def get_shot(self):
        """Ask player to pick a location to shoot at.

        Validate the shot coordinates and that the player has not
        shot at that location previously, if those conditions are met
        then return the shot.
        """
        shot = input("{}, enter a target location"
                     " on your opponents board: "
                     .format(self.name)).lower().replace(' ', '')

        if validation.are_valid_coordinates(shot, self.board,
                                            self.shots_board):
            if validation.is_valid_shot(shot, self.shot_list):
                self.shot_list.append(shot)
                return shot
            else:
                self.shots_board.display()
                self.board.display()
                print("\nYou've already shot at that location."
                      " Please try again.")
                return self.get_shot()
        else:
            return self.get_shot()

    def shoot(self, other_player):
        """Player method for guessing location of enemy ships.

        Validate coordinates and whether the players shot has hit a ship
        and update the players boards accordingly.
        """
        board = other_player.board.get_board()

        functions.clear_screen()
        self.shots_board.display()
        self.board.display()

        coordinates = self.get_shot()

        column = constants.VALID_LETTERS.index(coordinates[0])
        row = int(coordinates[1:]) - 1

        if validation.are_valid_coordinates(coordinates, self.board,
                                            shots_board=self.shots_board):
            if validation.hit_or_miss(coordinates, board):
                board[row][column] = constants.HIT
                self.shots_board.get_board()[row][column] = constants.HIT
                other_player.ships_list.remove(str(coordinates))
            else:
                board[row][column] = constants.MISS
                self.shots_board.get_board()[row][column] = constants.MISS

        input('Please pass the game to {}, and look away.'
              '\nPress Enter to continue'.format(other_player.name))

    def set_up_board(self, other_player):
        """Method to describe logic governing each player's turn."""
        functions.clear_screen()
        input('Please pass the Board to {}.'
              ' Press Enter to continue'.format(self.name))

        functions.clear_screen()
        input('{} prepare to place your fleet.'
              ' Please have {} look away and press Enter to begin.'
              .format(self.name, other_player))

        self.board.display()
        self.place_ships(constants.SHIP_INFO)

    def __init__(self, player='Player One'):
        """Instantiate name, boards, and lists for player.

        This sets up some default attributes for the player, such as
        a board to place ships on and a board to place shots on, as well
        as the players name and a list of the placement of their ships to
        keep track of whether their fleet has been sunk via another method.
        """
        self.name = input("{}, Please enter your first name:"
                          .format(player)).capitalize()
        self.board = Board()
        self.shots_board = Board()
        self.shot_list = list()
        self.ships_list = list()


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

from __future__ import absolute_import, division, print_function, unicode_literals
import numpy as np
import gdspy
import picwriter.toolkit as tk
from picwriter.components.waveguide import Waveguide

class FullCoupler(tk.Component):
    """Adiabatic Full Cell class.  Design based on asymmetric adiabatic full coupler designs, such as the one reported in 'Integrated Optic Adiabatic Devices on Silicon' by Y. Shani, et al (IEEE Journal of Quantum Electronics, Vol. 27, No. 3 March 1991).

    In this design, Region I is the first half of the input S-bend waveguide where the input waveguides widths taper by +dw and -dw, Region II is the second half of the S-bend waveguide with constant, unbalanced widths, Region III is the coupling region where the waveguides from unbalanced widths to balanced widths to reverse polarity unbalanced widths, Region IV is the fixed width waveguide that curves away from the coupling region, and Region V is the final curve where the waveguides taper back to the regular width specified in the waveguide template.

        Args:
           * **wgt** (WaveguideTemplate):  WaveguideTemplate object
           * **length** (float): Length of the coupling region.
           * **gap** (float): Distance between the two waveguides.
           * **dw** (float): Change in waveguide width.  Top arm tapers to the waveguide width - dw, bottom taper to width - dw.

        Keyword Args:
           * **angle** (float): Angle in radians (between 0 and pi/2) at which the waveguide bends towards the coupling region.  Default=pi/6.
           * **parity** (integer -1 or 1): If -1, mirror-flips the structure so that the input port is actually the *bottom* port.  Default = 1.
           * **port** (tuple): Cartesian coordinate of the input port (AT TOP if parity=1, AT BOTTOM if parity=-1).  Defaults to (0,0).
           * **direction** (string): Direction that the component will point *towards*, can be of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, OR an angle (float, in radians).  Defaults to 'EAST'.

        Members:
           * **portlist** (dict): Dictionary with the relevant port information

        Portlist format:
           * portlist['input_top'] = {'port': (x1,y1), 'direction': 'dir1'}
           * portlist['input_bot'] = {'port': (x2,y2), 'direction': 'dir1'}
           * portlist['output_top'] = {'port': (x3, y3), 'direction': 'dir3'}
           * portlist['output_bot'] = {'port': (x4, y4), 'direction': 'dir4'}

        Where in the above (x1,y1) (or (x2,y2) if parity=-1) is the same as the input 'port', (x3, y3), and (x4, y4) are the two output port locations.  Directions 'dir1', 'dir2', etc. are of type `'NORTH'`, `'WEST'`, `'SOUTH'`, `'EAST'`, *or* an angle in *radians*.
        'Direction' points *towards* the waveguide that will connect to it.

    """

    def __init__(
        self,
        wgt,
        length,
        gap,
        dw,
        angle=np.pi / 6.0,
        parity=1,
        port=(0, 0),
        direction="EAST",
    ):
        tk.Component.__init__(self, "FullCoupler", locals())

        self.portlist = {}

        self.port = port
        self.direction = direction

        if angle > np.pi / 2.0 or angle < 0:
            raise ValueError(
                "Warning! Improper angle specified ("
                + str(angle)
                + ").  Must be between 0 and pi/2.0."
            )
        self.angle = angle
        if parity != 1 and parity != -1:
            raise ValueError(
                "Warning!  Parity input *must* be 1 or -1.  Received parity="
                + str(parity)
                + " instead."
            )
        self.parity = parity
        self.length = length
        self.gap = gap
        self.dw = dw
        self.wgt = wgt
        self.wg_spec = {"layer": wgt.wg_layer, "datatype": wgt.wg_datatype}
        self.clad_spec = {"layer": wgt.clad_layer, "datatype": wgt.clad_datatype}

        self.__build_cell()
        self.__build_ports()

        """ Translate & rotate the ports corresponding to this specific component object
        """
        self._auto_transform_()

    def __build_cell(self):
        # Sequentially build all the geometric shapes using gdspy path functions
        # for waveguide, then add it to the Cell
        p = self.parity
        angle_x_dist = 2 * self.wgt.bend_radius * np.sin(self.angle)

        angle_y_dist = 2 * self.wgt.bend_radius * (1 - np.cos(self.angle))
        distx = 2 * angle_x_dist + self.length
        disty = p * (2 * abs(angle_y_dist) + self.gap + self.wgt.wg_width)

        x0, y0 = 0, 0  # shift to port location after rotation later

        """ Build the adiabatic DC from gdspy Path derivatives """
        """ First the top waveguide """
        wg_top = gdspy.Path(self.wgt.wg_width, (x0, y0))
        wg_top.turn(
            self.wgt.bend_radius,
            -p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            final_width=self.wgt.wg_width + self.dw,
            **self.wg_spec
        )
        wg_top.turn(
            self.wgt.bend_radius,
            p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            **self.wg_spec
        )
        wg_top.segment(
            self.length, final_width=self.wgt.wg_width - self.dw, **self.wg_spec
        )
        wg_top.turn(
            self.wgt.bend_radius,
            p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            **self.wg_spec
        )
        wg_top.turn(
            self.wgt.bend_radius,
            -p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            final_width=self.wgt.wg_width,
            **self.wg_spec
        )

        wg_top_clad = gdspy.Path(2 * self.wgt.clad_width + self.wgt.wg_width, (x0, y0))
        wg_top_clad.turn(
            self.wgt.bend_radius,
            -p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            **self.clad_spec
        )
        wg_top_clad.turn(
            self.wgt.bend_radius,
            p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            final_width=self.wgt.wg_width + 2 * self.wgt.clad_width,
            **self.clad_spec
        )
        wg_top_clad.segment(self.length, **self.clad_spec)
        wg_top_clad.turn(
            self.wgt.bend_radius,
            p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            final_width=self.wgt.wg_width + 2 * self.wgt.clad_width,
            **self.clad_spec
        )
        wg_top_clad.turn(
            self.wgt.bend_radius,
            -p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            **self.clad_spec
        )

        """ Next, the bottom waveguide """
        x1, y1 = 0, -disty
        wg_bot = gdspy.Path(self.wgt.wg_width, (x1, y1))
        wg_bot.turn(
            self.wgt.bend_radius,
            +p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            final_width=self.wgt.wg_width - self.dw,
            **self.wg_spec
        )
        wg_bot.turn(
            self.wgt.bend_radius,
            -p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            **self.wg_spec
        )
        wg_bot.segment(
            self.length, final_width=self.wgt.wg_width + self.dw, **self.wg_spec
        )
        wg_bot.turn(
            self.wgt.bend_radius,
            -p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            **self.wg_spec
        )
        wg_bot.turn(
            self.wgt.bend_radius,
            +p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            final_width=self.wgt.wg_width,
            **self.wg_spec
        )

        wg_bot_clad = gdspy.Path(2 * self.wgt.clad_width + self.wgt.wg_width, (x1, y1))
        wg_bot_clad.turn(
            self.wgt.bend_radius,
            +p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            **self.clad_spec
        )
        wg_bot_clad.turn(
            self.wgt.bend_radius,
            -p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            final_width=self.wgt.wg_width + 2 * self.wgt.clad_width,
            **self.clad_spec
        )
        wg_bot_clad.segment(self.length, **self.clad_spec)
        wg_bot_clad.turn(
            self.wgt.bend_radius,
            -p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            final_width=self.wgt.wg_width + 2 * self.wgt.clad_width,
            **self.clad_spec
        )
        wg_bot_clad.turn(
            self.wgt.bend_radius,
            +p * self.angle,
            number_of_points=self.wgt.get_num_points_wg(self.angle),
            **self.clad_spec
        )

        self.portlist_input_straight = (0, 0)
        self.portlist_output_straight = (distx, 0)
        self.portlist_output_cross = (distx, -disty)
        self.portlist_input_cross = (0, -disty)

        self.add(wg_top)
        self.add(wg_bot)
        self.add(wg_top_clad)
        self.add(wg_bot_clad)

    def __build_ports(self):
        # Portlist format:
        # example: example:  {'port':(x_position, y_position), 'direction': 'NORTH'}
        if self.parity == 1:
            self.portlist["input_top"] = {
                "port": self.portlist_input_straight,
                "direction": "WEST",
            }
            self.portlist["input_bot"] = {
                "port": self.portlist_input_cross,
                "direction": "WEST",
            }
            self.portlist["output_top"] = {
                "port": self.portlist_output_straight,
                "direction": "EAST",
            }
            self.portlist["output_bot"] = {
                "port": self.portlist_output_cross,
                "direction": "EAST",
            }
        elif self.parity == -1:
            self.portlist["input_top"] = {
                "port": self.portlist_input_cross,
                "direction": "WEST",
            }
            self.portlist["input_bot"] = {
                "port": self.portlist_input_straight,
                "direction": "WEST",
            }
            self.portlist["output_top"] = {
                "port": self.portlist_output_cross,
                "direction": "EAST",
            }
            self.portlist["output_bot"] = {
                "port": self.portlist_output_straight,
                "direction": "EAST",
            }

if __name__ == "__main__":
    from . import *
    from picwriter.components.waveguide import WaveguideTemplate

    top = gdspy.Cell("top")
    wgt = WaveguideTemplate(wg_width=2.0, bend_radius=100, resist="+")

    wg1 = Waveguide([(0, 0), (100, 40)], wgt)
    tk.add(top, wg1)

    fc = FullCoupler(
        wgt, 40.0, 0.5, 1.0, angle=np.pi / 12.0, parity=1, **wg1.portlist["output"]
    )
    tk.add(top, fc)
    for p in fc.portlist.keys():
        print(str(p) + ": " + str(fc.portlist[p]["port"]))

    gdspy.LayoutViewer()
#    gdspy.write_gds('fc.gds', unit=1.0e-6, precision=1.0e-9)


from django.contrib.gis.db.models import PointField

from location_field.forms import spatial as forms
from location_field.models.base import BaseLocationField

class LocationField(BaseLocationField, PointField):
    formfield_class = forms.LocationField

    def __init__(self, *args, **kwargs):
        super(LocationField, self).__init__(*args, **kwargs)

        kwargs.pop("based_fields", None)
        kwargs.pop("zoom", None)
        kwargs.pop("suffix", None)

        PointField.__init__(self, *args, **kwargs)


from matplotlib import pyplot
import numpy
import scipy.stats
import seaborn
import pickle

import prob_spline
import test_common
import pandas as pd
import pickle
import joblib
from time import gmtime, strftime

def parallel_splines(to_be_run,file_name,N,Mos_Class=0,sigma=0,sample=0,combine_index=[],remove_index=[]):
	if Mos_Class==0:
		if N==1:
			output = to_be_run(file_name,sigma=sigma,sample=sample,combine_index=combine_index,remove_index=remove_index,seed=1)
		else:

			print('Start Time')
			print(strftime("%Y-%m-%d %H:%M:%S", gmtime()))
			with joblib.Parallel(n_jobs=-1) as parallel:
				output = parallel(joblib.delayed(to_be_run)(file_name,sigma=sigma,sample=sample,combine_index=combine_index,remove_index=remove_index,
					seed=j+1) for j in range(N))
			print('Finish Time')
			print(strftime("%Y-%m-%d %H:%M:%S", gmtime()))

	else:		#change function to take *kargs?
		if N==1:
			output = to_be_run(file_name,Mos_Class,sigma=sigma,sample=sample)
		else:
			print('Start Time')
			print(strftime("%Y-%m-%d %H:%M:%S", gmtime()))
			with joblib.Parallel(n_jobs=-1) as parallel:
				output = parallel(joblib.delayed(to_be_run)(file_name,Mos_Class,sigma=sigma,sample=sample) for j in range(N))

			print('Finish Time')
			print(strftime("%Y-%m-%d %H:%M:%S", gmtime()))
	return(output)

def get_splines(bc_file,bm_file,bc_sigma,bm_sigma,N,combine_index,remove_index):
	bc_splines = parallel_splines(prob_spline.HostSpline,bc_file,N,sigma=bc_sigma,sample=1,combine_index=combine_index,remove_index=remove_index)
	bm_splines = parallel_splines(prob_spline.BloodmealSpline,bm_file,N,sigma = bm_sigma,sample=1,combine_index=combine_index,remove_index=remove_index)
	if combine_index!=[]:
		with open('host_splines_sample_combine_index=%s.pkl' %str(combine_index), 'wb') as output:
			pickle.dump(bc_splines,output)
		with open('vectors_splines_sample_combine_index=%s.pkl' %str(combine_index), 'wb') as output:
			pickle.dump(bm_splines,output)
	elif remove_index!=[]:
		with open('host_splines_sample_remove_index=%s.pkl' %str(remove_index), 'wb') as output:
			pickle.dump(bc_splines,output)
		with open('vectors_splines_sample_remove_index=%s.pkl' %str(remove_index), 'wb') as output:
			pickle.dump(bm_splines,output)
	return()

def generate_splines_fun(combine_index=[],remove_index=[]):
	bc_file = "Days_BirdCounts.csv"
	msq_file = "Vector_Data(NoZeros).csv"
	bm_file = "Days_BloodMeal.csv"
	bc_sigma = 0		# for testing purposes
	#bc_sigma = pickle.load(open('Sigma_Vals_Cross_Validation_1mil_iter.pkl','rb'))
	#bm_sigma = 0.199
	bm_sigma= 0

	MosClass = prob_spline.MosConstant

	N = 2 # number of samples to be generated
	if not isinstance(bc_sigma,int):
		if combine_index != []:
			for j in combine_index:
				if j!=6:
					bc_sigma.pop(j)
		elif remove_index != []:
			bc_sigma.pop(remove_index)
	get_splines(bc_file,bm_file,bc_sigma,bm_sigma,N,combine_index,remove_index)
	#mos_curve = parallel_splines(prob_spline.MosCurve,msq_file,N,Mos_Class = prob_spline.MosConstant,sample=1)
	#with open('mos_curve_sample.pkl', 'wb') as output:
	#	pickle.dump(mos_curve,output)
	return()

if __name__ == '__main__':
	remove_index = [0,1,2,3,4,5,6]
	for x in remove_index:
		generate_splines_fun(remove_index=x)


import logging
from flask import Flask
from flask_cors import CORS
from flask.logging import default_handler
from logging.handlers import RotatingFileHandler

app = Flask(__name__)
CORS(app)
app.config.from_pyfile('config.cfg')

app.logger.removeHandler(default_handler)

log_path = app.config["LOG_PATH"]
log_format = "%(asctime)s | Database API | %(levelname)s | %(message)s"
formatter = logging.Formatter(log_format)
logger = logging.getLogger()

file_handler = RotatingFileHandler(log_path, maxBytes=5242880, backupCount=5)
file_handler.setFormatter(formatter)
app.logger.addHandler(file_handler)

console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
app.logger.addHandler(console_handler)

app.logger.setLevel(logging.DEBUG)

from dbapi.handlers import login_handler, logout_handler, status_handler


def seq3np1(n):
	""" Print the 3n+1 sequence from n, terminating when it reaches 1."""

	while n != 1:
		print(n)
		if n % 2 == 0:		# n is even
			n = n // 2
		else:
			n = n * 3 + 1	# n is odd

	print(n)				# the last print 1

seq3np1(3)

# The condition for this loop is n != 1. The loop will continue running until n == 1 (which will make the condition false)


def get_input():
    with open("data/day11.txt") as f:
        data = [move for move in f.readline().strip("\n").split(",")]
    return data

def find_distance(moves):
    steps_north = 0
    steps_east = 0
    move_interpretations = {"n": [2, 0], "ne": [1, 1], "se": [-1, 1], "s": [-2, 0], "sw": [-1, -1], "nw": [1, -1]}
    for move in moves:
        step = move_interpretations[move]
        steps_north += step[0]
        steps_east += step[1]

    return distance_from_point(steps_north, steps_east)

def distance_from_point(north, east):
    shortest_steps_vertical = 0
    shortest_steps_horizontal = 0
    while shortest_steps_horizontal < abs(east):
        shortest_steps_horizontal += 1
        if shortest_steps_vertical < abs(north):
            shortest_steps_vertical += 1
        else:
            shortest_steps_vertical -= 1

    assert (abs(north) - shortest_steps_vertical) % 2 == 0

    return shortest_steps_horizontal + (abs(north) - shortest_steps_vertical) // 2

def find_max_distance(moves):
    steps_north = 0
    steps_east = 0
    move_interpretations = {"n": [2, 0], "ne": [1, 1], "se": [-1, 1], "s": [-2, 0], "sw": [-1, -1], "nw": [1, -1]}
    maximum_distance = 0
    for move in moves:
        step = move_interpretations[move]
        steps_north += step[0]
        steps_east += step[1]
        current_distance = distance_from_point(steps_north, steps_east)
        if current_distance > maximum_distance:
            maximum_distance = current_distance

    return maximum_distance

if __name__ == "__main__":
    movements = get_input()
    print(find_max_distance(movements))


import unittest
from sql import Validate, Parser, QueryBuilder

class CheckTests(unittest.TestCase):
  def test_fail_on_sql_restricted_keywords(self):
    keywords = ['select', 'Delete', 'fRom', 'join', 'wherE', 'ORDER BY', 'GROUP BY']
    for k in keywords:
      self.assertRaises(AssertionError, Validate.check_not_keywords, k)

  def test_fail_on_improperly_formatted_func_names(self):
    name_with_semicolon = 'sum;'
    name_with_nesting_func = 'sum(cat(val))'
    name_with_long_tail = 'bin(grouping, 10); delete blah'
    name_with_keyword = 'sum(select, jon)'
    self.assertRaises(AssertionError, Validate.check_func, name_with_semicolon)
    self.assertRaises(AssertionError, Validate.check_func, name_with_nesting_func)
    self.assertRaises(AssertionError, Validate.check_func, name_with_long_tail)
    self.assertRaises(AssertionError, Validate.check_func, name_with_keyword)

  def test_succeed_on_func_names(self):
    name_with_parens = 'bin(grouping, 10)'
    name_with_multiple_params = 'sum(col1, col2, col3)'
    Validate.check_func(name_with_parens)
    Validate.check_func(name_with_multiple_params)

class ValidateTests(unittest.TestCase):
  def test_fail_on_bad_word(self):
    word_with_semicolon = 'asdf;'
    word_with_bad_func_name = 'bin(grouping, select)'
    self.assertRaises(AssertionError, Validate.word, word_with_semicolon)
    self.assertRaises(AssertionError, Validate.word, word_with_bad_func_name)

  def test_fail_on_list_of_bad_words(self):
    lst_with_one_bad = ['asdf;', 'bin(grouping, 10)']
    self.assertRaises(AssertionError, Validate.list_of_words, lst_with_one_bad)

  def test_fail_on_bad_num(self):
    not_num = u'asd2'
    self.assertRaises(AssertionError, Validate.num, not_num)

  def test_succeed_on_word(self):
    word_with_func_name = 'asdf(jon, doe)'
    word_normal = 'hellothisisyourmaster'
    Validate.word(word_with_func_name)
    Validate.word(word_normal)

  def test_succeed_on_num(self):
    num_in_unicode = u'1337'
    num_in_str = '1337'
    num_normal = 1337
    Validate.num(num_in_unicode)
    Validate.num(num_in_str)
    Validate.num(num_normal)

class ParserTests(unittest.TestCase):
  def test_succeed_on_parse_select(self):
    select_as_str = 'group'
    select_as_lst = ['darth', 'vader']
    self.assertEqual(Parser.parse_select(select_as_str), 'group')
    self.assertEqual(Parser.parse_select(select_as_lst), 'darth, vader')

  def test_fail_on_parse_select(self):
    select_as_invalid_keyword = 'select'
    select_as_invalid_lst = ['bin(group, ', 'jondoe']
    self.assertRaises(AssertionError, Parser.parse_select, select_as_invalid_keyword)
    self.assertRaises(AssertionError, Parser.parse_select, select_as_invalid_lst)

  def test_succeed_on_parse_where(self):
    filter_with_inequalities = {'val1': { 'ge': 8, 'le': 17 }}
    filter_with_strict_inequalities = { 'val2': { 'lt': 9, 'gt':2} }
    filter_with_in = { 'color': {'in': ['blue', 'red']}}
    self.assertEqual(Parser.parse_where(filter_with_inequalities), [('val1', '>=', 8), ('val1', '<=', 17)])
    self.assertEqual(Parser.parse_where(filter_with_strict_inequalities), [('val2', '<', 9), ('val2', '>', 2)])
    self.assertEqual(Parser.parse_where(filter_with_in), [('color', 'in', ['blue', 'red'])])

  def test_fail_on_parse_where(self):
    filter_with_non_numerical_inequalities = { 'hour': {'ge': 'somenum'}}
    filter_with_invalid_qualifier = { 'hour': { 'blap': '3'} }
    self.assertRaises(AssertionError, Parser.parse_where, filter_with_non_numerical_inequalities)
    self.assertRaises(NameError, Parser.parse_where, filter_with_invalid_qualifier)

  def test_succeed_on_parse_group(self):
    # Does same thing as parse_select
    group_as_str = 'group'
    group_as_lst = ['darth', 'vader']
    self.assertEqual(Parser.parse_group(group_as_str), 'group')
    self.assertEqual(Parser.parse_group(group_as_lst), 'darth, vader')

  def test_succeed_on_parse_order(self):
    order_asc = {u'sort': u'sum(val1)', u'asc': True }
    order_non_specicied_asc = {u'sort': u'sum(val1)' }
    self.assertEqual(Parser.parse_order(order_asc), ('sum(val1)', 'ASC'))
    self.assertEqual(Parser.parse_order(order_non_specicied_asc), ('sum(val1)', 'DESC'))

  def test_fail_on_parse_order(self):
    order_without_sort = { 'limit': 2}
    self.assertRaises(AssertionError, Parser.parse_order, order_without_sort)

class QueryBuilderTests(unittest.TestCase):
  def test_succeed_build_sort_query(self):
    table = 'example1'
    spec = {u'filter': {}, u'meta': {u'grp': {u'sort': u'sum(val1)', u'asc': True, u'stat': {u'stat': u'sum', u'name': u'sum(val1)', u'key': u'val1'}}}, u'trans': [], u'stats': {u'stats': [{u'stat': u'sum', u'name': u'sum(val1)', u'key': u'val1'}], u'groups': [u'grp']}, u'select': [u'grp', u'sum(val1)']}
    query, params = QueryBuilder.build_sort_query(table, spec)
    self.assertEqual(query, 'SELECT grp FROM example1 GROUP BY grp ORDER BY ? ASC ')
    self.assertEqual(params, [u'sum(val1)'])

  def test_succeed_build_calc_query(self):
    table = 'example1'
    limit = 1000
    spec = {u'filter': {}, u'meta': {u'grp': {u'sort': u'sum(val1)', u'asc': True, u'stat': {u'stat': u'sum', u'name': u'sum(val1)', u'key': u'val1'}}}, u'trans': [], u'stats': {u'stats': [{u'stat': u'sum', u'name': u'sum(val1)', u'key': u'val1'}], u'groups': [u'grp']}, u'select': [u'grp', u'sum(val1)']}
    result = [(u'A',), (u'B',)]
    query, params = QueryBuilder.build_calc_query(table, spec, limit, result)
    self.assertEqual(query, "SELECT grp, sum(val1) FROM example1 WHERE grp in (?,?) GROUP BY grp LIMIT ?")
    self.assertEqual(params, [u'A', u'B', '1000'])

if __name__ == '__main__':
    unittest.main()


#Faça um programa que calcule e mostre a área de um trapézio. Sabe-se que: A = ((base maior + base menor) * altura) / 2
#Lembre-se a base maior e a base menor devem ser números maiores que zero

bmaior = float(input('Digite o valor da base maior: '))
bmenor = float(input('Digite o valor da base menor: '))
alt = float(input('Digite o valor da altura: '))

if bmaior > 0 and bmenor > 0:
    area = ((bmaior + bmenor) * alt) / 2
    print(f'A área do trapézio é: {area}')
else:
    print('A base deve ser maior que 0')

Estrutura lógica e Condicional/Exercícios/Ex17.py