from OpenGL.GL import *
from OpenGL.GL import shaders

import numpy as np
import glm
from meshes import geometry
import jax
import jax.numpy as jnp

class Water:
    def __init__(self, n: int, m: int, cell_width: float) -> None:
        self._n = n
        self._m = m
        self._cell_width = cell_width
        vertices, normals, indices = geometry.grid_vertices_normals_and_indices(
            n, m, cell_width
        )
        self._n_vertices = len(vertices)
        vertex_data = []
        normal_data = []
        for vertex, normal in zip(vertices, normals):
            vertex_data.extend(vertex)
            normal_data.extend(normal)
        self._xz = np.array(vertex_data, dtype=np.float32)
        vertex_data = glm.array(np.array(vertex_data, dtype=np.float32))
        normal_data = glm.array(np.array(normal_data, dtype=np.float32))

        self._normal_vbo = self._init_normal_vbo(normal_data)
        self._vbo = self._init_vbo(vertex_data)
        self._indices = glm.array(indices, dtype=glm.uint32)
        self._ebo = self._init_ebo(self._indices)

        self._height_vbo = self._init_model_y_vbo()

        self._vao = self._init_vao(
            self._vbo,
            self._height_vbo,
            self._normal_vbo,
            self._ebo,
        )

        self._shader = self._init_shader(self._vao)
        glBindVertexArray(0)

        self._vertex_normal_updater = jax.jit(
            WaterVertexNormalUpdater(n, m, self._xz, self._indices)
        )

    def _init_vbo(self, vertex_data: glm.array) -> GLint:
        vbo = glGenBuffers(1)
        try:
            glBindBuffer(GL_ARRAY_BUFFER, vbo)
            glBufferData(
                GL_ARRAY_BUFFER,
                glm.sizeof(vertex_data),
                vertex_data.ptr,
                GL_STATIC_DRAW,
            )
        except Exception as exception:
            glDeleteBuffers(1, vbo)
            raise exception
        return vbo

    def _init_model_y_vbo(self) -> GLint:
        vbo = glGenBuffers(1)
        try:
            glBindBuffer(GL_ARRAY_BUFFER, vbo)
            glBufferData(
                GL_ARRAY_BUFFER,
                self._n_vertices * glm.sizeof(glm.float32),
                None,
                GL_DYNAMIC_DRAW,
            )
        except Exception as exception:
            glDeleteBuffers(1, vbo)
            raise exception
        return vbo

    def _init_normal_vbo(self, normal_data: glm.array) -> GLint:
        vbo = glGenBuffers(1)
        try:
            glBindBuffer(GL_ARRAY_BUFFER, vbo)
            glBufferData(
                GL_ARRAY_BUFFER,
                glm.sizeof(normal_data),
                normal_data.ptr,
                GL_DYNAMIC_DRAW,
            )
        except Exception as exception:
            glDeleteBuffers(1, vbo)
            raise exception
        return vbo

    def _init_ebo(self, indices: glm.array) -> GLint:
        self._len_ebo = len(indices)
        ebo = glGenBuffers(1)
        try:
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
            glBufferData(
                GL_ELEMENT_ARRAY_BUFFER,
                glm.sizeof(indices),
                indices.ptr,
                GL_STATIC_DRAW,
            )
        except Exception as exception:
            glDeleteBuffers(1, ebo)
            raise exception
        return ebo

    def _init_vao(
        self,
        vbo: GLint,
        height_vbo: GLint,
        normal_vbo: GLint,
        ebo: GLint,
    ) -> GLint:
        vao = glGenVertexArrays(1)
        try:
            glBindVertexArray(vao)
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)

            # Contains X and Z coordinates.
            glBindBuffer(GL_ARRAY_BUFFER, vbo)
            glVertexAttribPointer(
                0,
                2,
                GL_FLOAT,
                GL_FALSE,
                2 * np.float32(0.0).itemsize,
                None,
            )
            glEnableVertexAttribArray(0)

            glBindBuffer(GL_ARRAY_BUFFER, normal_vbo)
            glVertexAttribPointer(
                1,
                3,
                GL_FLOAT,
                GL_FALSE,
                3 * np.float32(0.0).itemsize,
                None,
            )
            glEnableVertexAttribArray(1)

            # Contains Y coordinates.
            glBindBuffer(GL_ARRAY_BUFFER, height_vbo)
            glVertexAttribPointer(
                2,
                1,
                GL_FLOAT,
                GL_FALSE,
                np.float32(0.0).itemsize,
                None,
            )
            glEnableVertexAttribArray(2)
        except Exception as exception:
            glDeleteVertexArrays(1, vao)
            raise exception
        return vao

    def _init_shader(self, vao: GLint) -> GLint:
        with open("shaders/basic_lighting.vs", "r") as file:
            vertex_shader_source = file.read()
            light_cube_vertex_shader = shaders.compileShader(
                vertex_shader_source, GL_VERTEX_SHADER
            )
        try:
            with open("shaders/basic_lighting.fs", "r") as file:
                fragment_shader_source = file.read()
                light_cube_fragment_shader = shaders.compileShader(
                    fragment_shader_source, GL_FRAGMENT_SHADER
                )
            try:
                glBindVertexArray(vao)
                light_cube_shader = shaders.compileProgram(
                    light_cube_vertex_shader, light_cube_fragment_shader, validate=True
                )
            except Exception as exception:
                glDeleteShader(light_cube_fragment_shader)
                raise exception
        except Exception as exception:
            glDeleteShader(light_cube_vertex_shader)
            raise exception
        return light_cube_shader

    def set_light_color(self, light_color: glm.vec3) -> None:
        self._light_color = light_color
        glUseProgram(self._shader)
        glUniform3f(
            glGetUniformLocation(self._shader, "lightColor"),
            light_color.x,
            light_color.y,
            light_color.z,
        )

    def set_texture(self, texture: int) -> None:
        glUseProgram(self._shader)
        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, texture)
        glUniform1i(glGetUniformLocation(self._shader, "background"), 0)

    def set_view_position(self, view_position: glm.vec3) -> None:
        self._view_position = view_position
        glUseProgram(self._shader)
        glUniform3f(
            glGetUniformLocation(self._shader, "viewPos"),
            view_position.x,
            view_position.y,
            view_position.z,
        )

    def set_view(self, view: glm.mat4) -> None:
        self._view = view
        glUseProgram(self._shader)
        glUniformMatrix4fv(
            glGetUniformLocation(self._shader, "view"),
            1,
            GL_FALSE,
            glm.value_ptr(view),
        )

    def set_projection(self, projection: glm.mat4) -> None:
        self._projection = projection
        glUseProgram(self._shader)
        glUniformMatrix4fv(
            glGetUniformLocation(self._shader, "projection"),
            1,
            GL_FALSE,
            glm.value_ptr(projection),
        )

    def set_light_position(self, light_position: glm.vec3) -> None:
        self._light_position = light_position
        glUseProgram(self._shader)
        glUniform3f(
            glGetUniformLocation(self._shader, "lightPos"),
            light_position.x,
            light_position.y,
            light_position.z,
        )

    def set_water_heights(self, water_heights: glm.array) -> None:
        self._water_heights = water_heights
        if len(water_heights) != self._n_vertices:
            raise ValueError(
                f"Water heights array must have {self._n_vertices} elements."
            )
        # else...
        glBindBuffer(GL_ARRAY_BUFFER, self._height_vbo)
        glBufferSubData(
            GL_ARRAY_BUFFER,
            0,
            glm.sizeof(water_heights),
            water_heights.ptr,
        )

        np_water_heights = np.array(water_heights)
        arr = self._vertex_normal_updater(np_water_heights)
        water_normals = glm.array(np.asarray(arr))
        glBindBuffer(GL_ARRAY_BUFFER, self._normal_vbo)
        glBufferSubData(
            GL_ARRAY_BUFFER,
            0,
            glm.sizeof(water_normals),
            water_normals.ptr,
        )

    def draw(self) -> None:
        glUseProgram(self._shader)
        glBindVertexArray(self._vao)
        glDrawElements(GL_TRIANGLES, self._len_ebo, GL_UNSIGNED_INT, None)

    def __del__(self) -> None:
        glDeleteProgram(self._shader)
        glDeleteVertexArrays(1, self._vao)
        glDeleteBuffers(1, self._height_vbo)
        glDeleteBuffers(1, self._vbo)
        glDeleteBuffers(1, self._normal_vbo)
        glDeleteBuffers(1, self._ebo)

class WaterVertexNormalUpdater:
    def __init__(self, n: int, m: int, xz: jax.Array, indices: jax.Array) -> None:
        self._n = n
        self._m = m
        self._n_faces = (self._n - 1) * (self._m - 1) * 2
        self._xz = jnp.reshape(xz, (self._n * self._m, 2))
        self._indices = jnp.array(indices)
        (
            self._element_index_map,
            self._face_indices,
            self._vertex_normal_groups_size,
        ) = self._prepare()

    def __call__(self, water_heights: jax.Array) -> jax.Array:
        return self._compute(water_heights)

    def _prepare(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
        vertex_normal_groups_size = np.zeros((self._n * self._m,), dtype=np.int32)
        element_index_map = np.zeros((self._n_faces * 3,), dtype=np.int32)
        face_indices = self._indices.reshape(self._n_faces, 3)

        for i, vertex_index in enumerate(self._indices):
            element_index_map[i] = vertex_normal_groups_size[vertex_index]
            vertex_normal_groups_size[vertex_index] += 1

        return (
            element_index_map.reshape(-1, 3),
            face_indices,
            vertex_normal_groups_size,
        )

    def _compute(self, water_heights: jax.Array) -> jax.Array:
        face_normals = self._calculate_face_normals(water_heights)
        vertex_normal_groups = self._vertex_face_normals(face_normals)
        vertex_normals = jnp.divide(
            jnp.sum(vertex_normal_groups, axis=1),
            self._vertex_normal_groups_size[:, None],
        )
        return vertex_normals.flatten()

    def _vertex_face_normals(self, face_normals: jax.Array) -> jax.Array:
        vertex_normal_groups = jnp.zeros(
            (self._n * self._m, max(self._vertex_normal_groups_size), 3),
            dtype=jnp.float32,
        )
        vertex_normal_groups = vertex_normal_groups.at[
            self._face_indices, self._element_index_map
        ].set(face_normals[:, None, :], unique_indices=True)
        return vertex_normal_groups

    def _calculate_face_normals(self, water_heights: jax.Array) -> jax.Array:
        faces_vertices = jnp.concatenate(
            (
                self._xz[self._face_indices, 0][:, :, None],
                water_heights[self._face_indices][:, :, None],
                self._xz[self._face_indices, 1][:, :, None],
            ),
            axis=2,
        )
        normals = jnp.cross(
            faces_vertices[:, 1, :] - faces_vertices[:, 0, :],
            faces_vertices[:, 2, :] - faces_vertices[:, 0, :],
        )
        normals /= jnp.linalg.norm(normals, axis=1)[:, None]
        return normals


import cv2
import numpy as np
import os

if __name__ == '__main__':
    path = '/media/palm/data/7/data1-30-9-gs/data1/300'
    gt = [cv2.imread(os.path.join(path, f'{x}.jpg')) for x in [1, 2, 3]]
    for imid in range(12):
        image = [cv2.imread(os.path.join(path, f'{x}.jpg')) for x in [1+(1+imid)*3, 2+(1+imid)*3, 3+(1+imid)*3]]
        for idx in range(3):
            diff = np.abs(cv2.cvtColor(gt[idx], cv2.COLOR_BGR2GRAY) - cv2.cvtColor(image[idx], cv2.COLOR_BGR2GRAY))
            mask = cv2.threshold(diff, 128, 255, cv2.THRESH_BINARY)[1]
            mask = cv2.dilate(mask, None, iterations=1)
            mask[:350] = 255
            # mask[:, :70] = 255
            # mask[:, 400:] = 255
            contours, hierarchy = cv2.findContours(mask.astype('uint8'), mode=cv2.RETR_LIST, method=cv2.CHAIN_APPROX_SIMPLE)

            contours_poly = [None] * len(contours)
            boundRect = [None] * len(contours)
            pt = [float('inf'), float('inf'), -1, -1]  # x, y, width, height
            for i, c in enumerate(contours):
                contours_poly[i] = cv2.approxPolyDP(c, 3, True)
                boundRect[i] = cv2.boundingRect(contours_poly[i])

                if boundRect[i][0] == 0 or boundRect[i][2] >= 400:
                    continue
                if pt[0] > boundRect[i][0]:
                    pt[0] = boundRect[i][0]
                if pt[1] > boundRect[i][1]:
                    pt[1] = boundRect[i][1]
                if pt[2] < boundRect[i][2] + boundRect[i][0]:
                    pt[2] = boundRect[i][2] + boundRect[i][0]
                if pt[3] < boundRect[i][3] + boundRect[i][1]:
                    pt[3] = boundRect[i][3] + boundRect[i][1]

            # cv2.rectangle(image, (pt[0], pt[1]), (pt[2], pt[3]), (230, 180, 128))
            cv2.rectangle(image[idx], (pt[0], pt[1]), (pt[2], pt[3]), (128, 128, 255), 2)
            cv2.imwrite(f'/home/palm/PycharmProjects/Seven/out/1/{idx+1+(1+imid)*3}.jpg', image[idx])

    # cv2.imshow(f'gt{idx}', gt[idx])
    # cv2.imshow(f'mask{idx}', mask)
    # while 1:
    #     keyboard = cv2.waitKey()
    #     print(keyboard)
    #     if keyboard == 113:
    #         break


from django.urls import path
from . import views

app_name = 'movies'

urlpatterns = [
    path('movies/', views.movies_list, name='movies_list'),
    path('genres/', views.genres_list, name='genres_list'),
    path('genres/<int:genre_pk>/', views.genres_detail, name='genres_detail'),
    path('movies/<int:movie_pk>/', views.movies_detail, name='movies_detail'),
    path('movies/<int:movie_pk>/reviews/', views.reviews_create, name='reviews_create'),
    path('reviews/<int:review_pk>/', views.reviews_update_delete, name='reviews_udpate_delete'),
]


#Drew Childs, 12/2/2019, Program 7
import random
import math

class Bacteria:
    def __init__(self, resistance, health = 10, life_span = 15, birth_counter = 3):     #Initializes stats for bacteria
        self.resistance = resistance + random.randint(-1, 1)
        self.health = health
        self.life_span = life_span
        self.birth_counter = birth_counter
        if self.resistance < 1:             #makes sure resistance is between 1 and 10
            self.resistance = 1
        if self.resistance > 10:
            self.resistance = 10

    def __str__(self):      #returns stats for bacteria in form of string
        return ("H(%0.1f)    R(%d)    LS(%d)    BC(%d)" % (self.health, self.resistance, self.life_span, self.birth_counter))

    def is_alive(self):     #Determines if bacteria is still living
        if self.health > 0 and self.life_span > 0:
            return True
        else:
            return False

    def tick(self):     #Tick function for bacteria life span and birth counter
        self.birth_counter -= 1
        self.life_span -= 1

    def dose(self, dosage):     #applies dosage to bacteria health
        self.health -= dosage * (1/self.resistance)

    def reproduce(self):        #Ensures bacteria can reproduce
        if self.is_alive() == True and self.birth_counter <= 0:
            self.birth_counter = 3
            return Bacteria(self.resistance)

class Host(Bacteria):
    def __init__(self, bacteria_count):
        super().__init__(resistance = 3)        #inherits bacteria attributes
        self.bacteria_stats = []
        for each in range(bacteria_count):          #Creates new bacteria instance for desired amount of bacteria.
            self.bacteria_stats.append(Bacteria(3)) #adds them to list so attributes for individual bacteria can be called upon later

    def __str__(self):
        avg_health = 0
        avg_resistance = 0
        for each in self.bacteria_stats:        #calculating average health and resistance for bacteria group
            avg_health += each.health
            avg_resistance += each.resistance
        try:
            avg_health /= len(self.bacteria_stats)
            avg_resistance /= len(self.bacteria_stats)
        except:
            avg_health = math.nan       #only executes if there are no bacteria
            avg_resistance = math.nan
        return ("Count: %d\nAverage Health: %0.1f\nAverage Resistance: %0.1f" % (len(self.bacteria_stats), avg_health, avg_resistance))     #returns string containing bacteria stats

    def tick_host(self, with_dose = False):
        if with_dose == True:                       #I placed this if statement outside the loop instead of having one for loop so this redundant
            for bacteria in self.bacteria_stats:    #if statement wouldn't run thousands of times for no reason
                bacteria.dose(25)
                bacteria.tick()     #I have this function dose, tick, then reproduce
                new_bacteria = bacteria.reproduce()
                if bacteria.is_alive() == False:        #removes any dead bacteria
                    self.bacteria_stats.remove(bacteria)
                if new_bacteria != None:
                    self.bacteria_stats.append(new_bacteria)
        else:
            for bacteria in self.bacteria_stats:        #Same as above, just doesn't apply dosage
                bacteria.tick()
                new_bacteria = bacteria.reproduce()
                if bacteria.is_alive() == False:
                    self.bacteria_stats.remove(bacteria)
                if new_bacteria != None:
                    self.bacteria_stats.append(new_bacteria)

No_Dosage = Host(1)     #creates initial objects
Full_Dosage = Host(1)
Half_Dosage = Host(1)

for cycle in range(30):     #runs first 30 ticks for starting point for all
    No_Dosage.tick_host()
    Full_Dosage.tick_host()
    Half_Dosage.tick_host()

for dosage in range(15):        #Applies appropriate dosage to objects
    No_Dosage.tick_host()
    Full_Dosage.tick_host(True)
    if dosage % 2 == 0:
        Half_Dosage.tick_host()
    else:
        Half_Dosage.tick_host(True)

print("No Dosage:\n%s\n\nFull Dosage:\n%s\n\nHalf Dosed:\n%s" % (No_Dosage, Full_Dosage, Half_Dosage))  #prints what dosage was applied, then calls __str__ function in host to print stats


"""Sphinx theme for Open Free Energy software based on the PyData theme."""

# Add imports here
from pathlib import Path
from typing import Optional

from sphinx.application import Sphinx
from sphinx.config import Config
import sass
from sass import SassColor

from ._version import __version__

def html_theme_path() -> Path:
    """The path to the theme itself."""
    here = Path(__file__).parent.resolve()
    return here / "theme" / "ofe_sphinx_theme"

def compile_css(app: Sphinx, exception: Optional[Exception]):
    """Compile SASS into CSS"""
    if exception is not None:
        return

    src = html_theme_path() / "sass/ofe-sphinx-theme.scss"
    dest = Path(app.outdir) / "_static/styles/ofe-sphinx-theme.css"

    dest.parent.mkdir(exist_ok=True, parents=True)

    accent_color = app.config["html_theme_options"].get(
        "accent_color", "DarkGoldenYellow"
    )
    accent_color, accent_color_darkmode = {
        "DarkGoldenYellow": (
            "var(--ofe-color-DarkGoldenYellow)",
            "var(--ofe-color-DarkGoldenYellow)",
        ),
        "FeelingSpicy": (
            "var(--ofe-color-FeelingSpicy)",
            "var(--ofe-color-FeelingSpicy-darkmode)",
        ),
        "FeelingSick": (
            "var(--ofe-color-FeelingSick)",
            "var(--ofe-color-FeelingSick-darkmode)"),
        "FeelingFabulous": (
            "var(--ofe-color-FeelingFabulous)",
            "var(--ofe-color-FeelingFabulous-darkmode)",
        ),
        "FeelingBlue": (
            "var(--ofe-color-FeelingBlue)",
            "var(--ofe-color-FeelingBlue-darkmode)"
        ),
    }.get(accent_color, accent_color)

    css = sass.compile(
        filename=str(src),
        output_style="compressed",
        custom_functions={
            "accent_color": lambda: accent_color,
            "accent_color_darkmode": lambda: accent_color_darkmode,
        },
    )

    with open(dest, "w") as f:
        print(css, file=f)

def setup(app: Sphinx):
    """Set up the theme's Sphinx extension"""
    app.add_html_theme("ofe_sphinx_theme", str(html_theme_path()))

    app.connect("build-finished", compile_css)

    # usually this would be done in the `"config-init"` event,
    # but this event runs before themes are loaded
    app.config.templates_path.append(str(html_theme_path() / "templates"))

    return {
        "version": __version__,
        "parallel_read_safe": True,
        "parallel_write_safe": True,
    }


def hasDependencyRecursively(self, name, target=None, test_dependencies=False):
        ''' Check if this module, or any of its dependencies, have a
            dependencies with the specified name in their dependencies, or in
            their targetDependencies corresponding to the specified target.

            Note that if recursive dependencies are not installed, this test
            may return a false-negative.
        '''
        # checking dependencies recursively isn't entirely straightforward, so
        # use the existing method to resolve them all before checking:
        dependencies = self.getDependenciesRecursive(
                               target = target,
                                 test = test_dependencies
        )
        return (name in dependencies)


def handle_list(self, item):
        """Helper method for fetching a list(map) value."""
        doc = yield from self.call('LIST_GET_NEXT/'+item+'/-1', dict(
            maxItems=100,
        ))

        if doc is None:
            return []

        if not doc.status == 'FS_OK':
            return []

        ret = list()
        for index, item in enumerate(list(doc.iterchildren('item'))):
            temp = dict(band=index)
            for field in list(item.iterchildren()):
                temp[field.get('name')] = list(field.iterchildren()).pop()
            ret.append(temp)

        return ret


from lettuce import *
from base import *
import re

def find_pattern(pattern, process, timeout):
    found = False
    lines = []
    for l in process.read_lines(timeout):
        lines.append(l)
        if re.search(pattern, l):
            found = True
            break
    return found, lines

@step(u'an empty configuration')
def empty_configuration(step):
    world.airpnp_config = {}

@step(u'Airpnp is started')
def start_airpnp(step):
    world.start_airpnp()

@step('the log should contain the message "(.*)"')
def see_log_message(step, message):
    pattern = ".*%s.*" % re.escape(message)
    found, lines = find_pattern(pattern, world.airpnp, 10000)
    assert found == True, "Got log lines: %r" % lines

@step(u'an? (.*) with UDN (.*) and name (.*) is running')
def media_renderer_is_running(step, device, udn, name):
    # args are unicode, need to convert to str first!
    device = str(device).replace(" ", "")
    cmd = "python upnpclient.py %s %s %s" % (device, str(udn), str(name))
    world.start_process(cmd)

@step(u'Then an AirPlay service is published with the name (.*)')
def airplay_service_published(step, name):
    browser = world.start_process("avahi-browse -prk _airplay._tcp")
    pattern = "^=.*;%s;" % re.escape(name)
    found, lines = find_pattern(pattern, browser, 10000)
    assert found == True, "Got log lines: %r" % lines
    world.airplay_service_lines = [l for l in lines if l.startswith("=")]

@step(u'And the AirPlay service has features set to (.*)')
def and_the_airplay_service_has_features_set_to_0x77(step, features):
    lines = world.airplay_service_lines
    matches = [l for l in lines if l.find("features=" + features) != -1]
    assert len(matches) > 0

@step(u'And the AirPlay service has model set to (.*)')
def and_the_airplay_service_has_model_set_to_appletv2_1(step, model):
    lines = world.airplay_service_lines
    matches = [l for l in lines if l.find("model=" + model) != -1]
    assert len(matches) > 0

@step(u'Then (.*) AirPlay services with name prefix (.*) are published')
def then_2_airplay_services_are_published(step, count, prefix):
    browser = world.start_process("avahi-browse -prk _airplay._tcp")
    services = []
    for l in browser.read_lines(10000):
        parts = l.split(";")
        if parts[0] == "=" and parts[2] == "IPv4" and parts[3].startswith(prefix):
            services.append(l)
    world.airplay_service_lines = services
    assert len(world.airplay_service_lines) == int(count)

@step(u'And the AirPlay services have different device IDs')
def and_the_airplay_services_have_different_device_ids(step):
    devids = [re.search('"deviceid=([:a-zA-Z0-9]+)"', line).group(1) for line in
              world.airplay_service_lines]
    unique = len(set(devids))
    assert unique == len(devids), "Found device IDs: " + str(devids)


import sqlite3
from sqlite3 import IntegrityError
import logging

from typing import List
from datetime import datetime

from togglcmder.toggl.types.workspace import Workspace
from togglcmder.toggl.builders.workspace_builder import WorkspaceBuilder
from togglcmder.toggl.types.time_entry import TimeEntry
from togglcmder.toggl.builders.time_entry_builder import TimeEntryBuilder
from togglcmder.toggl.types.user import User
from togglcmder.toggl.builders.user_builder import UserBuilder
from togglcmder.toggl.types.tag import Tag
from togglcmder.toggl.builders.tag_builder import TagBuilder
from togglcmder.toggl.types.project import Project
from togglcmder.toggl.builders.project_builder import ProjectBuilder

class Caching(object):
    WORKSPACE_TABLE = '''
    CREATE TABLE IF NOT EXISTS workspaces (
        name TEXT NOT NULL,
        identifier INTEGER PRIMARY KEY,
        last_updated TIMESTAMP NOT NULL
    )
    '''

    PROJECT_TABLE = '''
    CREATE TABLE IF NOT EXISTS projects (
        name TEXT NOT NULL,
        color INTEGER,
        last_updated TIMESTAMP NOT NULL,
        created TIMESTAMP NOT NULL,
        identifier INTEGER PRIMARY KEY,
        workspace_identifier INTEGER NOT NULL,
        FOREIGN KEY (workspace_identifier) REFERENCES workspaces (identifier) ON DELETE CASCADE
    )
    '''

    TAG_TABLE = '''
    CREATE TABLE IF NOT EXISTS tags (
        name TEXT,
        identifier INTEGER PRIMARY KEY,
        workspace_identifier INTEGER,
        FOREIGN KEY (workspace_identifier) REFERENCES workspaces (identifier) ON DELETE CASCADE
    )
    '''

    TIME_ENTRY_TABLE = '''
    CREATE TABLE IF NOT EXISTS time_entries (
        description TEXT,
        start_time TIMESTAMP NOT NULL,
        stop_time TIMESTAMP,
        duration INTEGER,
        identifier INTEGER PRIMARY KEY,
        project_identifier INTEGER,
        workspace_identifier INTEGER NOT NULL,
        last_updated TIMESTAMP,
        FOREIGN KEY (project_identifier) REFERENCES projects (identifier) ON DELETE CASCADE,
        FOREIGN KEY (workspace_identifier) REFERENCES workspaces (identifier)
    )
    '''

    TIME_ENTRY_TAG_JUNCTION_TABLE = '''
    CREATE TABLE IF NOT EXISTS time_entry_tags (
        tag_identifier INTEGER NOT NULL,
        time_entry_identifier INTEGER NOT NULL,
        FOREIGN KEY (tag_identifier) REFERENCES tags (identifier) ON DELETE CASCADE,
        FOREIGN KEY (time_entry_identifier) REFERENCES time_entries (identifier) ON DELETE CASCADE
    )
    '''

    USER_TABLE = '''
    CREATE TABLE IF NOT EXISTS users (
        name TEXT,
        api_token TEXT,
        identifier INTEGER PRIMARY KEY,
        last_updated TIMESTAMP NOT NULL
    )
    '''

    def __init__(self, *, cache_name: str = "cache.db"):
        self.__connection = sqlite3.connect(cache_name)
        self.__connection.set_trace_callback(logging.getLogger(__name__).debug)
        self.__cursor = self.__connection.cursor()

        self.__cursor.execute("PRAGMA foreign_keys = 1")
        self.__connection.commit()

        self.__cursor.execute(Caching.WORKSPACE_TABLE)
        self.__cursor.execute(Caching.PROJECT_TABLE)
        self.__cursor.execute(Caching.TAG_TABLE)
        self.__cursor.execute(Caching.TIME_ENTRY_TABLE)
        self.__cursor.execute(Caching.TIME_ENTRY_TAG_JUNCTION_TABLE)
        self.__cursor.execute(Caching.USER_TABLE)

        self.__connection.commit()

        self.__workspaces: List[Workspace] = []
        self.__projects: List[Project] = []
        self.__tags: List[Tag] = []
        self.__time_entries: List[TimeEntry] = []

    def __del__(self):
        self.__connection.close()

    def update_workspace_cache(self, workspaces: List[Workspace]) -> int:
        insert_sql = '''
            INSERT INTO workspaces
            (name, identifier, last_updated) VALUES
            (?, ?, ?)
        '''

        update_sql = '''
            UPDATE workspaces SET name=?, last_updated=?
            WHERE identifier=?
        '''

        for workspace in workspaces:
            try:
                self.__cursor.execute(
                    insert_sql, (workspace.name,
                                 workspace.identifier,
                                 workspace.last_updated.timestamp()))
            except IntegrityError:
                self.__cursor.execute(
                    update_sql, (workspace.name,
                                 workspace.last_updated.timestamp(),
                                 workspace.identifier))

        self.__connection.commit()
        return self.__cursor.rowcount

    def retrieve_workspace_cache(self) -> List[Workspace]:
        sql = '''
            SELECT name, identifier, last_updated FROM workspaces
        '''

        self.__cursor.execute(sql)
        results = self.__cursor.fetchall()
        if results:
            return [
                WorkspaceBuilder()
                    .name(result[0])
                    .identifier(result[1])
                    .last_updated(epoch=result[2]).build()
                for result in results
            ]

    def update_user_cache(self, user: User) -> int:
        insert_sql = '''
            INSERT INTO users
            (name, api_token, identifier, last_updated) VALUES
            (?, ?, ?, ?)
        '''

        update_sql = '''
            UPDATE users SET name=?, api_token=?, last_updated=?
            WHERE identifier=?
        '''

        try:
            self.__cursor.execute(
                insert_sql, (user.name,
                             user.api_token,
                             user.identifier,
                             user.last_updated.timestamp()))
        except IntegrityError:
            self.__cursor.execute(
                update_sql, (user.name,
                             user.api_token,
                             user.last_updated.timestamp(),
                             user.identifier))

        self.__connection.commit()
        return self.__cursor.rowcount

    def retrieve_user_cache(self) -> User:
        sql = '''
            SELECT name, api_token, identifier, last_updated FROM users
        '''

        self.__cursor.execute(sql)
        results = self.__cursor.fetchone()
        if results:
            return UserBuilder()\
                .name(results[0])\
                .api_token(results[1])\
                .identifier(results[2])\
                .last_updated(epoch=results[3]).build()

    def update_project_cache(self, projects: List[Project]) -> int:
        insert_sql = '''
            INSERT INTO projects
            (name, color, last_updated, created, identifier, workspace_identifier) VALUES
            (?, ?, ?, ?, ?, ?)
        '''

        update_sql = '''
            UPDATE projects
            SET name=?, color=?, last_updated=?, workspace_identifier=?
            WHERE identifier=?
        '''

        for project in projects:
            try:
                self.__cursor.execute(
                    insert_sql, (project.name,
                                 project.color.value,
                                 project.last_updated.timestamp(),
                                 project.created.timestamp() if project.created else datetime.now().timestamp(),
                                 project.identifier,
                                 project.workspace_identifier))
            except IntegrityError:
                self.__cursor.execute(
                    update_sql, (project.name,
                                 project.color.value,
                                 project.last_updated.timestamp(),
                                 project.workspace_identifier,
                                 project.identifier))

        self.__connection.commit()
        return self.__cursor.rowcount

    def retrieve_project_cache(self) -> List[Project]:
        sql = '''
            SELECT name, color, last_updated, created, identifier, workspace_identifier FROM projects
        '''

        self.__cursor.execute(sql)
        results = self.__cursor.fetchall()
        if results:
            return [
                ProjectBuilder()
                    .name(result[0])
                    .color(result[1])
                    .last_updated(epoch=result[2])
                    .created(epoch=result[3])
                    .identifier(result[4])
                    .workspace_identifier(result[5]).build()
                for result in results
            ]

    def remove_project_from_cache(self, project: Project) -> None:
        sql = '''
            DELETE FROM projects
            WHERE identifier=?
        '''
        self.__cursor.execute(sql, (project.identifier,))

        self.__connection.commit()

    def update_tag_cache(self, tags: List[Tag]) -> int:
        insert_sql = '''
            INSERT INTO tags
            (name, identifier, workspace_identifier) VALUES
            (?, ?, ?)
        '''

        update_sql = '''
            UPDATE tags
            SET name=?, workspace_identifier=?
            WHERE identifier=?
        '''

        rows_affected = 0
        for tag in tags:
            try:
                self.__cursor.execute(
                    insert_sql, (tag.name,
                                 tag.identifier,
                                 tag.workspace_identifier))
            except IntegrityError:
                self.__cursor.execute(
                    update_sql, (tag.name,
                                 tag.workspace_identifier,
                                 tag.identifier))
            rows_affected += self.__cursor.rowcount

        self.__connection.commit()
        return rows_affected

    def retrieve_tag_cache(self) -> List[Tag]:
        sql = """
            SELECT name, identifier, workspace_identifier FROM tags
        """

        self.__cursor.execute(sql)
        results = self.__cursor.fetchall()
        if results:
            return [
                TagBuilder()
                    .name(result[0])
                    .identifier(result[1])
                    .workspace_identifier(result[2]).build()
                for result in results
            ]

    def remove_tag_from_cache(self, tag: Tag) -> None:
        tag_removal_sql = '''
            DELETE FROM tags
            WHERE identifier=?
        '''
        self.__cursor.execute(tag_removal_sql, (tag.identifier,))

        join_table_removal_sql = '''
            DELETE FROM time_entry_tags
            WHERE tag_identifier=?
        '''
        self.__cursor.execute(join_table_removal_sql, (tag.identifier,))

        self.__connection.commit()

    def __retrieve_time_entry_tags_join(self, time_entry_identifier: int) -> List[tuple]:
        sql = '''
            SELECT name, tag_identifier, time_entry_identifier
            FROM time_entry_tags
            INNER JOIN tags ON time_entry_tags.tag_identifier = tags.identifier
            WHERE time_entry_identifier=?
        '''
        self.__cursor.execute(sql, (time_entry_identifier,))
        return self.__cursor.fetchall()

    def __retrieve_time_entry_tags(self, time_entry_identifier: int) -> List[tuple]:
        sql = '''
            SELECT tag_identifier, time_entry_identifier
            FROM time_entry_tags
            WHERE time_entry_identifier=?
        '''
        self.__cursor.execute(sql, (time_entry_identifier,))
        return self.__cursor.fetchall()

    def __check_existing(self, tags: List[int], time_entry_identifier: int) -> List[int]:
        # returns a tuple of (tag_id, time_entry_id)
        existing_time_entry_tags = self.__retrieve_time_entry_tags(time_entry_identifier)
        if len(tags) == 0 or len(existing_time_entry_tags) == 0:
            return tags
        return list(
            # 2. map each tuple to be just the tag identifier, so given x,
            #    return the tag_id
            map(lambda x: x[0],
                # 1. filter so we only get tags tuples that aren't in the existing list
                #     of tags (checked based on x[0], which is the tag identifier)
                filter(lambda x: x[0] not in tags, existing_time_entry_tags)))

    def update_time_entry_cache(self, time_entries: List[TimeEntry]) -> int:
        insert_sql = '''
            INSERT INTO time_entries
            (description, start_time, stop_time, duration, identifier,
             project_identifier, workspace_identifier, last_updated) VALUES
            (?, ?, ?, ?, ?, ?, ?, ?)
        '''

        update_sql = '''
            UPDATE time_entries
            SET description=?,
                start_time=?,
                stop_time=?,
                duration=?,
                project_identifier=?,
                workspace_identifier=?,
                last_updated=?
            WHERE identifier=?
        '''

        insert_time_entry_tag_sql = '''
            INSERT INTO time_entry_tags
            (tag_identifier, time_entry_identifier)
            VALUES (?, ?)
        '''
        tag_rows = self.retrieve_tag_cache()
        rows_affected = 0

        for time_entry in time_entries:
            try:
                self.__cursor.execute(
                    insert_sql, (time_entry.description,
                                 time_entry.start_time.timestamp(),
                                 None if not time_entry.stop_time else time_entry.stop_time.timestamp(),
                                 time_entry.duration,
                                 time_entry.identifier,
                                 time_entry.project_identifier,
                                 time_entry.workspace_identifier,
                                 time_entry.last_updated.timestamp()))
            except IntegrityError:
                self.__cursor.execute(
                    update_sql, (time_entry.description,
                                 time_entry.start_time.timestamp(),
                                 None if not time_entry.stop_time else time_entry.stop_time.timestamp(),
                                 time_entry.duration,
                                 time_entry.project_identifier,
                                 time_entry.workspace_identifier,
                                 time_entry.last_updated.timestamp(),
                                 time_entry.identifier))
            rows_affected += self.__cursor.rowcount

            tag_ids = []
            if time_entry.tags:
                for tag in time_entry.tags:
                    for tag_row in tag_rows:
                        if tag == tag_row.name:
                            tag_ids.append(tag_row.identifier)
                            break
                for tag_id in self.__check_existing(tag_ids, time_entry.identifier):
                    self.__cursor.execute(
                        insert_time_entry_tag_sql,
                        (tag_id,
                         time_entry.identifier))

        self.__connection.commit()

        return rows_affected

    def retrieve_time_entry_cache(self) -> List[TimeEntry]:
        time_entry_sql = """
            SELECT  description,
                    start_time,
                    stop_time,
                    duration,
                    identifier,
                    project_identifier,
                    workspace_identifier,
                    last_updated
            FROM time_entries
        """

        time_entries = []

        self.__cursor.execute(time_entry_sql)
        results = self.__cursor.fetchall()
        for result in results:
            tag_results = self.__retrieve_time_entry_tags_join(result[4])

            builder = TimeEntryBuilder()\
                .description(result[0])\
                .start_time(epoch=result[1])\
                .stop_time(epoch=result[2])\
                .duration(result[3])\
                .identifier(result[4])\
                .project_identifier(result[5])\
                .workspace_identifier(result[6])\
                .last_updated(epoch=result[7])\
                .tags([tag_result[0] for tag_result in tag_results])
            time_entries.append(builder.build())

        return time_entries

    def remove_time_entry_from_cache(self, time_entry: TimeEntry) -> None:
        entry_removal_sql = '''
            DELETE FROM time_entries
            WHERE identifier=?
        '''
        self.__cursor.execute(entry_removal_sql, (time_entry.identifier,))

        joined_entry_removal_sql = '''
            DELETE FROM time_entry_tags
            WHERE time_entry_identifier=?
        '''
        self.__cursor.execute(joined_entry_removal_sql, (time_entry.identifier,))

        self.__connection.commit()

    def get_workspace_identifier(self, workspace_name: str) -> int:
        sql = """
            SELECT identifier
            FROM workspaces
            WHERE name=?
        """
        self.__cursor.execute(sql, (workspace_name,))
        return self.__cursor.fetchone()[0]

    def get_project_identifier(self, project_name: str) -> int:
        sql = """
            SELECT identifier
            FROM projects
            WHERE name=?
        """
        self.__cursor.execute(sql, (project_name,))
        return self.__cursor.fetchone()[0]


import lnschema_bionty as lb
import pandas as pd
import pytest

import lamindb as ln  # noqa

@pytest.fixture(scope="module")
def df():
    return pd.DataFrame(
        (
            ["T cell", "CL:0000084"],
            ["hepatocyte", "CL:0000182"],
            ["my new cell type", ""],
        ),
        columns=["cell_type", "cell_type_id"],
    )

def test_from_values_name(df):
    lb.CellType.filter().delete()
    assert df["cell_type"].tolist() == ["T cell", "hepatocyte", "my new cell type"]
    # create records from bionty
    result = lb.CellType.from_values(df.cell_type, "name")
    ids = [i.ontology_id for i in result]
    assert len(result) == 2
    assert set(ids) == {"CL:0000084", "CL:0000182"}
    assert result[0].bionty_source.entity == "CellType"

    # wrong field type
    with pytest.raises(TypeError):
        result = lb.CellType.from_values(df.cell_type, field=lb.CellType)

def test_from_values_ontology_id(df):
    assert df["cell_type_id"].tolist() == ["CL:0000084", "CL:0000182", ""]
    result = lb.CellType.from_values(df.cell_type_id, "ontology_id")
    names = {i.name for i in result}
    assert len(result) == 2
    assert names == {"T cell", "hepatocyte"}
    assert result[0].bionty_source.entity == "CellType"

def test_from_values_multiple_match():
    records = lb.Gene.from_values(["ABC1", "PDCD1"], lb.Gene.symbol, organism="human")
    assert len(records) == 3

def test_from_values_organism():
    from lnschema_bionty import Gene, settings

    settings._organism = None

    with pytest.raises(AssertionError):
        Gene.from_values(["ABC1"], Gene.symbol)

    settings.organism = "human"
    values = ["ABC1"]
    curated_values = Gene.bionty().standardize(values)
    records = Gene.from_values(curated_values, Gene.symbol)
    assert records[0].ensembl_gene_id == "ENSG00000068097"

    settings.organism = "mouse"
    values = ["ABC1"]
    curated_values = Gene.bionty().standardize(values)
    records = Gene.from_values(curated_values, Gene.symbol)
    assert records[0].ensembl_gene_id == "ENSMUSG00000015243"

def test_get_or_create_records():
    names = ["ulabel" + str(i) for i in range(25)]
    labels = [ln.ULabel(name=name) for name in names]
    ln.save(labels)
    # more than 20 existing values
    labels = ln.ULabel.from_values(names, field="name")
    assert len(labels) == 25

def test_from_values_synonyms_aware():
    lb.CellType.from_bionty(name="T cell").save(parents=False)
    # existing validated values
    records = lb.CellType.from_values(["T cell"], "name")
    assert len(records) == 1
    assert records[0].name == "T cell"
    assert isinstance(records[0].bionty_source, lb.BiontySource)
    # existing validated values and synonyms
    records = lb.CellType.from_values(["T cell", "T-cell"], "name")
    assert len(records) == 1
    assert records[0].name == "T cell"
    assert isinstance(records[0].bionty_source, lb.BiontySource)
    # bionty values and synonyms
    records = lb.CellType.from_values(["B-cell", "B cell"], "name")
    assert len(records) == 1
    assert records[0].name == "B cell"
    assert isinstance(records[0].bionty_source, lb.BiontySource)
    # all possibilities of validated values
    records = lb.CellType.from_values(
        ["T cell", "T-cell", "t cell", "B cell", "B-cell"], "name"
    )
    assert len(records) == 2
    names = [r.name for r in records]
    assert set(names) == set(["T cell", "B cell"])
    assert isinstance(records[0].bionty_source, lb.BiontySource)
    assert isinstance(records[1].bionty_source, lb.BiontySource)
    # non-validated values
    records = lb.CellType.from_values(["T cell", "mycell"], "name")
    assert len(records) == 1
    assert records[0].name == "T cell"
    assert isinstance(records[0].bionty_source, lb.BiontySource)
    assert records[0].ontology_id == "CL:0000084"
    lb.CellType.filter().all().delete()


from itertools import permutations
from city import Country
import numpy as np
import heapq
import random
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from python_tsp.distances import euclidean_distance_matrix
from python_tsp.exact import solve_tsp_dynamic_programming

class TSPGA:
    def __init__(self,
                 country: Country,
                 init_population_size: int=10,
                 max_population_size: int=100,
                 debug=False):
        """constructor

        Args:
            country (Country): refer to city.Country class
            init_population_size (int, optional): Initial population size when solving TSP. Defaults to 10.
            max_population_size (int, optional): Maximum population size when solving TSP. Defaults to 100.
            debug (bool, optional): Debug mode toggle. Defaults to False.
            Warning: slow for large number of cities should only try with num_cities < 15.
        """
        self.country = country
        self.init_size = init_population_size
        self.max_size = max_population_size
        self.population = self._init_population()
        self.fig, (self.path_ax, self.score_ax) = plt.subplots(2, 1)
        self.debug = debug
        if self.debug:
            _, self.ideal_distance = self.country.get_tsp_solution()
        self.scores = []

    def _init_population(self):
        """Initialize random population

        Returns:
            list: a list of population with heap organization. Useful for later operation
        """
        init_pop = []
        all_paths = permutations(range(self.country.num_cities))

        for _ in range(self.init_size):
            path = next(all_paths)
            score = self.country.total_distance(path)
            heapq.heappush(init_pop, (-score, list(path)))

        return init_pop

    def add_new_population(self, path):
        """Add new population to the list

        Args:
            path (list[int]): Cyclic path to all cities
        """
        score = self.country.total_distance(path)
        if len(self.population) <= self.max_size:
            heapq.heappush(self.population, (-score, list(path)))
        else:
            # only keep some top population and kill bad population to boost convergence
            heapq.heappushpop(self.population, (-score, list(path)))

    def select_best_individual(self, n_best=10):
        """take n-best population based on path length. This is when heap becomes powerful

        Args:
            n_best (int, optional): number of top population to take. Defaults to 10.

        Returns:
            list[list[int]]: top n population
        """
        assert n_best <= len(self.population)
        return heapq.nlargest(n_best, self.population)

    def path2adj_list(self, path):
        """generate adjacency list from cyclic path

        Args:
            path (list[int]): cyclic path

        Returns:
            dict[int, set]: adjacency list from given path
        """
        neighbor_list = {}
        for i in range(len(path)):
            neighbor_list[path[i]] = {
                path[(i - 1) % self.country.num_cities],
                path[(i + 1) % self.country.num_cities]
            }
        return neighbor_list

    def merge_neighbor_list(self, neighbor_list1, neighbor_list2):
        """generate a graph that contains all edges from two parent cyclic path

        Args:
            neighbor_list1 (list[int]): parent path 1
            neighbor_list2 (list[int]): parent path 2

        Returns:
            dict[int, set]: merged parent graph
        """
        neighbor_list_merge = {}

        for vertex in neighbor_list1:
            neighbor_list_merge[vertex] = neighbor_list1[vertex].union(neighbor_list2[vertex])

        return neighbor_list_merge

    def breed(self, parent1, parent2):
        """breed parent to create new children

        Args:
            parent1 (list[int]): parent path 1
            parent2 (list[int]): parent path 2

        Returns:
            list[int]: new-born path
        """
        adj_list_1 = self.path2adj_list(parent1)
        adj_list_2 = self.path2adj_list(parent2)
        adj_list_merge = self.merge_neighbor_list(adj_list_1, adj_list_2)
        vertex = random.choice([parent1[0], parent2[0]])

        paths = [vertex]

        while len(paths) < self.country.num_cities:
            minLen = None
            chosen_vertex = []
            for v in adj_list_merge[vertex]:
                adj_list_merge[v].remove(vertex)
                if minLen is None or len(adj_list_merge[v]) < minLen:
                    minLen = len(adj_list_merge[v])
                    chosen_vertex = [v]
                elif len(adj_list_merge[v]) == minLen:
                    chosen_vertex.append(v)
            adj_list_merge.pop(vertex)
            if len(chosen_vertex) != 0:
                paths.append(random.choice(chosen_vertex))
            else:
                paths.append(random.choice(list(adj_list_merge.keys())))
            vertex = paths[-1]
        return paths

    def mutate(self, individual, n_mutate_times=1):
        """mutate individual to mimic the nature of genome

        Args:
            individual (list[int]): a path within population
            n_mutate_times (int, optional): number of times mutation happens. Defaults to 1.

        Returns:
            list[int]: mutated individual
        """
        for _ in range(n_mutate_times):
            gen_pos_1, gen_pos_2 = np.random.choice(self.country.num_cities, 2, replace=False)
            individual[gen_pos_1], individual[gen_pos_2] = individual[gen_pos_2], individual[gen_pos_1]
        return individual

    def generate_single_frame(self,
                              t,
                              individual_per_epochs=10,
                              n_breed_per_epoch=10,
                              mutate_percentage=0.1):
        # select best individuals
        best_individuals = self.select_best_individual(individual_per_epochs)

        # breeding
        for _ in range(n_breed_per_epoch):
            (_, individual_1), (_, individual_2) = random.sample(best_individuals, 2)
            new_individual = self.breed(individual_1, individual_2)
            u = np.random.uniform()

            # mutation in nature
            if u > mutate_percentage:
                new_individual = self.mutate(new_individual)

            self.add_new_population(new_individual)

        # logging
        score, best_individual = heapq.nlargest(1, self.population)[0]
        xs = self.country.cities[best_individual + [best_individual[0]], 0]
        ys = self.country.cities[best_individual + [best_individual[0]], 1]

        self.path_ax.clear()
        self.path_ax.set_title("TSP solution")
        self.path_ax.scatter(xs, ys, label="city", color="blue")
        self.path_ax.plot(xs, ys, label="path", color="red")
        self.path_ax.legend()

        self.scores.append(abs(score))
        self.score_ax.clear()
        self.score_ax.plot(self.scores)
        self.score_ax.set_xlabel("Iteration")
        self.score_ax.set_ylabel("Cost")
        self.score_ax.set_title("GA Learning curve")

        if self.debug:
            self.score_ax.hlines(self.ideal_distance, xmin=0, xmax=t + 1, color="black", label="optimal solution")
            self.score_ax.legend()
        self.fig.tight_layout()
        if self.debug:
            print("Iteration {}: current path {}, shortest path {}".format(
                t + 1,
                abs(score),
                self.ideal_distance
            ))
        else:
            print("Iteration {}: current path {}".format(t + 1, abs(score)))

    def learn(self,
              total_timestep=100,
              individual_per_epochs=10,
              n_breed_per_epoch=10,
              mutate_percentage=0.1,
              mode="show",
              filename="demo.gif"):
        ani = animation.FuncAnimation(
            self.fig,
            self.generate_single_frame,
            total_timestep,
            fargs=(individual_per_epochs, n_breed_per_epoch, mutate_percentage),
            interval=10
        )
        if mode == "save":
            ani.save(filename, fps=60)
        elif mode == "show":
            plt.show()


'''

Write a Python program to square and cube every number in a given list of
integers using Lambda.

'''

sq=lambda x:x*x
cub=lambda x:x*x*x

n=int(input("Enter the integer:"))
print(f"Square -> {sq(n)}")
print(f"Cube -> {cub(n)}")


import boto3

#document
documentName = "image-name.jpg"

#read document content if image is stored on local computer
with open(documentName, 'rb') as document:
    imageBytes = bytearray(document.read())

#amazon textract client
textract = boto3.client('textract')

#call amazon textract
response = textract.detect_document_text(Document = {'Bytes': imageBytes})

# to obtain the image from s3_bucket use
# {'S3Object':{

#     'Bucket':'Bucket_name',
#     'name': 'name_of_iamge'
# }}
# to obtain the image from local computer use
# {'Bytes' : source_bytes}

# Amazon translate client
translate = boto3.client('translate')

#print the detected text

print('')
for item in response["Blocks"]:
    if item["BlockType"] == "LINE":
        print('\033[94m' + item["Text"] + '\033[0m')
        result = translate.translate_text(Text=item["Text"], SourceLanguageCode="en", TargetLanguageCode="code-for-language")
        print('\033[94m' + result.get('TranslatedText') + '\033[0m')
    print('')


#!/usr/bin/python

"""
    This is the code to accompany the Lesson 3 (decision tree) mini-project.

    Use a Decision Tree to identify emails from the Enron corpus by author:
    Sara has label 0
    Chris has label 1
"""

import sys
from time import time
sys.path.append("../tools/")
from email_preprocess import preprocess

### features_train and features_test are the features for the training
### and testing datasets, respectively
### labels_train and labels_test are the corresponding item labels
features_train, features_test, labels_train, labels_test = preprocess()

#########################################################
### your code goes here ###

from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

print(features_train.shape)

class Timer(object):
    def __init__(self, name=""):
        self.name = name
        self.start = None

    def __enter__(self):
        self.start = time()

    def __exit__(self, type, value, traceback):
        end = time()
        print('{} took {}s'.format(self.name, end-self.start))

clf = DecisionTreeClassifier(min_samples_split=40)
with Timer("Training"):
    clf.fit(features_train, labels_train)

with Timer("Testing"):
    pred = clf.predict(features_test)

print("Accuracy: {:.2f}".format(100 * accuracy_score(labels_test, pred)))

#########################################################


#!/usr/bin/env python3

# Created by: Liam Fletcher
# Created on: Oct 2021
# This program asks the user for two numbers
# Then the programs find the LCM of the two numbers

def main():
    # this tells the user the LCM of two numbers

    # input
    number1 = input("Please enter the first number: ")
    number2 = input("Please enter the second number: ")

    # process and output
    try:
        number1_as_integer = int(number1)
        number2_as_integer = int(number2)
        if (
            number1_as_integer > number2_as_integer
        ):  # This finds the greatest number among the two.
            max = number1_as_integer
        else:
            max = number2_as_integer
        while True:
            if max % number1_as_integer == 0 and max % number2_as_integer == 0:
                max = max + 1
                print(
                    "The LCM of the two numbers is {0}".format(
                        number1_as_integer, number2_as_integer
                    )
                )
                break

    except Exception:
        print("This is an invalid number!")
    finally:
        print("Done.")

if __name__ == "__main__":
    main()


import numpy as np
import matplotlib.pyplot as plt

def y_tt(y,t):
    return -10

def y_2tt(y,t):
    return float(-4*y)

def y_3tt(y,t):
    return -y

def EulerS_O(f, t, y_0, y_t0):
    y = np.zeros(len(t),dtype=float)
    y_t = np.zeros(len(t),dtype=float)
    delta = t[1]-t[0]
    y[0] = float(y_0)
    y_t[0] = float(y_t0)
    for i in range(1,len(y)):
        y_t[i] = y_t[i-1]+delta*f(y[i-1],t[i-1])
        y[i] = y[i-1]+delta*y_t[i-1]
    return y, y_t

def RungeKutta4S_O(f,t,y_0=0,y_t0=0):
    rungeKutta4 = np.zeros(len(t),dtype=float)
    rungeKutta4_t = np.zeros(len(t),dtype=float)
    delta = t[1]-t[0]
    rungeKutta4[0] = y_0
    rungeKutta4_t[0] = y_t0
    for l in range(1,len(rungeKutta4)):
        k1 = f(rungeKutta4[l-1],0)
        k2 = f(rungeKutta4[l-1]+(delta/2)*k1,0)
        k3 = f(rungeKutta4[l-1]+(delta/2)*k2,0)
        k4 = f(rungeKutta4[l-1]+(delta)*k3,0)
        k = (1/6.0)*(k1+2*k2+2*k3+k4)
        rungeKutta4_t[l] = rungeKutta4_t[l-1]+delta*k
        k1 = rungeKutta4_t[l-1]
        k2 = rungeKutta4_t[l-1]+(delta/2)*k
        k3 = rungeKutta4_t[l-1]+(delta/2)*k
        k4 = rungeKutta4_t[l-1]+(delta)*k
        k = (1/6.0)*(k1+2*k2+2*k3+k4)
        rungeKutta4[l] = rungeKutta4[l-1]+delta*k
    return rungeKutta4,rungeKutta4_t

def Resolver():
    t = np.linspace(0,100,10000)
    y, y_t = RungeKutta4S_O(y_3tt,t,0,10)
    plt.plot(y,y_t)
    plt.show()

Resolver()


word = input("Введите слово: ")
list_word = list(word)
index = len(list_word)

def check_poli(string):
    k = set()
    count = 0

    for i in range(index // 2):
        if list_word[i] == list_word[index - 1]:
            count += 1

    if count == index // 2:
        print("Слово является палиндромом")
    else:
        print("Слово не является палиндромом")

    if count != index // 2:
        for b in list_word:
            if b in k:
                k.remove(b)
            else:
                k.add(b)

        print(('Можно', 'Нельзя')[len(k) > 1], 'сделать полиндром')

def poli(string):
    while True:
        if word.isdigit() == False:
            check_poli(word)
            break
        else:
            print('Можно вводить только буквы!')
            break

poli(word)


# WRITE YOUR SOLUTION HERE:
class ListHelper:
    @classmethod
    def greatest_frequency(cls,my_list: list):
        freq = {}
        for i in my_list:
            if i not in freq:
                freq[i] = my_list.count(i)
        result = 0
        count = 0
        for i in freq:
            if freq[i] > count:
                result = i
                count = freq[i]
        return result

    @classmethod
    def doubles(cls,my_list: list):
        dou = []
        count = []
        for i in my_list:
            if i not in count:
                count.append(i)
            else:
                if i not in dou:
                    dou.append(i)
        return len(dou)


def user_exists(name, host='localhost', **kwargs):
    """
    Check if a MySQL user exists.
    """
    with settings(hide('running', 'stdout', 'stderr', 'warnings'), warn_only=True):
        res = query("""
            use mysql;
            SELECT COUNT(*) FROM user
                WHERE User = '%(name)s' AND Host = '%(host)s';
            """ % {
                'name': name,
                'host': host,
            }, **kwargs)
    return res.succeeded and (int(res) == 1)


#####################
#Starter code for Q3#
#####################

def equity(f):
    #inp is a dictionary key is company, value is a list [recievalbe, payable]
    receive = 0
    pay = 0
    for i in f:
        receive += f[i][0]
        pay += f[i][1]
    total = receive-pay
    return (receive,pay,total)


# il testbench genera gruppi di 10 numeri alla volta sulla stessa linea e separati da uno spazio.

# i primi 8 numeri sono le basi delle working zones, che devono essere distanti almeno 4 indirizzi fra di loro, e non per forza in ordine

# il nono numero è l'indirizzo da codificare compreso fra 0 e 127

# il decimo numero è la codifica del numero, quindi bisogna riprodurre nel testbench il behaviour del programma.

# 1 generare gli 8 indirizzi working zone
# il numero generato non deve cadere nei 4 indirizzi delle basi gia create, e le basi gia create non devono cadere nei 4 indirizzi della base generata
# Esempio: Se ad una certa posizione trovo una wz con base 20 allora devo generare una nuova base che sia minore di 17 e maggiore di 23
# Quindi 19 e 22 non sono validi, 15 e 25 si, e devo eseguire questo check per tutte le basi.

import random

for i in range(0, 4000):

    wzList = []
    isColliding = False
    isInWz = False
    addressPos = 0
    offsetDistance = 0
    binaryRep = str
    onehotString = str
    wzString = str
    oneHotRep = 0
    encodedValue = 0

    randBase = random.randint(0, 128)
    wzList.append(randBase)
    randAddress = 0

    while len(wzList) < 8:

        randBase = random.randint(0, 127)

        for y in range(0, len(wzList)):
            if randBase < wzList[y] - 3 or randBase > wzList[y] + 3:
                continue
            else:
                isColliding = True
                break

        if isColliding:
            isColliding = False
            continue
        else:
            wzList.append(randBase)

    randAddress = random.randint(0, 127)

    for z in range(0, len(wzList)):

        if randAddress < wzList[z] or randAddress > wzList[z] + 3:
            continue
        else:
            isInWz = True
            addressPos = z
            offsetDistance = randAddress - wzList[z]
            break

    if isInWz:
        # qua devo scrivere la stringa binaria

        # unica cosa da encoddare è l'offset, che si puo fare come 2^(wzoffset) per creare la codifica onehot

        oneHotRep = 2 ** offsetDistance

        onehotString = '{0:04b}'.format(oneHotRep)

        wzString = '{0:03b}'.format(addressPos)

        binaryRep = '1' + wzString + onehotString

        encodedValue = int(binaryRep, 2)

        print(wzList, isInWz, randAddress, addressPos, offsetDistance, encodedValue, binaryRep)

    else:
        # qua devo riscrivere il nome
        encodedValue = randAddress
        print(wzList, isInWz, randAddress)

    # Ora resta solo da scrivere su file i test

    testFile = open("testBenchLollo.txt", "a+")

    for w in range(0, len(wzList)):
        testFile.write(str(wzList[w]) + " ")

    testFile.write(str(randAddress) + " ")

    testFile.write(str(encodedValue) + "\n")

    testFile.close()


import numpy as np
from sklearn.preprocessing import PolynomialFeatures

from .data_sbj import DataSubject, bias_feat

class DataSubjectPoly(DataSubject):
    """ polynomial projection of subject features for non-linear regression

    Note: we preclude the inclusion of polynomial features which draw from both
    target and nuisance params (e.g. if one is interested in the effects of age
    while controlling for sex, a model with an age * sex term makes Freedman
    Lane difficult, maybe impossible)

    Attributes:
        poly_order (int): order of polynomial projection
        feat_list_raw (list): names of raw features (before projection)
        contrast_raw (np.array): (num_feat) which raw features are targets
        feat_raw (np.array): (num_sbj, num_feat) original features, unpermuted

        poly_trgt (PolynomialFeatures): mapping of target features, has bias
        poly_nuis (PolynomialFeatures): mapping of nuisance features, no bias
    """

    def __init__(self, *args, poly_order=1, interaction_only=False, **kwargs):
        super().__init__(*args, **kwargs)

        self.poly_order = poly_order
        self.feat_raw = self.feat
        self.feat_list_raw = list(self.feat_list)
        self.contrast_raw = self.contrast

        # strip bias_feat (will be added later via PolynomialFeatures)
        if bias_feat in self.feat_list_raw:
            idx = self.feat_list_raw.index(bias_feat)
            self.feat = np.delete(self.feat, idx, axis=1)
            self.feat_list.pop(idx)
            self.contrast = np.delete(self.contrast, idx, axis=0)

        # get idx of target and nuisance features (bias_feat is neither)
        idx_target = np.where(self.contrast)[0]
        idx_nuisance = np.where(np.logical_not(self.contrast))[0]

        feat_raw_target = self.feat[:, idx_target]
        feat_raw_nuisance = self.feat[:, idx_nuisance]

        # get lists of feature names
        feat_list_trgt = [self.feat_list[idx] for idx in idx_target]
        feat_list_nuis = [self.feat_list[idx] for idx in idx_nuisance]

        # project target features
        self.poly_trgt = PolynomialFeatures(degree=poly_order,
                                            include_bias=True,
                                            interaction_only=interaction_only)
        self.feat = self.poly_trgt.fit_transform(feat_raw_target)
        self.feat_list = self.poly_trgt.get_feature_names(feat_list_trgt)
        self.contrast = np.ones(self.feat.shape[1]).astype(bool)

        self.poly_nuis = None
        if self.has_nuisance:
            # project nuisance features
            self.poly_nuis = PolynomialFeatures(degree=poly_order,
                                                include_bias=False,
                                                interaction_only=interaction_only)
            feat_nuis = self.poly_nuis.fit_transform(feat_raw_nuisance)
            self.feat_list += self.poly_nuis.get_feature_names(feat_list_nuis)
            self.feat = np.hstack((self.feat, feat_nuis))

            # append nuisance to new contrast matrix
            contrast_nuis = np.zeros(feat_nuis.shape[1]).astype(bool)
            self.contrast = np.hstack((self.contrast, contrast_nuis))

        # compute pseudo inverse
        self.pseudo_inv = np.linalg.pinv(self.feat)

        # permute
        self.permute(self.perm_seed)

    def linspace(self, *args, **kwargs):
        feat_img = super().linspace(*args, feat=self.feat_raw, **kwargs)

        # strip bias_feat
        contrast_raw = self.contrast_raw
        if bias_feat in self.feat_list_raw:
            idx = self.feat_list_raw.index(bias_feat)
            contrast_raw = np.delete(contrast_raw, idx, axis=0)
            feat_img = np.delete(feat_img, idx, axis=1)

        # transform to polynomial space
        feat_img_out = self.poly_trgt.transform(feat_img[:, contrast_raw])
        if self.poly_nuis is not None:
            not_contrast = np.logical_not(contrast_raw)
            feat_nuisance = self.poly_nuis.transform(feat_img[:, not_contrast])
            feat_img_out = np.hstack((feat_img_out, feat_nuisance))
        return feat_img_out


import pygame as pg
import random
import os

from pygame.constants import MOUSEBUTTONDOWN, QUIT

pg.init()

class Player():
    def __init__(self, image, x,y,width,hight,up_button,down_button):
        self.image = pg.transform.scale(pg.image.load(os.path.join('data', image)).convert_alpha(), (width, hight))

        self.win_times = 0

        self.x = x
        self.y = y

        self.rect = pg.rect.Rect(x,y,width,hight)

        self.up_button = up_button
        self.down_button = down_button

        self.up = False
        self.down = False

        self.font = pg.font.Font(None, 300)

    def getting(self, event):
        if event.type == pg.KEYDOWN:
            if event.key == self.up_button:
                self.up = True
            elif event.key == self.down_button:
                self.down = True
        elif event.type == pg.KEYUP:
            if event.key == self.up_button:
                self.up = False
            elif event.key == self.down_button:
                self.down = False

    def go(self):

        if (self.up and self.rect.y>0):
            self.rect.y-=4
        elif (self.down and self.rect.y<400):
            self.rect.y+=4

    def blit(self):
        mw.blit(self.image, (self.rect.x,self.rect.y))

    def blit_results(self, x):
        mw.blit(self.font.render(str(self.win_times), True , (200,100,100)), (x, 150))

class Boll():
    def __init__(self, image, x,y,width,hight):
        self.image = pg.transform.scale(pg.image.load(os.path.join('data',image)).convert_alpha(), (width, hight))

        self.rect = pg.rect.Rect(x,y,width,hight)

        self.up = False
        self.left = False

        self.speed = 2
        self.front_speed = 2

    def colised(self, max_hight, min_hight, player1, player2):

        global win_menu
        global game

        if self.rect.y <= min_hight:
            self.up = False
            self.front_speed = random.randint(2,3)
            self.speed = random.randint(2,3)
        elif self.rect.y >= max_hight:
            self.up = True
            self.front_speed = random.randint(2,3)
            self.speed = random.randint(2,3)

        if self.rect.colliderect(player1.rect):
            self.left = False
            self.front_speed = random.randint(2,3)
            self.speed = random.randint(2,3)
        elif self.rect.colliderect(player2.rect):
            self.left = True
            self.front_speed = random.randint(2,3)
            self.speed = random.randint(2,3)
        if self.rect.x <= 10:
            player2.win_times += 1
            game = False
            win_menu = True

        elif self.rect.x >= 560:
            player1.win_times +=1
            game = False
            win_menu = True

    def go(self):

        self.colised(450,0,player1,player2)

        if (self.up):
            self.rect.y-=self.speed
        elif not(self.up):
            self.rect.y+=self.speed
        if (self.left):
            self.rect.x-=self.front_speed
        if not(self.left):
            self.rect.x+=self.front_speed

    def blit(self):
        mw.blit(self.image,(self.rect.x, self.rect.y))

class Menu():
    def __init__(self, text_ferst_button, text_thecond_button, button_x1,button_x2, button_y1, button_y2, width, hight):
        self.button1 = pg.rect.Rect(button_x1, button_y1, width, hight)
        self.button2 = pg.rect.Rect(button_x2, button_y2, width, hight)

        self.font = pg.font.Font(None, 70)

        self.text_button1 = self.font.render(text_ferst_button, True ,(0,0,0))
        self.text_button2 = self.font.render(text_thecond_button, True ,(0,0,0))

    def getting (self, event):
        global setting_menu
        global start_menu
        global back_was_start_menu
        global game

        if event.type == pg.MOUSEBUTTONDOWN:
            if self.button1.collidepoint(pg.mouse.get_pos()):
                start_menu = False
                back_was_start_menu = False
                game = True
            elif self.button2.collidepoint(pg.mouse.get_pos()):
                setting_menu = True
                start_menu = False

    def blit(self):
        pg.draw.rect(mw,(50,50,150),self.button1)
        pg.draw.rect(mw,(50,50,150),self.button2)

        mw.blit(self.text_button1, (self.button1.x, self.button1.y))
        mw.blit(self.text_button2, (self.button2.x, self.button2.y))

class Pause_menu():
    def __init__(self, text_first_button, text_thecond_button, text_thirthd_button, x1, y1, x2, y2, x3, y3, text_x1, text_y1, text_x2, text_y2, text_x3, text_y3, width, hight):
        self.button1 = pg.rect.Rect(x1,y1,width,hight)
        self.button2 = pg.rect.Rect(x2,y2,width,hight)
        self.button3 = pg.rect.Rect(x3,y3,width,hight)

        self.font = pg.font.Font(None, 70)

        self.text1 = self.font.render(text_first_button, True,(0,0,0))
        self.text2 = self.font.render(text_thecond_button, True,(0,0,0))
        self.text3 = self.font.render(text_thirthd_button, True,(0,0,0))

        self.text1_x = text_x1
        self.text1_y = text_y1
        self.text2_x = text_x2
        self.text2_y = text_y2
        self.text3_x = text_x3
        self.text3_y = text_y3

    def getting(self, event, pause_button):
        global game
        global pause_menu
        global setting_menu

        if (event.type == pg.MOUSEBUTTONDOWN):
            if (pause_button.collidepoint(pg.mouse.get_pos())):
                game = not(game)
                pause_menu = not(pause_menu)

        if (event.type == pg.KEYDOWN):
            if (event.key == pg.K_ESCAPE):
                game = not(game)
                pause_menu = not(pause_menu)

        if (pause_menu):
            if (event.type == pg.MOUSEBUTTONDOWN):
                if (self.button1.collidepoint(pg.mouse.get_pos())):
                    pause_menu = False
                    game = True

                elif (self.button2.collidepoint(pg.mouse.get_pos())):
                    pause_menu = False
                    setting_menu = True

                elif (self.button3.collidepoint(pg.mouse.get_pos())):
                    quit()

    def blit(self):
        pg.draw.rect(mw,(50,50,150), self.button1)
        pg.draw.rect(mw,(50,50,150), self.button2)
        pg.draw.rect(mw,(50,50,150), self.button3)

        mw.blit(self.text1,(self.text1_x, self.text1_y))
        mw.blit(self.text2,(self.text2_x, self.text2_y))
        mw.blit(self.text3,(self.text3_x, self.text3_y))

class Setting_menu():
    def __init__(self):
        self.main_surface = pg.Surface((550,400))
        self.main_surface.fill((20,20,40))

        self.volume_rect1 = pg.rect.Rect(200, 140, 200, 20)
        self.seting_rect1 = pg.rect.Rect(200, 140, 100, 20)

        self.volume_rect2 = pg.rect.Rect(200, 170, 200, 20)
        self.seting_rect2 = pg.rect.Rect(200, 170, 100, 20)

        self.button_out = pg.rect.Rect(200,250,200,35)

        self.font = pg.font.Font(None, 70)

        self.text1 = self.font.render('music', True, (0,0,0))
        self.text2 = self.font.render('effects', True, (0,0,0))
        self.text3 = self.font.render('exit', True, (0,0,0))

        self.button_down = False

        self.volume_music = 0.5
        self.volume_efects = 0.25

    def getting(self, event, flag):
        global setting_menu
        global start_menu
        global pause_menu

        if event.type == pg.MOUSEBUTTONDOWN:
            if event.button == 1:
                self.button_down = True
        if event.type == pg.MOUSEBUTTONUP:
            if event.button == 1:
                self.button_down = False

        if self.button_down:
            self.cursor_pos_x, self.cursor_pos_y = pg.mouse.get_pos()

            self.cursor_pos_x -= 25
            self.cursor_pos_y -= 65

            if self.cursor_pos_x >= 200 and self.cursor_pos_x <= 400:
                    if self.cursor_pos_y >= 140 and self.cursor_pos_y <= 160:
                        self.seting_rect1.width = self.cursor_pos_x-200

                        self.volume_music = self.seting_rect1.width/200

                    elif self.cursor_pos_y >= 170 and self.cursor_pos_y <= 190:
                        self.seting_rect2.width = self.cursor_pos_x-200

                        self.volume_efects = self.seting_rect2.width/200

            if self.button_out.collidepoint(self.cursor_pos_x, self.cursor_pos_y):
                setting_menu = False
                if flag == True:
                    start_menu = True
                else:
                    pause_menu = True

        return self.volume_efects

    def blit(self):
        mw.blit(self.main_surface, (25,65))

        pg.draw.rect(self.main_surface, (255,255,255), self.volume_rect1)
        pg.draw.rect(self.main_surface, (255,255,255), self.volume_rect2)

        pg.draw.rect(self.main_surface, (200,100,100), self.seting_rect1)
        pg.draw.rect(self.main_surface, (200,100,100), self.seting_rect2)

        pg.draw.rect(self.main_surface, (200,100,100), self.button_out)

        pg.mixer.music.set_volume(self.volume_music)

        self.main_surface.blit(self.text1, (200,90))
        self.main_surface.blit(self.text2, (200, 200))
        self.main_surface.blit(self.text3, (240, 245))

class Win_menu():
    def __init__(self, text_ferst_button, text_thecond_button, button_x1,button_x2, button_y1, button_y2, width, hight):
        self.button1 = pg.rect.Rect(button_x1, button_y1, width, hight)
        self.button2 = pg.rect.Rect(button_x2, button_y2, width, hight)

        self.font = pg.font.Font(None, 70)

        self.text_button1 = self.font.render(text_ferst_button, True ,(0,0,0))
        self.text_button2 = self.font.render(text_thecond_button, True ,(0,0,0))

    def getting (self, event):
        global win_menu
        global game

        if event.type == pg.MOUSEBUTTONDOWN:
            if self.button1.collidepoint(pg.mouse.get_pos()):
                boll.rect.x = 275
                boll.rect.y = 225

                boll.speed = 1
                boll.front_speed = 1

                win_menu = False
                game = True
            elif self.button2.collidepoint(pg.mouse.get_pos()):
                quit()

    def blit(self):
        pg.draw.rect(mw,(50,50,150),self.button1)
        pg.draw.rect(mw,(50,50,150),self.button2)

        mw.blit(self.text_button1, (self.button1.x, self.button1.y))
        mw.blit(self.text_button2, (self.button2.x, self.button2.y))

game = False
win_menu = False
pause_menu = False
start_menu = True
setting_menu = False
back_was_start_menu = True

pause_button = pg.rect.Rect(0,0,50,50)
pause_button_image = pg.transform.scale(pg.image.load(os.path.join('data','pause.png')), (50,50))

mw = pg.display.set_mode((600,500))
mw.fill((200,255, 255))

setting__menu = Setting_menu()
menu = Menu('start', 'settings',200,200,175,275,200,50)
win__menu = Win_menu('continue', 'exit',200,200,175,275,200,50)
pause__menu = Pause_menu('continue', 'settings', 'exit', 200, 150, 200, 225, 200, 300, 200, 150, 200, 225, 250, 300, 200, 50)

player1 = Player('pngwin.png',0,200,50,100,pg.K_w,pg.K_s)
player2 = Player('pngwin.png',550,200,50,100,pg.K_UP,pg.K_DOWN)

boll = Boll('boll.png', 275,225,50,50)

pg.mixer.music.load(os.path.join('data', 'music.mp3'))
pg.mixer.music.play(-1)
pg.mixer.music.set_volume(0.5)

clock = pg.time.Clock()

while True:

    pg.draw.rect(mw,(200,255,255),(pg.rect.Rect(0,0,600,500)))

    for event in pg.event.get():
        if event.type == pg.QUIT:
            quit()
        player1.getting(event)
        player2.getting(event)

        if (start_menu):
            menu.getting(event)

        if not(start_menu) and not(win_menu) and not(setting_menu):
            pause__menu.getting(event, pause_button)

        if setting_menu:
            setting__menu.getting(event, back_was_start_menu)

        if win_menu:
            win__menu.getting(event)

    if game:
        player1.go()
        player2.go()

        player1.blit()
        player2.blit()

        player1.blit_results(100)
        player2.blit_results(400)

        boll.go()

        boll.blit()

        start_menu = False

    elif (start_menu):
        menu.blit()

    if (pause_menu) and not(setting_menu):
        pause__menu.blit()

    if not(start_menu) and not(win_menu):
        mw.blit(pause_button_image,(0,0))

    if setting_menu:
        setting__menu.blit()

    if win_menu:
        win__menu.blit()

    clock.tick(100)
    pg.display.update()


from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from Primitives3D import *
from Shapes3D import *
import math

def getCameraMatrix(t, u, r, P):
	rotMat = Matrix4([r.x, u.x, -t.x, 0, r.y, u.y, -t.y, 0, r.z, u.z, -t.z, 0, 0, 0, 0, 1])
	rotMat = rotMat.Inverse()
	transMat = Matrix4([1, 0, 0, -P.x, 0, 1, 0, -P.y, 0, 0, 1, -P.z, 0, 0, 0, 1])
	#Translate first then rotate
	mat = rotMat*transMat
	return mat

#This function pushes a matrix onto the stack that puts everything
#in the frame of a camera which is centered at position "P",
#is pointing towards "t", and has vector "r" to the right
#t - towards vector
#u - up vector
#r - right vector
#P - Camera center
def gotoCameraFrame(t, u, r, P):
	rotMat = Matrix4([r.x, u.x, -t.x, 0, r.y, u.y, -t.y, 0, r.z, u.z, -t.z, 0, 0, 0, 0, 1])
	rotMat = rotMat.Inverse()
	transMat = Matrix4([1, 0, 0, -P.x, 0, 1, 0, -P.y, 0, 0, 1, -P.z, 0, 0, 0, 1])
	#Translate first then rotate
	mat = rotMat*transMat
	#OpenGL is column major and mine are row major so take transpose
	mat = mat.Transpose()
	glMatrixMode(GL_MODELVIEW)
	glLoadIdentity()
	glMultMatrixd(mat.m)

class Key6DOFCamera(object):
	def __init__(self, eye, towards = Vector3D(0, 0, 1), up = Vector3D(0, 1, 0), yfov = 0.75):
		self.eye = eye
		self.towards = towards
		self.up = up
		self.yfov = yfov
		self.keys = {}
		self.LINEAR_RATE = 0.02
		self.ANGULAR_RATE = 0.01
		self.minTimestepLen = 1 #Minimum Timestep Length (msecs)
		self.zP = Point3D(0, 0, 0) #Zero Point (used for axis rotation)

	def gotoCameraFrame(self):
		gotoCameraFrame(self.towards, self.up, self.towards%self.up, self.eye)

	def Roll(self, theta):
		self.up = rotateAroundAxis(self.zP, self.towards, theta, self.up)

	def Pitch(self, theta):
		right = self.towards % self.up
		self.up = rotateAroundAxis(self.zP, right, theta, self.up)
		self.towards = rotateAroundAxis(self.zP, right, theta, self.towards)

	def Yaw(self, theta):
		self.towards = rotateAroundAxis(self.zP, self.up, theta, self.towards)

	def keyPressed(self, key1, key2 = 'null'):
		if (key1 in self.keys and self.keys[key1]) or (key2 in self.keys and self.keys[key2]):
			return True
		return False

	def handleUserInput(self, value):
		if self.keyPressed('w', 'W'):
			self.eye = self.eye + self.LINEAR_RATE*self.towards
		if self.keyPressed('s', 'S'):
			self.eye = self.eye - self.LINEAR_RATE*self.towards
		if self.keyPressed('a', 'A'):
			self.eye = self.eye - self.LINEAR_RATE*(self.towards%self.up)
		if self.keyPressed('d', 'D'):
			self.eye = self.eye + self.LINEAR_RATE*(self.towards%self.up)
		if self.keyPressed(' '):
			self.eye = self.eye + self.LINEAR_RATE*self.up
		if self.keyPressed('c', 'C'):
			self.eye = self.eye - self.LINEAR_RATE*self.up
		if self.keyPressed('e', 'E'):
			self.Roll(-self.ANGULAR_RATE)
		if self.keyPressed('q', 'Q'):
			self.Roll(self.ANGULAR_RATE)
		if self.keyPressed(GLUT_KEY_DOWN):
			self.Pitch(self.ANGULAR_RATE)
		if self.keyPressed(GLUT_KEY_UP):
			self.Pitch(-self.ANGULAR_RATE)
		if self.keyPressed(GLUT_KEY_LEFT):
			self.Yaw(self.ANGULAR_RATE)
		if self.keyPressed(GLUT_KEY_RIGHT):
			self.Yaw(-self.ANGULAR_RATE)
		glutTimerFunc(self.minTimestepLen, self.handleUserInput, 1)
		glutPostRedisplay()

	def __str__(self):
		right = self.towards % self.up
		str = "Eye: %s\n"%(self.eye)
		str = str+"Towards: (%g) %s\n"%(self.towards.Length(), self.towards)
		str = str + "Up: (%g) %s\n"%(self.up.Length(), self.up)
		str = str + "Right: (%g) %s"%(right.Length(), right)
		return str

class MousePolarCamera(object):
	#Coordinate system is defined as in OpenGL as a right
	#handed system with +z out of the screen, +x to the right,
	#and +y up
	#phi is CCW down from +y, theta is CCW away from +z
	def __init__(self, pixWidth, pixHeight, yfov = 0.75):
		self.pixWidth = pixWidth
		self.pixHeight = pixHeight
		self.yfov = yfov
		self.center = Point3D(0, 0, 0)
		self.R = 1
		self.theta = 0
		self.phi = 0
		self.updateVecsFromPolar()

	def centerOnBBox(self, bbox, theta = -math.pi/2, phi = math.pi/2):
		self.center = bbox.getCenter()
		self.R = bbox.getDiagLength()*3
		self.theta = theta
		self.phi = phi
		self.updateVecsFromPolar()

	def centerOnMesh(self, mesh):
		bbox = mesh.getBBox()
		self.centerOnBBox(bbox)

	def centerOnPointCloud(self, pcl):
		bbox = pcl.getBBox()
		self.centerOnBBox(bbox)

	def updateVecsFromPolar(self):
		[sinT, cosT, sinP, cosP] = [math.sin(self.theta), math.cos(self.theta), math.sin(self.phi), math.cos(self.phi)]
		#Make the camera look inwards
		#i.e. towards is -dP(R, phi, theta)/dR, where P(R, phi, theta) is polar position
		self.towards = Vector3D(-sinP*cosT, -cosP, sinP*sinT)
		self.up = Vector3D(-cosP*cosT, sinP, cosP*sinT)
		self.eye = self.center - self.R*self.towards

	def gotoCameraFrame(self):
		gotoCameraFrame(self.towards, self.up, self.towards%self.up, self.eye)

	def orbitUpDown(self, dP):
		dP = 1.5*dP/float(self.pixHeight)
		self.phi = self.phi+dP
		self.updateVecsFromPolar()

	def orbitLeftRight(self, dT):
		dT = 1.5*dT/float(self.pixWidth)
		self.theta = self.theta-dT
		self.updateVecsFromPolar()

	def zoom(self, rate):
		rate = rate / float(self.pixHeight)
		self.R = self.R*pow(4, rate)
		self.updateVecsFromPolar()

	def translate(self, dx, dy):
		length = (self.center-self.eye).Length()*math.tan(self.yfov);
		dx = length*dx / float(self.pixWidth)
		dy = length*dy / float(self.pixHeight)
		r = self.towards % self.up
		self.center = self.center - dx*r - dy*self.up
		self.updateVecsFromPolar()

	def __str__(self):
		right = self.towards % self.up
		str = "R = %g, theta = %g, phi = %g\n"%(self.R, self.theta, self.phi)
		str = str + "Eye: %s\n"%(self.eye)
		str = str + "Towards: (%g) %s\n"%(self.towards.Length(), self.towards)
		str = str + "Up: (%g) %s\n"%(self.up.Length(), self.up)
		str = str + "Right: (%g) %s"%(right.Length(), right)
		str = str + "\nCenter: %s\n"%(self.center)
		str = str + "Eye.Dot(Towards) = %g\n"%(self.towards.Dot(self.eye)/self.eye.Length())
		return str

#This is different from a polar camera because it continually rotates the pole
#to align with where the user is looking (easier to use locally but harder
#to align to axes once you've stared moving)
class MouseSphericalCamera(object):
	def __init__(self, pixWidth, pixHeight, yfov = 0.75):
		self.pixWidth = pixWidth
		self.pixHeight = pixHeight
		self.yfov = yfov
		self.zP = Point3D(0, 0, 0) #Zero Point (used for axis rotation)
		self.center = Point3D(0, 0, 0)
		self.eye = Point3D(0, 0, 1)
		self.towards = Vector3D(0, 0, -1)
		self.up = Vector3D(0, 1, 0)

	def centerOnBBox(self, bbox):
		self.center = bbox.getCenter()
		self.towards = Vector3D(0, 0, -1)
		self.up = Vector3D(0, 1, 0)
		self.eye = self.center - (bbox.getDiagLength()*3)*self.towards

	def centerOnMesh(self, mesh):
		bbox = mesh.getBBox()
		self.centerOnBBox(bbox)

	def centerOnPointCloud(self, pcl):
		bbox = pcl.getBBox()
		self.centerOnBBox(bbox)

	def gotoCameraFrame(self):
		gotoCameraFrame(self.towards, self.up, self.towards%self.up, self.eye)

	def orbitUpDown(self, dTheta):
		dTheta = 1.5*dTheta / float(self.pixHeight)
		right = self.towards % self.up
		self.eye = rotateAroundAxis(self.center, right, dTheta, self.eye)
		self.up = rotateAroundAxis(self.zP, right, dTheta, self.up)
		self.towards = rotateAroundAxis(self.zP, right, dTheta, self.towards)

	def orbitLeftRight(self, dTheta):
		dTheta = 1.5*dTheta / float(self.pixWidth)
		self.eye = rotateAroundAxis(self.center, self.up, -dTheta, self.eye)
		self.towards = rotateAroundAxis(self.zP, self.up, -dTheta, self.towards)

	def zoom(self, rate):
		rate = rate / float(self.pixHeight)
		R = self.eye - self.center
		R = pow(4, rate)*R
		self.eye = self.center + R

	def translate(self, dx, dy):
		length = (self.center-self.eye).Length()*math.tan(self.yfov);
		dx = length*dx / float(self.pixWidth)
		dy = length*dy / float(self.pixHeight)
		right = self.towards % self.up
		self.eye = self.eye - dx*right - dy*self.up
		self.center = self.center - dx*right - dy*self.up

	def __str__(self):
		right = self.towards % self.up
		str = "Eye: %s\n"%(self.eye)
		str = str + "Towards: (%g) %s\n"%(self.towards.Length(), self.towards)
		str = str + "Up: (%g) %s\n"%(self.up.Length(), self.up)
		str = str + "Right: (%g) %s"%(right.Length(), right)
		return str