import random
import numpy as np
from pylatex import Document, Math, Matrix, Subsection, NoEscape, Tabular, Section, Package
import csv
from datetime import date
from color_matrix import ColorMatrix

class MatrixHandler():
    def __init__(self, f=None,
                pdf_filename='matrices',
                csv_filename='select_entries.csv',
                percent_change=20,
                perm_fixed_entries=[(0,0)],
                constrain_digits=True):

        self.f = f
        self.pdf_filename = pdf_filename
        self.csv_filename = csv_filename
        self.percent_change = percent_change
        self.perm_fixed_entries = perm_fixed_entries
        self.constrain_digits = constrain_digits

    def get_rand_positive_seeds(self, n: int, f=None):
        total_samples = 2*n-1
        if f == None:
            f = self.f
        if n == 1:
            return [f]
        if f-1 < total_samples:
            zeros = [ 0 for i in range(total_samples - f + 1) ]
        else:
            zeros = []

            # 2*n-1 - f
        # split f into a sum of 2n integers.
        # there are f-1 choose 2n-1 ways to do this, so we will choose one randomly.

        # get random ints that split f into 2n segments
        split_points = sorted([0] + list(random.sample(zeros + list(range(1, f)), total_samples)) + [f])

        # convert into a list of 2n ints that sum up to f
        seeds = [ split_points[i+1] - split_points[i] for i in range(2*n) ]
        # there could be up to 2n! ways of ordering this, so once again we will choose randomly
        random.shuffle(seeds)

        return np.array(seeds)

    def get_rand_int_seeds(self, n: int, f: int, bound: int):
        if f == None:
            f = self.f
        # split f into a sum of 2n integers.
        # since the integers can be negative, we need to bound their amplitude
        # so as to avoid putting stupidly large numbers in the matrix
        init = np.array([ random.choice(range(bound)) for i in range(2*n) ])
        zero_sum = init - np.array(self.get_rand_positive_seeds(n, sum(init))) # these seeds sum to 0 and each seed has an amplitude < bound

        # now add a positive seed list to it and we will get seeds that sum to f but can have negative numbers
        return zero_sum + np.array(self.get_rand_positive_seeds(n, f))

    # returns a matrix where the seeds are not present. no random choices
    def get_exterior_matrix(self, seeds: list, n: int):
        col_seeds = seeds[0:n]
        row_seeds = seeds[n:2*n]

        # sum up the seeds to produce a force matrix (exterior seeds)
        return np.array([ [col_seed + row_seed for col_seed in col_seeds] for row_seed in row_seeds ])

    # returns a matrix that contains the seeds, and a 0.
    # Where these seeds are positioned in the matrix is chosen randomly if rand_order, else middle
    # requires 2 fewer seeds than for exterior matrices
    def get_interior_matrix(self, seeds: list, n: int, rand_order=True):
        if len(seeds) != 2*(n-1):
            print("get_interior_matrix expected 2*(n-1) seeds but got: " + str(len(seeds)) + " with n=" + str(n))
            raise TypeError()
        n = n-1
        col_seeds = list(seeds[0:n])
        row_seeds = list(seeds[n:2*n])

        if rand_order:
            seed_col = random.choice(range(n))
            seed_row = random.choice(range(n))
        else:
            seed_col = int(n/2 + 1)
            seed_row = int(n/2 + 1)

        col_seeds.insert(seed_row, 0)
        row_seeds.insert(seed_col, 0)

        M = np.array([ [col_seed + row_seed for col_seed in col_seeds] for row_seed in row_seeds ])

        # M[:, seed_col] -= col_seeds[seed_col]
        # M[seed_row, :] -= row_seeds[seed_row]

        return M

    # shuffles the given matrix but keeps the indicated items unchanged
    def get_shuffled_matrix(self, matrix: np.array, fixed_entries=[], constrain_digits=None):
        if constrain_digits == None:
            constrain_digits = self.constrain_digits
        if constrain_digits:
            for i in range(len(str(np.amax(matrix)))):
                curr_fixed = [ (j, k) for j in range(len(matrix)) for k in range(len(matrix[0])) if not (abs(matrix[j][k]) >= pow(10, i) and abs(matrix[j][k]) < pow(10, (i+1)) )] + list(fixed_entries)
                curr_fixed = list(set(curr_fixed))
                matrix = self.shuffle_helper(matrix, curr_fixed)
            return matrix
        else:
            return self.shuffle_helper(matrix, fixed_entries)

    def shuffle_helper(self, matrix: np.array, fixed_entries=[]):
        n, m = np.shape(matrix)
        entries = list(np.matrix.flatten(matrix))
        # convert the entries into indices corresponding to the flattened matrix
        flattened_fixed_entries = [ n*entry[0] + entry[1] for entry in fixed_entries ]
        # reverse sorted order so that we can remove items from entries while iterating over it
        # (effectively itterating over entries backwards)
        flattened_fixed_entries.sort(reverse=True)
        fixed_entry_dict = { fixed: entries.pop(fixed) for fixed in flattened_fixed_entries }
        # shuffle the entries now that the fixed entries are removed
        random.shuffle(entries)
        # return the fixed entries to their place
        for i, val in reversed(list(fixed_entry_dict.items())):
            entries.insert(i, val)

        # return the entries in matrix form
        return np.reshape(entries, (n, m))

    # replaces a given percentage of items with random numbers, but keeps indicated
    # items unchanged
    def get_altered_matrix(self, matrix: np.array, bound: int, fixed_entries=[], percent_change=None, constrain_digits=None):
        if constrain_digits == None:
            constrain_digits = self.constrain_digits
        if percent_change == None:
            percent_change = self.percent_change

        n, m = np.shape(matrix)
        quant_to_change = int((n*m-len(fixed_entries))*percent_change/100)
        items_to_change = random.sample([(i,j) for i in range(n) for j in range(m) if (i,j) not in fixed_entries], quant_to_change)
        # if the matrix only has positive entries, generate random positive integers only
        if (matrix >= 0).all():
            interval = list(range(1, bound))
            pos_signs = [1]
        else:
            interval = list(range(-1*bound, -1)) + list(range(1, bound))
            pos_signs = [1, -1]
            # else allow negative integers as well

        if constrain_digits:
            for to_change in items_to_change:
                digits = str(abs(matrix[to_change]))
                matrix[to_change] = random.choice(pos_signs) * int(''.join([str(random.choice(list(range(1*int(digits.index(d) == 0), 10)))) for d in digits]))
                                    # random sign               ranomize each digit from 0 through 9, except first digit never 0
        else:

            for to_change in items_to_change:
                matrix[to_change] = random.choice(interval)

        return matrix

    def print_to_pdf(self, matrices: list, color_matrices: list, filename=None, add_borders=True):
        if filename == None:
            filename = self.pdf_filename

        doc = Document()
        doc.packages.append(Package('xcolor', options='table'))
        doc.packages.append(Package('transparent'))
        # doc.packages.append(Package('geometry', options='margin=0.5in'))
        doc.append(NoEscape('\setcounter{secnumdepth}{0}'))
        doc.append(NoEscape('\pagenumbering{gobble}'))
        doc.append(NoEscape('\setlength{\\arrayrulewidth}{1pt}'))
        # define colors from color dict
        colors = ColorMatrix()
        for color in colors.strDict:
            doc.append(NoEscape(colors.strDict[color]))

        today = date.today()
        with doc.create(Section(NoEscape('{\\fontsize{20pt}{15pt}\selectfont' + ' Randomly generated numbers collected on ' + str(today) + ' and adapted from:}'))):
            doc.append(NoEscape('{\\fontsize{14pt}{12pt}\selectfont' + ' https://onlinemathtools.com/generate-random-matrix}'))

        with doc.create(Section(NoEscape('{\\fontsize{20pt}{15pt}\selectfont' + ' Zoom color palette shuffled with and adapted from:}'))):
            doc.append(NoEscape('{\\fontsize{14pt}{12pt}\selectfont' + ' https://onlinerandomtools.com/shuffle-words}'))
            doc.append(NoEscape("\\newpage"))

        for i in range(len(matrices)):
            M = matrices[i]
            C = color_matrices[i]
            # set the specs for the table that holds the matrix entries
            if add_borders:
                border = '|'
            else:
                border = ''

            with doc.create(Subsection('Option #' + str(i+1))):
                ############ create number table
                with doc.create(Tabular(border + border.join(['c' for t in M[0]]) + border)) as table:
                    if add_borders:
                        table.add_hline()
                    for row in M:
                        # if there are negative entries, put a + in front of positives
                        if (M < 0).any():
                            row = [ '+' + str(num) if num >= 0 else '–' + str(-1*num) for num in row ]
                            if "+0" in row:
                                row[row.index("+0")] = '0'
                                # NoEscape('\cellcolor{green}0')

                        table.add_row(row)
                        if add_borders:
                            table.add_hline()

                # doc.append(NoEscape("\hfill")) # same line right side
                doc.append(NoEscape("\\break")) # new line
                doc.append(NoEscape("\\vspace{1cm}")) # new line
                # doc.append('.') # new line
                doc.append(NoEscape("\\break")) # new line
                ############ create color table
                # fin correct table width
                if (M < 0).any():
                    if (M > 1000).any():
                        spacer = '23.2'
                    else:
                        spacer = '23.1'
                else:
                    spacer = '10'

                with doc.create(Tabular(border + border.join(['m{' + spacer + 'px}' for t in C[0]]) + border)) as table:
                    if add_borders:
                        table.add_hline()
                    for row in C:
                        table.add_row([ NoEscape('\cellcolor{'+ color + '}') for color in row])
                        if add_borders:
                            table.add_hline()

        doc.generate_pdf(filename, clean_tex=False)

    def get_fixed_entries(self, matrix: np.array, filename=None):
        input_matrix = matrix
        if filename == None:
            filename = self.csv_filename

        # make sure some key entries are fixed, for example if there is a 0 entry
        perm_fixed_entries = [ tuple(ntry) for ntry in self.perm_fixed_entries ]
        if (matrix == 0).any():
            coord0 = np.where(matrix == 0)
            try:
                perm_fixed_entries.append((int(coord0[0]), int(coord0[1])))
            except:
                coord0 = np.transpose(coord0)
                for coords in coord0:
                    perm_fixed_entries.append((int(coords[0]), int(coords[1])))

        perm_fixed_entries = list(set(perm_fixed_entries))

        # convert ndarray to vanilla python
        matrix = [ [item for item in row] for row in matrix ]
        # mark fixed entries with *
        for entry in perm_fixed_entries:
            matrix[entry[0]][entry[1]] = '[' + str(matrix[entry[0]][entry[1]]) + ']'

        with open(filename, 'w', newline='') as csvfile:
            writer = csv.writer(csvfile, delimiter=',',
                            quotechar='"', quoting=csv.QUOTE_MINIMAL)
            writer.writerows(matrix)
            # add color choice columns
            color_handler = ColorMatrix()
            # color_choices = np.transpose(np.array([ list(color_handler.strDict.keys()) for i in matrix ]))
            # # convert ndarray to vanilla python
            # color_choices = [ [item for item in row] for row in color_choices ] #just one line
            # writer.writerows(color_choices)

            writer.writerow(list(color_handler.strDict.keys()))

        print('generated \'select_entries.csv\'')
        # print('add a \'x\' to the number entries of \'select_entries.csv\' that should remain invariant')
        # print('some entries have already been marked as invariant with brackets: []')
        print('add coordinates (period seperated) to the color entries to select the color of the invariant entry said coordinates (ex: white1.2')
        input('press enter to save invariant entries and their colors: ')

        # fixed_entries = []
        # chosen_entries = []
        rows = []
        with open(filename, newline='') as csvfile:
            reader = csv.reader(csvfile, delimiter=',', quotechar='"')
            for row in reader:
                rows.append(row)

            # collect selected number entries
            # for i in range(len(matrix)):
            #     for j in range(len(matrix[0])):
            #         if 'x' in rows[i][j]:
            #             chosen_entries.append((i, j))
            #         if 'x' in rows[i][j] or '[' in rows[i][j]:
            #             fixed_entries.append((i, j))

            # # sanity check
            # if len(chosen_entries) != len(matrix):
            #     print('the matrix is of size ' + str(len(matrix)) + ' but ' + str(len(chosen_entries)) + ' entries were selected')
            #     print('retrying selection: ')
            #     print()
            #     return self.get_fixed_entries(np.array(input_matrix))

            # collect selected color entries
            # coord_dict = {}
            # for i in range(len(rows[0]), len(rows)):
            #     for j in range(len(rows[0])):
            #         if 'x' in rows[i][j]:
            #             color = list(color_handler.strDict.keys())[i-len(rows[0])]
            #             matching_coords = [ coords for coords in chosen_entries if coords[1] == j ]
            #             if len(matching_coords) > 1:
            #                 print("Warning: expected one item but found: " + len(coords))
            #                 print("Using first item.")
            #             coords = matching_coords.pop()

            #             coord_dict[coords] = color
            color_row = rows[-1]
            # turn color row into list of coords
            coord_list = [ item for item in color_row if '.' in item ] # get entries that have .
            coord_list = [ [d for d in item if d.isdigit() or d == '.'] for item in coord_list ] # extract only the numbers and . from the entry
            coord_list = [ "".join(item) for item in coord_list ] # join the coords into a single string
            coord_list = [ item.split('.') for item in coord_list ] # split the string around the .
            coord_list = [ [int(n)-1 for n in item] for item in coord_list ] # convert to int and subtract 1 to get 0 indexed column and row numbers
            coord_list = [ tuple(item) for item in coord_list ] # turn the coordinate list of list into list of tuples
            # sanity check
            if len(coord_list) != len(matrix):
                print('the matrix is of size ' + str(len(matrix)) + ' but ' + str(len(coord_list)) + ' colors were selected')
                print('retrying selection: ')
                print()
                return self.get_fixed_entries(np.array(input_matrix))

            # get indexes of entries that have '.'
            color_indexes = tuple( i for i in range(len(color_row)) if '.' in color_row[i] )
            # turn list of coords into a color dict of coords
            color_names = list(color_handler.strDict.keys())
            coord_dict = { coord_list[i]: color_names[color_indexes[i]] for i in range(len(coord_list))}

            # confirm correctness of entered info
            # OKGREEN = '\033[92m'
            # ENDC = '\033[0m'
            # colored_mat = matrix
            # for coords in coord_dict:
                # chosen = ', ' + str(matrix[coords[0]][coords[1]]) + ', '
                # start = str(matrix[coords[0]][:coords[1]]).replace('[', '').replace(']', '')
                # end = str(matrix[coords[0]][(coords[1]+1):]).replace('[', '').replace(']','')
                # colored_mat[coords[0]] = f'{start}{color_handler.code_dict[coord_dict[coords]]}{chosen}{ENDC}{end}'

            colored_mat = list(matrix)
            for coords in coord_dict:
                colored_mat[coords[0]][coords[1]] = [colored_mat[coords[0]][coords[1]]]

            for row in colored_mat:
                print(row)

            if input('Press enter if this is correct. Enter \'n\' to restart selection: '):
                return self.get_fixed_entries(np.array(input_matrix))

        return list(coord_dict.keys()), coord_dict


import numpy as np

def double_sine(p, x):

    sine1 = p[0] * np.sin((p[1] * x) + p[2]) + p[3]
    sine2 = p[4] * np.sin((p[5] * x) + p[6]) + p[7]

    return sine1 + sine2


import pdfplumber
from time import sleep
import dotenv
import os

dotenv.load_dotenv(dotenv.find_dotenv())
arquivo = os.getenv("ARQUIVO_PDF_TEXTO ")

pdf = pdfplumber.open(arquivo)
numero_paginas = len(pdf.pages)

pagina = 0
while pagina < numero_paginas:
    print(f"\nPágina {pagina}:")
    texto = pdf.pages[pagina].extract_text()
    # print(texto)
    linhas = texto.split("\n")
    for linha in linhas:
        print(linha)
    pagina += 1
    sleep(2)

print("Fim das páginas.")


import datetime
import sphinx_bootstrap_theme

# Sphinx project configuration
templates_path = ["_templates"]
exclude_patterns = ["_build"]
source_suffix = ".rst"
master_doc = "index"

# General information about the project
year = datetime.date.today().year
project = "The Generic Mapping Tools"
copyright = "2019 - {:d}, The GMT Developers".format(year)
version = ""

html_title = project
html_short_title = ""
html_logo = "_static/gmt-logo.png"
html_favicon = "_static/favicon.png"
html_static_path = ["_static"]
html_extra_path = [".nojekyll", "CNAME", "team.html"]
html_use_smartypants = True
pygments_style = "default"

# Theme config
html_theme = "bootstrap"
html_theme_path = sphinx_bootstrap_theme.get_html_theme_path()
html_theme_options = {
    "bootswatch_theme": "flatly",
    "navbar_title": "",
    "navbar_site_name": "Site",
    "navbar_links": [
        ("Home", "/", True),
        ("About", "about/", True),
        ("Download", "download/", True),
        ("Mirrors", "mirrors/", True),
        ("Citing", "cite/", True),
        ("Documentation", "documentation/", True),
        ("Ecosystem", "projects/", True),
        ("Workshops", "workshops/", True),
    ],
    # Render the next and previous page links in navbar. (Default: true)
    "navbar_sidebarrel": False,
    # Render the current pages TOC in the navbar. (Default: true)
    "navbar_pagenav": False,
    # Tab name for the current pages TOC. (Default: "Page")
    "navbar_pagenav_name": "This page",
    # Global TOC depth for "site" navbar tab. (Default: 1)
    # Switching to -1 shows all levels.
    "globaltoc_depth": 1,
    # Include hidden TOCs in Site navbar?
    # Note: If this is "false", you cannot have mixed ``:hidden:`` and
    # non-hidden ``toctree`` directives in the same page, or else the build
    # will break.
    # Values: "true" (default) or "false"
    "globaltoc_includehidden": "false",
    # HTML navbar class (Default: "navbar") to attach to
 element.
    # For black navbar, do "navbar navbar-inverse"
    "navbar_class": "navbar navbar-default",
    # Fix navigation bar to top of page?
    # Values: "true" (default) or "false"
    "navbar_fixed_top": "false",
    # Location of link to source.
    # Options are "nav" (default), "footer" or anything else to exclude.
    "source_link_position": "footer",
    "bootstrap_version": "3",
}
html_context = {
    "social_links": [
        (
            "Try Online!",
            "Try Online",
            "https://github.com/GenericMappingTools/try-gmt",
        ),
        (
            "Forum",
            "Forum",
            "https://forum.generic-mapping-tools.org/",
        ),
        (
            '',
            "GitHub",
            "https://github.com/GenericMappingTools",
        ),
        (
            '',
            "Twitter",
            "https://twitter.com/gmt_dev",
        ),
        (
            '',
            "YouTube",
            "https://www.youtube.com/c/TheGenericMappingTools",
        ),
        (
            '',
            "Instagram",
            "https://www.instagram.com/genericmappingtools/",
        ),
    ],
    "url": "https://www.generic-mapping-tools.org",
    "last_updated": str(datetime.date.today()),
    "repository": "GenericMappingTools/website",
}

# Load the custom CSS files (needs sphinx >= 1.6 for this to work)
def setup(app):
    app.add_css_file("style.css")
    app.add_css_file("fontawesome/css/all.css")


from vibes import vibes
from pyibex import *
# Libraries we need
# import pyxhook
import time
import math
import curses
import curses.ascii

from curses import wrapper
from Catan2 import Catan2

def print2(itv):
  return "[%s; %s]"%(itv.lb().hex(),  itv.ub().hex())

def main(stdscr):
    # Clear screen
    stdscr.clear()
    stdscr.keypad(1)
    begin_x = 20; begin_y = 7
    height = 5; width = 40
    win = curses.newwin(height, width, begin_y, begin_x)
    stdscr.addstr(0,10,"Hit 'space' to quit")
    stdscr.refresh()
    cpt = 0
    key = ''
    x = Interval(1,1.5)
    y = Interval(2,3)
    dth = Interval.HALF_PI.mid()/32.
    th = Interval(0).inflate(2*dth)

    Interval.__str__ = print2
    fout = open("test.txt", "a")
    fout.write("#================ RUN ===============\n")

    while key != ord(' '):

        key = stdscr.getch()
        lower_key = chr(key).lower()

        if lower_key == 'z' :
          y += 0.5 if curses.ascii.isupper(key) else 0.1
        elif lower_key == 's' :
          y -= 0.5 if curses.ascii.isupper(key) else 0.1
        elif lower_key == 'd' :
          x += 0.5 if curses.ascii.isupper(key) else 0.1
        elif lower_key == 'q' :
          x -= 0.5 if curses.ascii.isupper(key) else 0.1
        elif lower_key == 'a':
          th += dth/8. if curses.ascii.isupper(key) else dth
        elif lower_key == 'e':
          th -= dth/8. if curses.ascii.isupper(key) else dth

        elif key == curses.KEY_NPAGE:
          th = th.lb() + (Interval(0,th.diam()-dth) | Interval(0))
        elif key == curses.KEY_PPAGE:
          th = th.lb() + (Interval(0,th.diam()+dth) | Interval(0))
        elif key == curses.KEY_RIGHT:
          x = x.lb() + (Interval(0,x.diam()+1) | Interval(0))
        elif key == curses.KEY_LEFT:
          x = x.lb() + (Interval(0,x.diam()-1) | Interval(0))
        elif key == curses.KEY_UP:
          y = y.lb() + (Interval(0,y.diam()+1) | Interval(0))
        elif key == curses.KEY_DOWN:
          y = y.lb() + (Interval(0,y.diam()-1) | Interval(0))

        #
        # # if curses.ascii.ascii(key) == "a":
        #
        # if key == curses.KEY_UP or key == 566:
        #   y += 0.5 if key==566 else 0.1
        # elif key == curses.KEY_DOWN or key == 525:
        #   y -= 0.5 if key==525 else 0.1
        # elif key == curses.KEY_RIGHT or key == 560:
        #   x += 0.5 if key==560 else 0.1
        # elif key == curses.KEY_LEFT or key == 545:
        #   x -= 0.5 if key==545 else 0.1
        # elif key == ord('a') or key == 1:
        #   th += 0.1 if key==1 else 0.05
        # elif key == ord('e') or key == 5:
        #   th -= 0.1 if key==5 else 0.05
        #
        # elif key == ord('A'):
        #   th = th.lb() + (Interval(0,th.diam()-0.1) | Interval(0))
        # elif key == ord('E'):
        #   th = th.lb() + (Interval(0,th.diam()+0.1) | Interval(0))
        # elif key == ord('z'):
        #   x = x.lb() + (Interval(0,x.diam()+1) | Interval(0))
        # elif key == ord('s'):
        #   x = x.lb() + (Interval(0,x.diam()-1) | Interval(0))
        # elif key == ord('d'):
        #   y = y.lb() + (Interval(0,y.diam()+1) | Interval(0))
        # elif key == ord('q'):
        #   y = y.lb() + (Interval(0,y.diam()-1) | Interval(0))

        elif key == ord('R'):
          x = Interval(1,1.5)
          y = Interval(2,3)
          th = Interval(0).inflate(1)

        xx,yy,thh = Catan2(x,y,Interval(th))

        # print(event.Key, event.Ascii, th, thh)
        # print(x, y, th  )
        vibes.clearFigure()
        vibes.drawBox(x[0], x[1], y[0], y[1], 'r')
        vibes.drawPie((0,0), (1e-5,5), th*180./math.pi, "r")
        vibes.drawPie((0,0), (1e-5,5), thh*180./math.pi, "b")
        vibes.drawBox(xx[0], xx[1], yy[0], yy[1], 'b')

        # if key in actions:
        #   x, y, th = actions[key]( x,y,th )
        # fct = actions.get(key, lambda x,y,th:x,y,th)

        stdscr.clear()
        stdscr.addstr(1, 10, "Pressed %s %d"%(lower_key, key))
        stdscr.addstr(1,25,str(curses.ascii.ctrl(key)))
        stdscr.addstr(1,40,str(curses.ascii.ascii(key)))
        stdscr.addstr(1,40,str(curses.ascii.alt(key)))
        stdscr.addstr(3, 20, "x    : %s\t\t%s"%(x,xx))
        stdscr.addstr(4, 20, "y    : %s\t\t%s"%(y,yy))
        stdscr.addstr(5, 20, "theta : %s\t\t%s"%(th, thh))
        if key == ord("t"):
          fout.write("%s %s %s -> %s %s %s\n"%(x,y,th, xx, yy, thh))
        stdscr.refresh()

    curses.endwin()
    fout.write("#===================== END ===============\n");

if __name__ == '__main__':
  vibes.beginDrawing()
  vibes.newFigure("Debut Catan2")
  vibes.setFigureSize(500,500)
  vibes.drawBox(-5,5,-5,5)
  vibes.axisAuto()

  wrapper(main)


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

import re
import copy
import os
import stat
import errno
import config
import shutil
from zsync_logger import MYLOGGER
from collections import deque

def check_file_same(file_path, file_size, file_mtime):
    if not os.path.exists(file_path):
        return False

    file_stat = os.stat(file_path)

    if abs(file_stat.st_mtime - file_mtime) > config.FILE_MODIFY_WINDOW or \
        file_stat.st_size != file_size:
        return False

    return True

def fix_file_type(file_path, file_type, from_path=''):
    if os.path.exists(file_path):
        exist_type = config.FILE_TYPE_DIR if os.path.isdir(file_path) \
            else config.FILE_TYPE_FILE

        if exist_type != file_type:
            if exist_type == config.FILE_TYPE_FILE:
                os.remove(file_path)
            else:
                shutil.rmtree(file_path)
    else:
        if not file_path.startswith(from_path):
            raise ValueError('fix_file_type args is invalid')

        dirs = file_path[len(from_path):].split(os.path.sep)
        dirs = dirs[:-1]

        check_path = from_path

        for dir_part in dirs:
            check_path = os.path.join(check_path, dir_part)
            if os.path.exists(check_path) and not os.path.isdir(check_path):
                os.remove(check_path)
                return

    return

def makedir(directory, mode = None, mtime = None):
    if not os.path.exists(directory):
        try:
            os.makedirs(directory)
        except OSError as e:
            if e.errno != errno.EEXIST:
                return str(e)

    if mode or mtime:
        dir_stat = os.stat(directory)

        if mode and dir_stat[stat.ST_MODE] != mode:
            os.chmod(directory, mode)

        if mtime and dir_stat[stat.ST_MTIME] != mtime:
            os.utime(directory, (mtime, mtime))
    return None

ip_pattern = re.compile(r'^((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?):|^localhost:')

class CommonPath(object):
    def __init__(self, path):
        self.origin_path = path
        self.ip = ''
        self.path = os.path.normpath(path)
        match = ip_pattern.match(self.path)
        if match:
            self.ip = match.group()[:-1]
            self.path = self.path[len(self.ip) + 1:]
        else:
            self.ip = '127.0.0.1'

        if self.ip == 'localhost':
            self.ip = '127.0.0.1'

        self.last_path = os.path.basename(self.path)
        self.prefix_path = self.path[:self.path.find(self.last_path)]
        return

    def isValid(self):
        return self.path and self.ip

    def full(self):
        return self.ip + ':' + self.path

    def isLocal(self):
        return self.ip == '127.0.0.1'

    def visitValid(self):
        if not os.path.exists(self.path):
            return False

        return True

class CommonFile(object):
    def __init__(self):
        self.path = ''
        self.file = None
        self.mode = 0
        self.file_mode = 0
        self.file_mtime = 0
        self.chunk_map = {}
        self.credit = 0
        self.fetch_offset = 0
        self.offset = 0
        self.total = 0
        self.writedone = False
        return

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

    def is_open(self):
        return self.file

    def writing_file(self):
        return self.file and 'w' in self.mode

    def close(self):
        if self.file:
            self.file.close()
            self.file = None
            if 'w' in self.mode:
                if self.file_mode:
                    os.chmod(self.path, self.file_mode)
                if self.file_mtime:
                    os.utime(self.path, (self.file_mtime, self.file_mtime))
        return

    def open(self, path, mode, total=0, credit=0, file_mode=0, file_mtime=0):
        self.close()
        self.__init__()
        self.path = path
        self.mode = mode
        self.file = open(path, mode)
        self.file_mode = file_mode
        self.file_mtime = file_mtime
        self.total = total
        self.credit = credit
        return

    def fetch(self, offset, size):
        self.offset = offset
        self.file.seek(offset, os.SEEK_SET)
        data = self.file.read(size)
        return data

    def write(self, data):
        if not self.file:
            return
        self.file.write(data)
        self.offset += len(data)
        if self.offset == self.total:
            self.writedone = True
            self.close()
        return

    def write_chunk(self, offset, data):
        if not self.file:
            return

        if offset != self.offset:
            self.chunk_map[offset] = data
        else:
            self.write(data)

            for woffset in sorted(self.chunk_map.keys()):
                if woffset == self.offset:
                    chunk = self.chunk_map.pop(woffset)
                    self.write(chunk)
                else:
                    break
        return

class CommonExclude(object):
    def __init__(self, excludes):
        self.excludes_origin = copy.deepcopy(excludes)
        self.excludes = None
        if excludes:
            self.excludes = [re.compile(exclude) for exclude in excludes]
        return

    # find match exclude
    # if fname is not none, will match fname and join(path, fname)
    # else match path
    def isExclude(self, path, fname=None):
        if not self.excludes:
            return False

        if fname:
            real_path = os.path.join(path, fname)
            for exclude in self.excludes:
                if exclude.match(fname):
                    return True

                if exclude.match(real_path):
                    return True
        else:
            for exclude in self.excludes:
                if exclude.match(path):
                    return True

        return False

def create_sub_process(args_dict):
    import subprocess
    import inspect
    import sys
    file_path = inspect.stack()[-1][1]
    dir_path = os.path.dirname(file_path)
    zsync_path = os.path.join(dir_path, 'zsync.py')

    for key in ['src', 'dst']:
        if type(args_dict[key]) is unicode:
            args_dict[key] = args_dict[key].encode(sys.stdin.encoding)

    sub_args = [
        'python', zsync_path,
        args_dict['src'], \
        args_dict['dst'], \
        '--port', str(args_dict['port']), \
        '--thread-num', str(args_dict['thread_num']), \
        '--timeout', str(args_dict['timeout']), \
        '--pipeline', str(args_dict['pipeline']), \
        '--chunksize', str(args_dict['chunksize']), \
    ]

    if args_dict['excludes']:
        for exclude in args_dict['excludes']:
            sub_args.extend(['--exclude', str(exclude)])

    if args_dict.get('local'):
        sub_args.append('--local')

    if args_dict.get('remote'):
        sub_args.append('--remote')

    if args_dict.get('compress'):
        sub_args.append('--compress')

    MYLOGGER.debug('creating subprocess %s' % sub_args)
    sub = subprocess.Popen(sub_args)
    return sub

CHECKSUM_M = 1 << 16

def calc_checksum(data):
    # a = sum(data) mod M
    # b = sum((i+1) * data[i]) mod M
    # s = a + 2^16 * b
    a = 0
    b = 0
    maxlen = len(data)
    for i, char in enumerate(data):
        ordc = ord(char)
        a += ordc
        b += (maxlen - i) * ordc
        if a > CHECKSUM_M:
            a %= CHECKSUM_M
        if b > CHECKSUM_M:
            b %= CHECKSUM_M
    s = a + (b << 16)
    return a, b, s

class RollingChecksum(object):
    def __init__(self, data=None):
        self.roll_queue = None
        self.a = 0
        self.b = 0
        self.s = 0
        if data:
            self.calc_checksum(data)
        return

    def calc_checksum(self, data):
        self.roll_queue = deque((ord(char) for char in data), len(data))
        maxlen = self.roll_queue.maxlen
        self.a, self.b, self.s = calc_checksum(data)
        return

    def calc_next(self, char):
        ordc = ord(char)
        self.a = (self.a - self.roll_queue[0] + ordc) % CHECKSUM_M
        self.b = (self.b - self.roll_queue.maxlen * self.roll_queue[0] + self.a) % CHECKSUM_M
        self.roll_queue.append(ordc)
        self.s = self.a + (self.b << 16)
        return


import operations as op
import cgraph as cg

class Layer(object):
    def __init__(self, input):
        self.input = input

class InputLayer(Layer):
    def __init__(self, input):
        super().__init__(input)
        self.out = self.input
        self.nodes = [self.out]

class WeightLayer(Layer):
    def __init__(self, input, out_shape, in_shape) -> None:
        super().__init__(input)
        self.weights = cg.Variable([out_shape, in_shape])
        self.out = op.Wx([self.weights, self.input])

        self.nodes = [self.weights,self.out]

class ActivationLayer(Layer):
    ACTIVATIONS = {
                        "sgm":op.Sigmoid,
                        "tanh":op.Tanh,
                        "relu":op.ReLU
                        }

    def __init__(self, input, activation):
        super().__init__(input)
        self.out = ActivationLayer.ACTIVATIONS[activation]([self.input])

        self.nodes = [self.out]

class ProbabilityLayer(Layer):
    PROBABILITIES = {
                        "Softmax":op.Softmax,
                        "sgm":op.Sigmoid
                    }

    def __init__(self, input, out_shape, input_shape, prob = "Softmax"):
        super().__init__(input)
        self.weights = cg.Variable([out_shape, input_shape])
        self.wx = op.Wx([self.weights, self.input])
        self.out = ProbabilityLayer.PROBABILITIES[prob]([self.wx])

        self.nodes = [self.weights, self.wx, self.out]

class LossLayer(Layer):
    LOSSES = {
                "CrossEntropy":op.CrossEntropy,
            }

    def __init__(self, input, out_shape, true_prob, loss = "CrossEntropy"):
        super().__init__(input)
        self.out = LossLayer.LOSSES[loss]([input, true_prob])
        self.nodes = [self.out]


import numpy as np
import matplotlib.pyplot as plt
from project_math import *
from scipy.integrate import solve_ivp
from matplotlib.animation import FuncAnimation

x_min = -1e-7
x_max = 1e-7
B0 = 1
xs = np.linspace(x_min,x_max,N)
delta = xs[1]-xs[0]

gaussianA = generateGaussianWavePacket(xs,-5e-8,5e-9,9e8)
gaussianB = generateGaussianWavePacket(xs,0,5e-9,-9e8)
As = generateMagneticVectorPotential(xs,B0,5e-10)

gaussianAUp = np.zeros(N*N*2, dtype=complex)
gaussianADown = np.zeros(N*N*2, dtype=complex)
gaussianBUp = np.zeros(N*N*2, dtype=complex)
gaussianBDown = np.zeros(N*N*2, dtype=complex)

for z1 in range(N):
    for z2 in range(N):
        gaussianAUp  [mapZ12StoLin(z1,z2,0,N)] = 1/np.sqrt(N)*gaussianA[z1]
        gaussianADown[mapZ12StoLin(z1,z2,1,N)] = 1/np.sqrt(N)*gaussianA[z1]
        gaussianBUp  [mapZ12StoLin(z1,z2,0,N)] = 1/np.sqrt(N)*gaussianB[z2]
        gaussianBDown[mapZ12StoLin(z1,z2,1,N)] = 1/np.sqrt(N)*gaussianB[z2]

H = hamiltonian2Particles(xs,delta, As)

def f(t, phi):
    return 1/(1j*constants.hbar) * (H @ phi)

tmax = 15e-13
timesteps = 100

startCond = gaussianAUp

sol = solve_ivp(f, [0, tmax], startCond,  t_eval=np.linspace(0,tmax,timesteps), method='DOP853')
print(sol)

solutionUpA   = np.zeros((timesteps,N),dtype=complex)
solutionDownA = np.zeros((timesteps,N),dtype=complex)
solutionUpB   = np.zeros((timesteps,N),dtype=complex)
solutionDownB = np.zeros((timesteps,N),dtype=complex)

for i in range(timesteps):
    for z in range(N):
        solutionUpA  [i][z] = np.sum(np.power(np.abs([(sol.y[mapZ12StoLin(z,x,0,N)][i]) for x in range(N)]),2))
        solutionDownA[i][z] = np.sum(np.power(np.abs([(sol.y[mapZ12StoLin(z,x,1,N)][i]) for x in range(N)]),2))

        solutionUpB  [i][z] = np.sum(np.power(np.abs([(sol.y[mapZ12StoLin(x,z,0,N)][i]) for x in range(N)]),2))
        solutionDownB[i][z] = np.sum(np.power(np.abs([(sol.y[mapZ12StoLin(x,z,1,N)][i]) for x in range(N)]),2))

ys = solutionUpA[0]
print(np.trapz(ys,xs))

# Create a figure and axis
fig, ((ax1,ax3),(ax2,ax4)) = plt.subplots(2,2)

# Set the x-axis limits
ax1.set_xlim(x_min,x_max)
ax2.set_xlim(x_min,x_max)
ax3.set_xlim(x_min,x_max)
ax4.set_xlim(x_min,x_max)

ax1.set_ylim(0,1e8)
ax2.set_ylim(0,1e8)
ax3.set_ylim(0,1e8)
ax4.set_ylim(0,1e8)

# Initialize an empty point for the plot
point1, = ax1.plot(xs, solutionUpA[0])
point2, = ax2.plot(xs, solutionDownA[0])
point3, = ax3.plot(xs, solutionUpB[0])
point4, = ax4.plot(xs, solutionDownB[0])
ax1.plot(xs, As*1e14)
ax2.plot(xs, As*1e14)
ax3.plot(xs, As*1e14)
ax4.plot(xs, As*1e14)

# Function to initialize the plot
def init():
    point1.set_data(xs, ys)
    point2.set_data(xs, ys)
    point3.set_data(xs, ys)
    point4.set_data(xs, ys)
    return point1, point2, point3, point4

# Function to update the plot for each frame
def update(frame):
    x = xs
    point1.set_data(xs, solutionUpA[frame])
    point2.set_data(xs, (solutionDownA[frame]))
    point3.set_data(xs, solutionUpB[frame])
    point4.set_data(xs, (solutionDownB[frame]))
    return point1, point2, point3, point4

ani = FuncAnimation(fig, update, frames=len(sol.y[0]), init_func=init, blit=True,interval=1/60*1000)
ani.save(f"testTwoParticle{N}_B{B0}.gif")
plt.show()


from setuptools import setup

PACKAGE = 'TracHgrcEdit'
VERSION = '0.11.0'

setup(
    name = PACKAGE,
    version = VERSION,
    author = "Matthias Bilger",
    author_email = "matthias@bilger.info",
    description = "A interface to edit hgrc file via admin panel",
    license = "BSD",
    keywords = "trac plugin mercurial hgrc",
    packages=['hgrcedit'],
    entry_points={'trac.plugins': '%s = hgrcedit' % PACKAGE},
    package_data={'hgrcedit': ['templates/*.html']},
)


class Solution:
    def reverseVowels(self, s: str) -> str:
        vowels = ["a", "e", "i", "u", "o"]

        n = len(s)
        s_vowels = []
        for i in range(n):
            if s[i] in vowels:
                s_vowels.append(s[i])

        reversed_s_vowels = s_vowels[::-1]
        result = ""
        x = 0
        for i in range(n):
            if s[i] in vowels:
                result += reversed_s_vowels[x]
                x += 1
            else:
                result += s[i]
        return result

def main():
    print(Solution().reverseVowels("leetcode"))

if __name__ == '__main__':
    main()


import numpy as np
from matplotlib import pyplot
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import glob
import cv2

# Get size of calibration image
img = cv2.imread('camera_cal/calibration1.jpg')
img_size = (img.shape[1], img.shape[0])

# 1. Camera calibration
# prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
objp = np.zeros((6*9,3), np.float32)
objp[:,:2] = np.mgrid[0:9, 0:6].T.reshape(-1,2)

# Arrays to store object points and image points from all the images.
objpoints = [] # 3d points in real world space
imgpoints = [] # 2d points in image plane.

# Make a list of calibration images
images = glob.glob('camera_cal/calibration*.jpg')

# Step through the list and search for chessboard corners
for idx, fname in enumerate(images):
    img = cv2.imread(fname)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # Find the chessboard corners
    ret, corners = cv2.findChessboardCorners(gray, (9,6), None)

    # If found, add object points, image points
    if ret == True:
        objpoints.append(objp)
        imgpoints.append(corners)

        # Draw and display the corners
        cv2.drawChessboardCorners(img, (9,6), corners, ret)
        #write_name = 'corners_found'+str(idx)+'.jpg'
        #cv2.imwrite(write_name, img)
        #cv2.imshow('img', img)
        cv2.waitKey(500)
#print(corners[0,0])
#print(np.shape(corners))
#print(np.shape(imgpoints))
cv2.destroyAllWindows()

import pickle
# Do camera calibration given object points and image points
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, img_size,None,None)

# Save the camera calibration result for later use (we won't worry about rvecs / tvecs)
dist_pickle = {}
dist_pickle["mtx"] = mtx
dist_pickle["dist"] = dist
pickle.dump( dist_pickle, open( "output_images/cal_dist_pickle.p", "wb" ) )

# 2. Distortion correction
# Test undistortion on an image
img = cv2.imread('test_images/test2.jpg')
# undistort test image
undist = cv2.undistort(img, mtx, dist, None, mtx)
cv2.imwrite('output_images/test2_undist.jpg',undist)
# Visualize undistortion
undist = cv2.cvtColor(undist, cv2.COLOR_BGR2RGB) #convert to RGB
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

f, (ax1, ax2) = plt.subplots(1, 2, figsize=(20,10))
ax1.imshow(img)
ax1.set_title('Original Image', fontsize=30)
ax2.imshow(undist)
ax2.set_title('Undistorted Image', fontsize=30)
pyplot.savefig('output_images/test2_vs_undistort.png')

def select_yellow(image):
    hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
    lower = np.array([20,60,60])
    upper = np.array([38,174, 250])
    mask = cv2.inRange(hsv, lower, upper)

    return mask

def select_white(image):
    lower = np.array([202,202,202])
    upper = np.array([255,255,255])
    mask = cv2.inRange(image, lower, upper)

    return mask

img = mpimg.imread('output_images/test2_undist.jpg')
def doubleThreshold(image):
    yellow = select_yellow(image)
    white = select_white(image)

    combined_binary = np.zeros_like(yellow)
    combined_binary[(yellow >= 1) | (white >= 1)] = 255

    return combined_binary
threshed=doubleThreshold(img)
cv2.imwrite('output_images/test2_undist_thresh.jpg',threshed)

# 3. Color/gradient threshold
# Read in an image and grayscale it
# img = mpimg.imread('output_images/cal_undist.jpg')
# def doubleThreshold(img):
#     # Convert to HLS color space and separate the S channel
#     # Note: img is the undistorted image
#     hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)
#     s_channel = hls[:,:,2]
#
#     # Grayscale image
#     # NOTE: we already saw that standard grayscaling lost color information for the lane lines
#     # Explore gradients in other colors spaces / color channels to see what might work better
#     gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
#
#     # Sobel x
#     sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0) # Take the derivative in x
#     abs_sobelx = np.absolute(sobelx) # Absolute x derivative to accentuate lines away from horizontal
#     scaled_sobel = np.uint8(255*abs_sobelx/np.max(abs_sobelx))
#
#     # Threshold x gradient
#     thresh_min = 30
#     thresh_max = 100
#     sxbinary = np.zeros_like(scaled_sobel)
#     sxbinary[(scaled_sobel >= thresh_min) & (scaled_sobel <= thresh_max)] = 1
#
#     # Threshold color channel
#     s_thresh_min = 170
#     s_thresh_max = 255
#     s_binary = np.zeros_like(s_channel)
#     s_binary[(s_channel >= s_thresh_min) & (s_channel <= s_thresh_max)] = 1
#     # Combine the two binary thresholds
#     combined_binary = np.zeros_like(sxbinary)
#     combined_binary[(s_binary == 1) | (sxbinary == 1)] = 255
#     print(np.shape(combined_binary))
#     return combined_binary
#
# threshed=doubleThreshold(img)
# cv2.imwrite('output_images/cal_undist_thresh.jpg',threshed)
# #Plotting thresholded images
# fig = plt.figure()
# plt.title('Combined S channel and gradient thresholds')
# plt.imshow(threshed, cmap='gray')
# plt.show()
# pyplot.savefig('output_images/cal_undist_thresh.png')

# 4. Perspective Transform
# Read image from last step
img = mpimg.imread('output_images/undist_thresh.jpg')
def perspectiveTrans(img):
#     offset=50
#     up=np.int(0.65*img_size[1]) #vertical height
#     down=np.int(img_size[1])
#     leftup=np.int(0.45*img_size[0]) #horizontal
#     leftdown=np.int(0.2*img_size[0])
#     rightup=np.int(0.6*img_size[0])
#     rightdown=np.int(0.85*img_size[0])
#     hist_leftup = np.sum(img[up:(up+offset),leftup-offset:leftup], axis=0)
#     hist_leftdown = np.sum(img[down-offset:down,leftdown:leftdown+offset], axis=0)
#     hist_rightup = np.sum(img[up:(up+offset),rightup:rightup+offset], axis=0)
#     hist_rightdown = np.sum(img[down-offset:down,rightdown-offset:rightdown], axis=0)
#
#     leftup = np.argmax(hist_leftup)+leftup-offset #argmax returns index of max value
#     leftdown = np.argmax(hist_leftdown)+leftdown-offset
#     rightup = np.argmax(hist_rightup)+rightup-offset
#     rightdown = np.argmax(hist_rightdown)+rightdown-offset
#     #
#     src = np.float32([[leftup,up], [rightup,up], [leftdown,down], [rightdown,down]])
#     dst = np.float32([[leftdown, 0], [rightdown,0], [leftdown, down], [rightdown,down]])
    #src = np.float32([[599,447], [680,447], [227,704], [1065,704]])

    src = np.float32([[545, 460],
                    [735, 460],
                    [1280, 700],
                    [0, 700]])

    dst = np.float32([[0, 0],
                     [1280, 0],
                     [1280, 720],
                     [0, 720]])

#     print(src)
#     print(dst)
    # Given src and dst points, calculate the perspective transform matrix
    M = cv2.getPerspectiveTransform(src, dst)
    MInv = cv2.getPerspectiveTransform(dst,src)
    # Warp the image using OpenCV warpPerspective()
    warped = cv2.warpPerspective(img, M, img_size)
    org = cv2.warpPerspective(warped, MInv, img_size)
    return warped, org, MInv

warped, org, MInv = perspectiveTrans(img)
cv2.imwrite('output_images/test2_undist_thresh_transform.jpg',warped)
# cv2.imwrite('output_images/undist_thresh_invtrans.jpg',org)

# 5. Detect lane lines
# Read in an image and grayscale it
img = mpimg.imread('output_images/undist_thresh_transform.jpg')
ploty = np.linspace(0, img_size[1]-1, img_size[1] ) #args: (start,stop,elements)
def slide_window(img):
    # detect lane lines by sliding windows
    binary_warped=img
    # Assuming you have created a warped binary image called "binary_warped"
    # Take a histogram of the bottom half of the image
    histogram = np.sum(binary_warped[np.int(img_size[1]/2):,:], axis=0)
    # Create an output image to draw on and  visualize the result
    #out_img = np.dstack((binary_warped, binary_warped, binary_warped))#*255
    # Find the peak of the left and right halves of the histogram
    # These will be the starting point for the left and right lines
    midpoint = np.int(histogram.shape[0]/2)
    leftx_base = np.argmax(histogram[:midpoint]) #argmax returns index of max value
    rightx_base = np.argmax(histogram[midpoint:]) + midpoint

    # Choose the number of sliding windows
    nwindows = 9
    # Set height of windows
    window_height = np.int(img_size[1]/nwindows) # y vertical height
    # Identify the x and y positions of all nonzero pixels in the image
    nonzero = binary_warped.nonzero() #nonzero() returns tuple of arrays. e.g.coord:(3,2) (1,7),(8,6) -> ([3,1,8],[2,7,6])
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    # Current positions to be updated for each window
    leftx_current = leftx_base #current is center of window in x axis
    rightx_current = rightx_base
    # Set the width of the windows +/- margin
    margin = 100
    # Set minimum number of pixels found to recenter window
    minpix = 50
    # Create empty lists to receive left and right lane pixel indices
    left_lane_inds = []
    right_lane_inds = []

    # Step through the windows one by one
    for window in range(nwindows):
        # Identify window boundaries in x and y (and right and left)
        win_y_low = img_size[1] - (window+1)*window_height
        win_y_high = img_size[1] - window*window_height
        win_xleft_low = leftx_current - margin
        win_xleft_high = leftx_current + margin
        win_xright_low = rightx_current - margin
        win_xright_high = rightx_current + margin
        # Draw the windows on the visualization image
    #     cv2.rectangle(out_img,(win_xleft_low,win_y_low),(win_xleft_high,win_y_high),
    #     (0,255,0), 2)
    #     cv2.rectangle(out_img,(win_xright_low,win_y_low),(win_xright_high,win_y_high),
    #     (0,255,0), 2)
        # Identify the nonzero pixels in x and y within the window
        good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
        (nonzerox >= win_xleft_low) &  (nonzerox < win_xleft_high)).nonzero()[0]
        good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
        (nonzerox >= win_xright_low) &  (nonzerox < win_xright_high)).nonzero()[0]
        # Append these indices to the lists
        left_lane_inds.append(good_left_inds)
        right_lane_inds.append(good_right_inds)
        # If you found > minpix pixels, recenter next window on their mean position
        if len(good_left_inds) > minpix:
            leftx_current = np.int(np.mean(nonzerox[good_left_inds]))
        if len(good_right_inds) > minpix:
            rightx_current = np.int(np.mean(nonzerox[good_right_inds]))

    # Concatenate the arrays of indices
    left_lane_inds = np.concatenate(left_lane_inds)
    right_lane_inds = np.concatenate(right_lane_inds)

    # Extract left and right line pixel positions
    leftx = nonzerox[left_lane_inds]
    lefty = nonzeroy[left_lane_inds]
    rightx = nonzerox[right_lane_inds]
    righty = nonzeroy[right_lane_inds]

    # Fit a second order polynomial to each
    left_fit = np.polyfit(lefty, leftx, 2) #lefty in 1st place, similar to x axis as in ax^2+bx+c
    right_fit = np.polyfit(righty, rightx, 2) #returns polynomial coeffients a,b,c (xx_fit[0],[1],[2])

    # Generate x and y values for plotting
    left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2] # all pairs of (ploty,left_fitx) form a polynomial line (power 2)
    right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2] # as later plotted line in yellow color
    return leftx, lefty, rightx, righty, left_fit, right_fit, left_fitx, right_fitx

leftx, lefty, rightx, righty, left_fit, right_fit, left_fitx, right_fitx = slide_window(img)

def searchAfter(img,undist,left_fit,right_fit):
    binary_warped=img
    # Assume you now have a new warped binary image
    # from the next frame of video (also called "binary_warped")
    # It's now much easier to find line pixels!
    nonzero = binary_warped.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    margin = 100
    left_lane_inds = ((nonzerox > (left_fit[0]*(nonzeroy**2) + left_fit[1]*nonzeroy +
    left_fit[2] - margin)) & (nonzerox < (left_fit[0]*(nonzeroy**2) +
    left_fit[1]*nonzeroy + left_fit[2] + margin)))  # use +/- margin criteria for nonzero indexes be included in lane lines

    right_lane_inds = ((nonzerox > (right_fit[0]*(nonzeroy**2) + right_fit[1]*nonzeroy +
    right_fit[2] - margin)) & (nonzerox < (right_fit[0]*(nonzeroy**2) +
    right_fit[1]*nonzeroy + right_fit[2] + margin)))

    # Again, extract left and right line pixel positions
    leftx = nonzerox[left_lane_inds]
    lefty = nonzeroy[left_lane_inds]
    rightx = nonzerox[right_lane_inds]
    righty = nonzeroy[right_lane_inds]
    # Fit a second order polynomial to each
    left_fit = np.polyfit(lefty, leftx, 2)
    right_fit = np.polyfit(righty, rightx, 2)
    # Generate x and y values for plotting
    ploty = np.linspace(0, img.shape[0]-1, img.shape[0] )
    left_fitx = left_fit[0]*ploty**2 + left_fit[1]*ploty + left_fit[2]  # all x values along lane line for each y value
    right_fitx = right_fit[0]*ploty**2 + right_fit[1]*ploty + right_fit[2]

    # Project those lines onto the original image as follows:
    # Create an image to draw the lines on
    warp_zero = np.zeros_like(binary_warped).astype(np.uint8)
    color_warp = np.dstack((warp_zero, warp_zero, warp_zero))
    lane_mask=np.copy(color_warp)
    # Recast the x and y points into usable format for cv2.fillPoly()
    pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
    pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
    pts = np.hstack((pts_left, pts_right))
    # Draw the lane onto the warped blank image
    cv2.fillPoly(color_warp, np.int_([pts]), (0,255, 0))
    # mark lane points with red color on left lane and blue on right lane
    lane_mask[lefty, leftx] = [255, 0, 0]
    lane_mask[righty, rightx] = [0, 0, 255]
    color_warp = cv2.addWeighted(color_warp, 1, lane_mask, 1, 0)
    # Warp the blank back to original image space using inverse perspective matrix (Minv)
    newwarp = cv2.warpPerspective(color_warp, MInv, img_size)
    # Combine the result with the original image
    result = cv2.addWeighted(undist, 1, newwarp, 0.5, 0)
    # Color in left and right line pixels
    #result=cv2.cvtColor(result,cv2.COLOR_RGB2BGR)
    return leftx, lefty, rightx, righty, left_fit, right_fit, left_fitx, right_fitx, result

leftx, lefty, rightx, righty, left_fit, right_fit,left_fitx, right_fitx, result=searchAfter(img,undist,left_fit,right_fit)
result=cv2.cvtColor(result,cv2.COLOR_RGB2BGR)
# cv2.imwrite('output_images/shaded_lane.jpg',result)

#6. Determine the lane curvature
def laneCurvature(leftx,lefty,rightx,righty,left_fit,right_fit):
    # Define y-value where we want radius of curvature
    # I'll choose the maximum y-value, corresponding to the bottom of the image
    y_eval = np.max(ploty)
    # Define conversions in x and y from pixels space to meters
    ym_per_pix = 30/720 # meters per pixel in y dimension
    xm_per_pix = 3.7/700 # meters per pixel in x dimension

    # Fit new polynomials to x,y in world space
    left_fit_cr = np.polyfit(lefty*ym_per_pix, leftx*xm_per_pix, 2)
    right_fit_cr = np.polyfit(righty*ym_per_pix, rightx*xm_per_pix, 2)
    # Calculate the new radii of curvature
    left_curverad = ((1 + (2*left_fit_cr[0]*y_eval*ym_per_pix + left_fit_cr[1])**2)**1.5) / np.absolute(2*left_fit_cr[0])
    right_curverad = ((1 + (2*right_fit_cr[0]*y_eval*ym_per_pix + right_fit_cr[1])**2)**1.5) / np.absolute(2*right_fit_cr[0])
    # Now our radius of curvature is in meters
    left_basex = left_fit[0]*y_eval**2 + left_fit[1]*y_eval + left_fit[2]
    right_basex = right_fit[0]*y_eval**2 + right_fit[1]*y_eval + right_fit[2]

    road_center=(left_basex+right_basex)*0.5
    vehicle_center=img_size[0]*0.5
    abs_diff=np.abs(road_center-vehicle_center)*xm_per_pix
    if vehicle_center<road_center:
        shift='left'
    else:
        shift='right'
    return left_curverad,right_curverad,shift,abs_diff

left_curverad,right_curverad,shift,abs_diff=laneCurvature(leftx,lefty,rightx,righty,left_fit,right_fit)
# print(left_curverad, 'm,', right_curverad, 'm', '.vehicle is', abs_diff,'m', shift,'of center')

class Line():
    def __init__(self,recent_xfitted,recent_fit,current_fit,radius,allx,ally):
        # was the line detected in the last iteration?
        self.detected = True
        # x values of the last n fits of the line
        self.recent_xfitted = recent_xfitted
        #average x values of the fitted line over the last n iterations
        self.bestx = np.mean(self.recent_xfitted,axis=0)
        # last n fits of the line
        self.recent_fit = recent_fit
        #polynomial coefficients averaged over the last n iterations
        self.best_fit = np.mean(self.recent_fit,axis=0)
        #polynomial coefficients for the most recent fit
        self.current_fit = current_fit
        #radius of curvature of the line in some units
        self.radius_of_curvature = radius
        #distance in meters of vehicle center from the line
        self.line_base_pos = None
        #difference in fit coefficients between last and new fits
        self.diffs = np.abs(self.current_fit-self.recent_fit[-1])
        #x values for detected line pixels
        self.allx = allx
        #y values for detected line pixels
        self.ally = ally
    def sanityCheck(self):
        if (self.radius_of_curvature/1000<0.4) or (self.radius_of_curvature/1000>3):
            self.detected=False
        last_fit=self.recent_fit[-1]
        if np.any(self.diffs>[0.01*last_fit[0], 0.01*last_fit[1], 0.01*last_fit[2]]):
            self.detected=False

def process_image(img):
    global frame
    global lastFrame

    frame+=1
    # 2. Undistortion
    undist = cv2.undistort(img, mtx, dist, None, mtx)

    # 3. gradient & color thresholding
    threshed=doubleThreshold(undist)

    # 4. Perspective transform
    warped, org, MInv = perspectiveTrans(threshed)

    # 5. Detect lane lines
    # first frame use slide window to locate lanes
    if frame==1:
        leftx, lefty, rightx, righty, left_fit, right_fit, left_fitx, right_fitx = slide_window(warped)
        left_curverad,right_curverad,shift,abs_diff=laneCurvature(leftx,lefty,rightx,righty,left_fit,right_fit)
    else: # from 2nd frame onwards, search around last detected lanes for current lanes
        left_fit, right_fit = lastFrame["left_fit"],lastFrame["right_fit"]
        leftx, lefty, rightx, righty, left_fit, right_fit, left_fitx, right_fitx, result=searchAfter(warped,undist,left_fit,right_fit)
        left_curverad,right_curverad,shift,abs_diff=laneCurvature(leftx,lefty,rightx,righty,left_fit,right_fit)

    if frame<=avg_n:
        recent_left_fitx.append(left_fitx)
        recent_left_fit.append(left_fit)
        recent_right_fitx.append(right_fitx)
        recent_right_fit.append(right_fit)
        #left
        line_l_current=Line(recent_left_fitx,recent_left_fit,left_fit,left_curverad,leftx,lefty)
        #right
        line_r_current=Line(recent_right_fitx,recent_right_fit,right_fit,right_curverad,rightx,righty)

    if frame>avg_n:
        recent_left_fitx.pop(0)
        recent_left_fitx.append(left_fitx)
        recent_left_fit.pop(0)
        recent_left_fit.append(left_fit)
        recent_right_fitx.pop(0)
        recent_right_fitx.append(right_fitx)
        recent_right_fit.pop(0)
        recent_right_fit.append(right_fit)
        #left
        line_l_current=Line(recent_left_fitx,recent_left_fit,left_fit,left_curverad,leftx,lefty)
        line_l_current.sanityCheck()
        #right
        line_r_current=Line(recent_right_fitx,recent_right_fit,right_fit,right_curverad,rightx,righty)
        line_r_current.sanityCheck()
        if line_r_current.detected=='False' or line_l_current.detected=='False':
            print("sanity check failed")
            leftx, lefty, rightx, righty, left_fit, right_fit, left_fitx, right_fitx = slide_window(warped)

    #print('frame',frame,':',line_l_current.recent_fit,line_l_current.best_fit)
    #leftx, lefty, rightx, righty, left_fit, right_fit,left_fitx,right_fitx, result=searchAfter(warped,undist,left_fit,right_fit)
    leftx, lefty, rightx, righty, left_fit, right_fit,left_fitx,right_fitx, result=searchAfter(warped,undist,line_l_current.best_fit,line_r_current.best_fit)

    # 6. Determine the lane curvature and center shift
    left_curverad,right_curverad,shift,abs_diff=laneCurvature(leftx,lefty,rightx,righty,line_l_current.best_fit,line_r_current.best_fit)

    # For use in next frame
    lastFrame={"leftx":leftx,"lefty":lefty, "rightx":rightx, "righty":righty,"left_fit":left_fit,"right_fit":right_fit,
          "left_fitx":left_fitx, "right_fitx":right_fitx, "left_curve":left_curverad,"right_curve":right_curverad}

    text1= "left curvature %f m, right curvature %f m" % (left_curverad,right_curverad)
    text2= "vehicle is %f m %s of road center" % (abs_diff, shift)
    font = cv2.FONT_HERSHEY_SIMPLEX
    result=cv2.putText(result,text1,(100,100), font, 1,(0,0,255),2,cv2.LINE_AA)
    result=cv2.putText(result,text2,(100,200), font, 1,(0,0,255),2,cv2.LINE_AA)

    return result

avg_n = 20
recent_left_fitx=[]
recent_left_fit =[]
recent_right_fitx=[]
recent_right_fit =[]
frame=0
lastFrame={}

from moviepy.editor import VideoFileClip
clip1 = VideoFileClip("project_video.mp4")#.subclip(40,48)
out_clip = clip1.fl_image(process_image) #NOTE: this function expects color images!!
video_output = 'output_images/out_project_video.mp4'
out_clip.write_videofile(video_output, audio=False)


#Imports=========================================================================================================================#
import pandas as pd
import pickle

#Data============================================================================================================================#
# Read the data csv file into a dataframe
def get_df(filename):
    df = pd.read_csv(filename, parse_dates=True)
    df.index = pd.to_datetime(df['timestamp'])

    return df

#Lists of Dictionaries===========================================================================================================#
# Get a location list of dictionaries from store_data
def get_locations():
    data = get_store_data()
    locations_ld = []
    locations = data[0]
    for location in locations:
        locations_ld.append({'name': location, 'alias': locations[location]['alias']})

    return locations_ld

# Get a device list of dictionaries from store_data
def get_devices(location=None):
    data = get_store_data()
    devices_ld = []
    if location:
        devices = data[0][location]['children']
        for device in devices:
            devices_ld.append({'name': device, 'alias': devices[device]['alias']})
    else:
        devices = data[0][location]['children']
        for device in devices:
            devices_ld.append({'name': device, 'alias': devices[device]['alias']})

    return devices_ld

# Get a channel list of dictionaries from store_data
def get_channels(location=None, device=None):
    data = get_store_data()
    channels_ld = []
    if location and device:
        channels = data[0][location]['children'][device]['children']
        for channel in channels:
            channels_ld.append({'name': channel,
                                'alias': channels[channel]['alias'],
                                'scaling_fact': channels[channel]['scaling_fact'],
                                'disabled': channels[channel]['disabled'],
                                'unit': channels[channel]['unit']})

    return channels_ld

#Store Data - this tracks the location/device/channel hierarchy and children=====================================================#
# Write (reset) the store data
def write_store_data():

    data = []
    locations_l = ['Default_Location']
    devices_l = ['Default_Device']
    locations_d = {}
    devices_d = {}
    for location in locations_l:
        for device in devices_l:
            devices_d[device] = {
                'alias': '', 'children': {
                    'ch1': {'alias': '', 'scaling_fact': '1', 'disabled': 'Enabled', 'unit': ''},
                    'ch2': {'alias': '', 'scaling_fact': '1', 'disabled': 'Enabled', 'unit': ''},
                    'ch3': {'alias': '', 'scaling_fact': '1', 'disabled': 'Enabled', 'unit': ''},
                    'ch4': {'alias': '', 'scaling_fact': '1', 'disabled': 'Enabled', 'unit': ''},
                    'ch5': {'alias': '', 'scaling_fact': '1', 'disabled': 'Enabled', 'unit': ''},
                }
            }
        locations_d[location] = {'alias': location, 'children': devices_d}
    data.append(locations_d)

    file = open(r'temp/dcc_store_data.pkl', 'wb')
    pickle.dump(data, file)
    file.close()

# Get the store data
def get_store_data():
    file = open(r'temp/dcc_store_data.pkl', 'rb')
    flag = False
    while not flag:
        try:
            data = pickle.load(file)
            flag = True
        except EOFError:
            pass
    file.close()

    return data

# Update the store data
def update_store_data(data):
    file = open(r'temp/dcc_store_data.pkl', 'wb')
    pickle.dump(data, file)
    file.close()

# Add locations/devices to the store data
def add_device_store_data():
    df = get_df('../data/sensor_data.csv')
    data = get_store_data()
    devices_l = sorted(df['deviceId'].unique()),

    for device in devices_l[0]:
        location = df.loc[df['deviceId'] == device]['location'][0]
        if not data[0].get(location):
            data[0][location] = {'alias': location, 'children': {}}
        if not data[0][location]['children'].get(device):
            device_d = {
                'alias': '', 'children': {
                    'ch1': {'alias': '', 'scaling_fact': '1', 'disabled': 'Enabled', 'unit': ''},
                    'ch2': {'alias': '', 'scaling_fact': '1', 'disabled': 'Enabled', 'unit': ''},
                    'ch3': {'alias': '', 'scaling_fact': '1', 'disabled': 'Enabled', 'unit': ''},
                    'ch4': {'alias': '', 'scaling_fact': '1', 'disabled': 'Enabled', 'unit': ''},
                    'ch5': {'alias': '', 'scaling_fact': '1', 'disabled': 'Enabled', 'unit': ''},
                }
            }
            data[0][location]['children'][device] = device_d
            if data[0].get('Default_Location'): del data[0]['Default_Location']
            update_store_data(data)

#Tree============================================================================================================================#
# Write the nodes of the navigation tree from the store data
def update_tree_nodes(data):
    tree_children = []
    locations = data[0]
    for location in locations:
        loc_children = []
        devices = locations[location]['children']
        for device in devices:
            dev_children = []
            channels = devices[device]['children']
            for channel in channels:
                if channels[channel]['disabled'] == 'Enabled':
                    ch_node = {}
                    ch_node['value'] = 'loc' + location + '_' + 'dev' + device + '_' + channel
                    ch_node['label'] = channels[channel]['alias'] if channels[channel]['alias'] else channel
                    dev_children.append(ch_node)
            dev_node = {}
            dev_node['value'] = 'loc' + location + '_' + 'dev' + device
            dev_node['label'] = devices[device]['alias'] if devices[device]['alias'] else device
            dev_node['children'] = dev_children
            loc_children.append(dev_node)
        loc_node = {}
        loc_node['value'] = 'loc' + location
        loc_node['label'] = locations[location]['alias']
        loc_node['children'] = loc_children
        tree_children.append(loc_node)

    return tree_children

#Views===========================================================================================================================#
# Get the current page type and instance
def get_current_page_dict():
    with open('temp/tree.txt', 'r') as file:
        checked_global = [current_place.rstrip() for current_place in file.readlines()]
        file.close()

    page_dict = {}
    string = checked_global[0].replace('_', '')
    if (string.find('ch') > -1):
        string, page_dict['ch'] = (string.split('ch'))
    if (string.find('dev') > -1):
        string, page_dict['dev'] = (string.split('dev'))
    if (string.find('loc') > -1):
        string, page_dict['loc'] = (string.split('loc'))
    return page_dict


import pygame.sprite
import Config
from Config import *
from Images.Animations.Animation import Animation

class laserflea(pygame.sprite.Sprite):
    class states():
        IDLELEFT = 0
        IDLERIGHT = 1
        IDLELTOR = 2
        IDLERTOL = 3
        LASERLEFT = 4
        LASERRIGHT = 5
        WALKLEFT = 6
        WALKRIGHT = 7
        WALKLTOR = 8
        WALKRTOL = 9
        DEATH = 10

    Config.WIN

    # constants
    counter = 0
    shootcounter = 0
    walklooper = 0

    # images
    idle = pygame.image.load("./Images/enemies/laser-flea/idle_change_direction.png").convert_alpha()
    idleleft = Animation(idle, 222, 222, flip=True, num=1)
    idleleft.currenty = (idleleft.dy * 2)
    idleleft.scalefactor = 200
    idleleft.build()
    idleleft = idleleft.animation
    idleright = Animation(idle, 222, 222, num=1)
    idleright.currenty = (idleright.dy * 2)
    idleright.scalefactor = 200
    idleright.build()
    idleright = idleright.animation

    idlertol = Animation(idle, 222, 222, flip=True, num=1)
    idlertol.scalefactor = 200
    idlertol.build()
    idlertol = idlertol.animation

    idleltor = Animation(idle, 222, 222, num=1)
    idleltor.scalefactor = 200
    idleltor.build()
    idleltor = idleltor.animation

    walk = pygame.image.load("./Images/enemies/laser-flea/walk_change_direction.png").convert_alpha()
    walkrtol = Animation(walk, 220, 230, num=1)
    walkrtol.scalefactor = 200
    walkrtol.build()
    walkrtol = walkrtol.animation

    walkltor = Animation(walk, 220, 230, flip=True, num=1)
    walkltor.scalefactor = 200
    walkltor.build()
    walkltor = walkltor.animation

    walkleft = walkrtol[10:]
    walkright = walkltor[10:]

    laseratk = pygame.image.load("./Images/enemies/laser-flea/lasser_swept.png").convert_alpha()
    laserleft = Animation(laseratk, 221, 224)
    laserleft.scalefactor = 200
    laserleft.build()
    laserleft = laserleft.animation[0:35]
    laserright = Animation(laseratk, 221, 224, flip=True)
    laserright.scalefactor = 200
    laserright.build()
    laserright = laserright.animation[0:35]

    laser = pygame.image.load("./Images/enemies/laser-flea/laser.png").convert_alpha()
    laser = Animation(laser, 1464, 133, scale=False)
    laser.build()
    laser = laser.animation[5:6]
    laser = laser[0]
    laser = pygame.transform.scale(laser, (laser.get_width(), 50)).convert_alpha()
    i = pygame.Surface((100, 64)).convert_alpha()
    i.fill((0, 0, 0, 0))
    i.blit(laser, (0 - 500, 0))
    laser = i
    laser = pygame.transform.scale(laser, (1500, laser.get_height()))

    animationdic = {
        0: idleleft,
        1: idleright,
        2: idleltor,
        3: idlertol,
        4: laserleft,
        5: laserright,
        6: walkleft,
        7: walkright,
        8: walkltor,
        9: walkrtol,
    }

    def __init__(self, direction):
        pygame.sprite.Sprite.__init__(self)
        self.dead = False
        self.grounded = True
        if direction == "right":
            self.state = self.states.WALKRIGHT
            self.animation = self.animationdic[self.state]
            self.image = self.animation[0]
            self.facing = "right"
        else:
            self.state = self.states.WALKLEFT
            self.animation = self.animationdic[self.state]
            self.image = self.animation[0]
            self.facing = "left"
        self.index = 0
        self.width = self.image.get_width()
        self.height = self.image.get_height()
        self.rect = self.image.get_rect()
        self.rect.center = (self.image.get_width()/2, self.image.get_height()/2)
        self.rect.x = map.get_width() - 2000
        self.rect.y = map.get_height() - (map.get_height()/4)
        self.vel = 3
        self.hitmask = pygame.mask.from_surface(self.image)
        self.shot = False
        self.health = 100

    def update(self):
        self.animation = self.animationdic[self.state]
        self.think()

        if self.state == self.states.LASERLEFT or self.state == self.states.LASERRIGHT:
            if self.counter >= 1:
                self.index += 1
                self.counter = 0
        elif self.state == self.states.IDLELEFT or self.state == self.states.IDLERIGHT or self.state == self.states.IDLELTOR or self.state == self.states.IDLERTOL:
            if self.counter >= 1:
                self.index += 1
                self.counter = 0
        else:
            if self.counter >= 1:
                self.index += 1
                self.counter = 0

        if self.state == self.states.IDLELEFT:
            self.image = self.animation[self.index]
            self.facing = "left"
            if self.index == len(self.animation) - 1:
                self.index = 0
                self.state = self.states.WALKLEFT
                self.animation = self.animationdic[self.state]
                self.image = self.animation[self.index]
        elif self.state == self.states.IDLERIGHT:
            self.image = self.animation[self.index]
            self.facing = "right"
            if self.index == len(self.animation) - 1:
                self.index = 0
                self.state = self.states.WALKRIGHT
                self.animation = self.animationdic[self.state]
                self.image = self.animation[self.index]
        elif self.state == self.states.IDLELTOR:
            self.image = self.animation[self.index]
            if self.index == len(self.animation) - 1:
                self.index = 0
                self.state = self.states.IDLERIGHT
                self.facing = "right"
                self.animation = self.animationdic[self.state]
                self.image = self.animation[self.index]
        elif self.state == self.states.IDLERTOL:
            self.image = self.animation[self.index]
            if self.index == len(self.animation) - 1:
                self.index = 0
                self.state = self.states.IDLELEFT
                self.facing = "left"
                self.animation = self.animationdic[self.state]
                self.image = self.animation[self.index]
        elif self.state == self.states.WALKLEFT:
            self.image = self.animation[self.index]
            self.rect.x -= self.vel
            self.facing = "left"
            if self.index == len(self.animation) - 1:
                self.index = 0
                if self.walklooper > 10:
                    self.walklooper = 0
                    self.state = self.states.WALKLTOR
                    self.animation = self.animationdic[self.state]
                    self.image = self.animation[self.index]
                else:
                    self.walklooper += 1
        elif self.state == self.states.WALKRIGHT:
            self.image = self.animation[self.index]
            self.rect.x += self.vel
            self.facing = "right"
            if self.index == len(self.animation) - 1:
                self.index = 0
                if self.walklooper > 10:
                    self.walklooper = 0
                    self.state = self.states.WALKRTOL
                    self.animation = self.animationdic[self.state]
                    self.image = self.animation[self.index]
                else:
                    self.walklooper += 1
        elif self.state == self.states.WALKLTOR:
            self.image = self.animation[self.index]
            if self.index == len(self.animation) - 1:
                self.index = 0
                self.state = self.states.WALKRIGHT
                self.facing = "right"
                self.animation = self.animationdic[self.state]
                self.image = self.animation[self.index]
        elif self.state == self.states.WALKRTOL:
            self.image = self.animation[self.index]
            if self.index == len(self.animation) - 1:
                self.index = 0
                self.state = self.states.WALKLEFT
                self.facing = "left"
                self.animation = self.animationdic[self.state]
                self.image = self.animation[self.index]
        elif self.state == self.states.LASERLEFT:
            self.image = self.animation[self.index]
            self.facing = "left"
            if not self.shot:
                if self.index >= len(self.animation) - 8:
                    self.shot = True
                    self.rect.x -= 1500
            if self.index >= len(self.animation) - 8:
                self.shootlaser()
            if self.index == len(self.animation) - 1:
                self.rect.x += 1500
                self.index = 0
                self.state = self.states.IDLELEFT
                self.animation = self.animationdic[self.state]
                self.image = self.animation[self.index]
                self.rect = Rect(self.rect.x, self.rect.y, self.image.get_width(), self.image.get_height())
                self.shot = False

        elif self.state == self.states.LASERRIGHT:
            self.facing = "right"
            self.image = self.animation[self.index]
            if self.index >= len(self.animation) - 8:
                self.shootlaser()
            if self.index == len(self.animation) - 1:
                self.index = 0
                self.state = self.states.IDLERIGHT
                self.animation = self.animationdic[self.state]
                self.image = self.animation[self.index]
                self.rect = Rect(self.rect.x, self.rect.y, self.image.get_width(), self.image.get_height())

        if self.rect.x <= 0:
            self.rect.x = 0
        if self.rect.right >= Config.map.get_width():
            self.rect.right = Config.map.get_width()

        self.grounded = False
        for g in ground_sprites:
            if self.rect.bottom <= g.rect.top + 15:
                if g.rect.left < self.rect.x < g.rect.right or (g.rect.left < self.rect.right < g.rect.right):
                    if self.rect.bottom >= g.rect.top:
                        self.rect.bottom = g.rect.top
                        self.grounded = True

        self.counter += 1
        if self.facing == "right":
            pygame.draw.line(Config.map, Config.RED, (self.rect.x, self.rect.y - 10), (self.rect.x + 200, self.rect.y - 10),
                             4)
            pygame.draw.line(Config.map, Config.GREEN, (self.rect.x, self.rect.y - 10),
                             (self.rect.x + ((self.health / 100) * 200), self.rect.y - 10), 4)
        else:
            pygame.draw.line(Config.map, Config.RED, (self.rect.x + self.rect.width - 200, self.rect.y - 10),
                             (self.rect.x + self.rect.width, self.rect.y - 10),
                             4)
            pygame.draw.line(Config.map, Config.GREEN, (self.rect.x + self.rect.width - 200, self.rect.y - 10),
                             (self.rect.x + self.rect.width - 200 + ((self.health / 100) * 200), self.rect.y - 10), 4)

        for p in player_sprite:
            for b in p.bullets:
                bx = b.rect.x
                by = b.rect.y
                sx = self.rect.x
                sy = self.rect.y

                offset = (bx - sx, by - sy)
                result = self.hitmask.overlap(b.hitmask, offset)
                if result:
                    if self.state != self.states.LASERLEFT or self.state != self.states.LASERRIGHT:
                        if (self.rect.center[1] - 20) <= b.rect.center[1] <= (self.rect.center[1] + 20):
                            if self.facing == "right":
                                if self.rect.right < p.rect.x:
                                    p.bullets.remove(b)
                                    if self.health > 0:
                                        self.health -= 5
                            else:
                                if self.rect.left > p.rect.x:
                                    p.bullets.remove(b)
                                    if self.health > 0:
                                        self.health -= 5
                    p.bullets.remove(b)

        if self.health <= 0:
            self.dead = True

    def think(self):
        for p in player_sprite:
            if self.state == self.states.WALKLEFT or self.state == self.states.IDLELEFT:
                if self.state != self.states.LASERLEFT:
                    if self.rect.y <= p.rect.top and (self.rect.bottom >= p.rect.bottom):
                        if self.rect.left - WIDTH/2 + 200 <= p.rect.right <= self.rect.left:
                            self.state = self.states.LASERLEFT
                            self.animation = self.animationdic[self.state]
                            self.index = 0
            elif self.state == self.states.WALKRIGHT or self.state == self.states.IDLERIGHT:
                if self.state != self.states.LASERRIGHT:
                    if self.rect.y <= p.rect.top and (self.rect.bottom >= p.rect.bottom):
                        if self.rect.right <= p.rect.left <= self.rect.right + WIDTH/2 - 100:
                            self.state = self.states.LASERRIGHT
                            self.animation = self.animationdic[self.state]
                            self.index = 0

    def shootlaser(self):

        if self.state == self.states.LASERLEFT:
            newimg = Surface(
                ((self.laser.get_width() + self.image.get_width()), self.image.get_height())).convert_alpha()
            newimg.fill((0, 0, 0, 0))
            newimg.blit(self.laser, (30, newimg.get_height()/2 - 25))
            newimg.blit(self.image, (newimg.get_width() - self.image.get_width(), 0))
            self.image = newimg
            self.rect = Rect(self.rect.x , self.rect.y, self.image.get_width(), self.image.get_height())
            self.hitmask = pygame.mask.from_surface(self.image)

        elif self.state == self.states.LASERRIGHT:
            newimg = Surface(
                ((self.laser.get_width() + self.image.get_width()), self.image.get_height())).convert_alpha()
            newimg.fill((0, 0, 0, 0))
            newimg.blit(self.laser, (self.image.get_width() - 30, newimg.get_height()/2 - 25))
            newimg.blit(self.image, (0, 0))
            self.image = newimg
            self.rect = Rect(self.rect.x, self.rect.y, self.image.get_width(), self.image.get_height())
            self.hitmask = pygame.mask.from_surface(self.image)

        for p in player_sprite:
            px = p.rect.x
            py = p.rect.y
            sx = self.rect.x
            sy = self.rect.y

            offset = (px - sx, py - sy)
            result = self.hitmask.overlap(p.hitmask, offset)
            if result:
                if p.health > 0:
                    p.damage(10)