from django.shortcuts import render
from .models import *
from django.http import HttpResponse
import json
from rest_framework import generics
from .serializers import *

# Create your views here.
def dashboard(request):
    return render(request,"dashboard.html")

def department(request):
    #department_list = Department.objects.all()
    department_list = Department.objects.raw('SELECT * FROM emp_department')
    return render(request,'departments.html',{'department_list':department_list})

def manage_departments(request):
    department = {}
    if request.method == 'GET':
        data =  request.GET
        id = ''
        if 'id' in data:
            id= data['id']
            print(id)
        if id.isnumeric() and int(id) > 0:
            department = Department.objects.raw(f'SELECT * FROM emp_department WHERE emp_department.id = {id}')[0]
            #department = Department.objects.filter(pk=id).first()
            print(department)

    context = {
        'department' : department
    }
    return render(request, 'manage_department.html',context)

def save_department(request):
    data =  request.POST
    resp = {'status':'failed'}
    try:
        if (data['id']).isnumeric() and int(data['id']) > 0 :
            d_name=data['name']
            d_id=data['id']
            #save_department = Department.objects.filter(id = data['id']).update(department=data['name'])
            q=f'UPDATE emp_department SET department = \'{d_name}\' WHERE emp_department.id = {d_id}'
            Department.objects.raw(q)[:]
        else:
            d_name=data['name']
            q=f'INSERT INTO emp_department (department) VALUES (\'{d_name}\') RETURNING emp_department.id'
            Department.objects.raw(q)[:]
            #save_department = Department(department=data['name'])
        resp['status'] = 'success'
    except Exception as e:
        print(e)
        resp['status'] = 'success'
    return HttpResponse(json.dumps(resp), content_type="application/json")

def delete_department(request):
    data =  request.POST
    resp = {'status':''}
    try:
        d_id=data['id']
        resp['status'] = 'success'
        Department.objects.raw(f'DELETE FROM emp_department WHERE emp_department.id = {d_id}')[:]
        #Department.objects.filter(id = data['id']).delete()

    except:
        resp['status'] = 'success'
    return HttpResponse(json.dumps(resp), content_type="application/json")

def employee(request):
    #employee_list = Employee.objects.all()
    employee_list =Employee.objects.raw('SELECT * FROM emp_employee')
    return render(request,'employees.html',{'employee_list':employee_list})

def view_employee(request):
    employee = {}
    if request.method == 'GET':
        data =  request.GET
        id = ''
        if 'id' in data:
            id= data['id']
        if id.isnumeric() and int(id) > 0:
            employee = Employee.objects.raw(f'SELECT * FROM emp_employee WHERE emp_employee.id = {id}')[0]
            #employee = Employee.objects.filter(pk=id).first()
    context = {
        'employee' : employee,

    }
    return render(request, 'view_employee.html',context)

def manage_employees(request):
    employee = {}
    #departments = Department.objects.all()
    departments =Department.objects.raw('SELECT * FROM emp_department')
    if request.method == 'GET':
        data =  request.GET
        id = ''
        if 'id' in data:
            id= data['id']
        if id.isnumeric() and int(id) > 0:
            employee = Employee.objects.raw(f'SELECT * FROM emp_employee WHERE emp_employee.id = {id}')[0]
            #employee = Employee.objects.filter(id=id).first()
    context = {
        'employee' : employee,
        'departments' : departments,

    }
    return render(request, 'manage_employee.html',context)

def save_employee(request):
    data =  request.POST
    resp = {'status':'failed'}
    print(data)
    if (data['id']).isnumeric() and int(data['id']) > 0:
        check  = Employee.objects.filter(pk = data['id'])
        if check:
            d_id=data['department_id']
            e_id=data['id']
            name=data['firstname']
            salary=data['salary']
            #dept = Department.objects.filter(id=data['department_id']).first()

            q=f'UPDATE emp_employee SET name = \'{name}\', department_id = {d_id}, salary = {salary} WHERE emp_employee.id = {e_id}'
            Employee.objects.raw(q)[:]

            #Employee.objects.filter(id = data['id']).update(name = data['firstname'],department = dept,salary = data['salary'])
        resp['status'] = 'success'

    else:
        try:
            d_id=data['department_id']
            e_id=data['id']
            name=data['firstname']
            salary=data['salary']
            # dept = Department.objects.filter(id=data['department_id']).first()
            # save_employee = Employee(name = data['firstname'],department= dept,salary = data['salary'])
            # save_employee.save()
            q=f'INSERT INTO emp_employee (name, salary, department_id) VALUES (\'{name}\', {salary}, {d_id}) RETURNING emp_employee.id'
            Employee.objects.raw(q)[:]
            resp['status'] = 'success'
        except Exception as e:
            resp['status'] = 'success'
            print(e)
    return HttpResponse(json.dumps(resp), content_type="application/json")

def delete_employee(request):
    data =  request.POST
    resp = {'status':''}
    try:
        #Employee.objects.filter(pk = data['id']).delete()
        e_id=data['id']
        Employee.objects.raw(f'DELETE FROM emp_employee WHERE emp_employee.id= {e_id}')[:]
        resp['status'] = 'success'
    except:
        resp['status'] = 'success'
    return HttpResponse(json.dumps(resp), content_type="application/json")

def task(request):
    #task_list = Task.objects.all()
    task_list =Task.objects.raw('SELECT * FROM emp_task')
    return render(request,'task.html',{'task_list':task_list})

def view_task(request):
    task = {}
    if request.method == 'GET':
        data =  request.GET
        id = ''
        if 'id' in data:
            id= data['id']
            print("aaaa",id)
        if id.isnumeric() and int(id) > 0:
            #task = Task.objects.filter(pk=id).first()
            task=Task.objects.raw(f'SELECT * FROM emp_task WHERE emp_task.id = {id}')[0]
    context = {
        'task' : task,

    }
    return render(request, 'view_task.html',context)

def manage_task(request):
    task = {}
    status = ["ACCEPTED","COMPLETED"]
    if request.method == 'GET':
        data =  request.GET
        id = ''
        if 'id' in data:
            id= data['id']
        if id.isnumeric() and int(id) > 0:
            #task = Task.objects.filter(id=id).first()
            task=Task.objects.raw(f'SELECT * FROM emp_task WHERE emp_task.id = {id}')[0]
    context = {
        'task' : task,
        'status' : status,

    }
    return render(request, 'manage_task.html',context)

def save_task(request):
    data =  request.POST
    resp = {'statuss':'failed'}
    try:
        if (data['id']).isnumeric() and int(data['id']) > 0 :
            t_id=data['id']
            t_name=data['id']
            t_status=data['status']
            #save_task = Task.objects.filter(pk = data['id']).update(t_name=data['id'],status=data['status'])
            q=f'UPDATE emp_task SET t_name = \'{t_name}\', status = \'{t_status}\' WHERE emp_task.id = {t_id}'
            Task.objects.raw(q)[:]

            resp['statuss'] = 'success'
    except:
        resp['statuss'] = 'success'
    return HttpResponse(json.dumps(resp), content_type="application/json")

def delete_task(request):
    data =  request.POST
    resp = {'status':''}
    try:
        #Task.objects.filter(id = data['id']).delete()
        t_id=data['id']
        Task.objects.raw(f'DELETE FROM emp_task WHERE emp_task.id = {t_id}')[:]
        resp['status'] = 'success'
    except:
        resp['status'] = 'success'
    return HttpResponse(json.dumps(resp), content_type="application/json")

def add_new_task(request):
    status = ["ACCEPTED","COMPLETED"]
    #employee = Employee.objects.all()
    employee =Employee.objects.raw('SELECT * FROM emp_employee')
    context = {
        'employee' : employee,
        'status' : status,

    }
    return render(request, 'add_new_task.html',context)

def save_new_task(request):
    data =  request.POST
    resp = {'status':''}
    try:
        # emp=Employee.objects.get(pk=data['employee_id'])
        # Task(employee=emp,t_name=data['task_name'],status=data['status_id']).save()
        e_id=data['employee_id']
        t_name=data['task_name']
        t_status=data['status_id']
        q=f'INSERT INTO emp_task (employee_id, t_name, status) VALUES ({e_id}, \'{t_name}\', \'{t_status}\') RETURNING emp_task.id'
        Task.objects.raw(q)[:]
        resp['status'] = 'success'
    except:

        resp['status'] = 'success'
    return HttpResponse(json.dumps(resp), content_type="application/json")

def signle_emp(request):
    emp=Employee.objects.all()
    return render(request,"single.html",{"emp":emp})

def emp_task(request):
    task = {}
    if request.method == 'GET':
        data =  request.GET
        id = ''
        if 'id' in data:
            id= data['id']
            print("aaaa",id)
        if id.isnumeric() and int(id) > 0:
            #task = Task.objects.filter(pk=id).first()
            task=Task.objects.raw(f'SELECT * FROM emp_task WHERE emp_task.id = {id}')[0]
    context = {
        'task' : task,

    }
    return render(request, 'single.html',context)

##############################################################################################################
class DepartmentList(generics.ListAPIView):
    queryset = Department.objects.raw('SELECT * FROM emp_department')
    serializer_class = DepartmentSerializer

class DepartmentCreate(generics.CreateAPIView):
    queryset = Department.objects.raw('SELECT * FROM emp_department')
    serializer_class = DepartmentSerializer

class DepartmentRetrieve(generics.RetrieveAPIView):
    queryset = Department.objects.raw('SELECT * FROM emp_department')
    serializer_class = DepartmentSerializer

class DepartmentUpdate(generics.UpdateAPIView):
    queryset = Department.objects.raw('SELECT * FROM emp_department')
    serializer_class = DepartmentSerializer

class DepartmentDestroy(generics.DestroyAPIView):
    queryset = Department.objects.raw('SELECT * FROM emp_department')
    serializer_class = DepartmentSerializer

class EmployeeList(generics.ListAPIView):
    queryset = Employee.objects.raw('SELECT * FROM emp_employee')
    serializer_class = EmployeeSerializer

class EmployeeCreate(generics.CreateAPIView):
    queryset = Employee.objects.raw('SELECT * FROM emp_employee')
    serializer_class = EmployeeSerializer

class EmployeeRetrieve(generics.RetrieveAPIView):
    queryset = Employee.objects.raw('SELECT * FROM emp_employee')
    serializer_class = EmployeeSerializer

class EmployeeUpdate(generics.UpdateAPIView):
    queryset = Employee.objects.raw('SELECT * FROM emp_employee')
    serializer_class = EmployeeSerializer

class EmployeeDestroy(generics.DestroyAPIView):
    queryset = Employee.objects.raw('SELECT * FROM emp_employee')
    serializer_class = EmployeeSerializer

class TaskList(generics.ListAPIView):
    queryset = Task.objects.raw('SELECT * FROM emp_task')
    serializer_class = TaskSerializer

class TaskCreate(generics.CreateAPIView):
    queryset = Task.objects.raw('SELECT * FROM emp_task')
    serializer_class = TaskSerializer

class TaskRetrieve(generics.RetrieveAPIView):
    queryset = Task.objects.raw('SELECT * FROM emp_task')
    serializer_class = TaskSerializer

class TaskUpdate(generics.UpdateAPIView):
    queryset = Task.objects.raw('SELECT * FROM emp_task')
    serializer_class = TaskSerializer

class TaskDestroy(generics.DestroyAPIView):
    queryset = Task.objects.raw('SELECT * FROM emp_task')
    serializer_class = TaskSerializer


import logging

from scrim_bot.core.role import Role, RoleIsInvalid

logger = logging.getLogger(__name__)

class Player:
    """
    Represents a player taking part in inhouse games
    """

    _id: int
    _name: str
    roles: list[Role]
    elo: float
    summoner_name: str

    def __init__(self, _id: int, name: str, roles: list[str] = None, elo: float = None, summoner_name: str = None):
        self._id = _id
        self._name = name
        self.roles = []
        self.elo = elo
        self.summoner_name = summoner_name

        if roles is not None:
            self.addRoles(roles)

    def __eq__(self, other):
        if isinstance(other, Player):
            return self.id == other.id
        return False

    def __name__(self, other):
        return not self.__eq__(other)

    def addRole(self, role: str):
        validated_role = Role.validateRole(role)
        if validated_role == Role.INVALID:
            logger.error(f"Role {role} is invalid")
            raise RoleIsInvalid(role)
        if validated_role in self.roles:
            logger.error(f"Role {validated_role.name} is already registered")
            return
        logger.info(f"Role {validated_role.name} has been registered")
        self.roles.append(validated_role)

    def addRoles(self, role_list: list[str]):
        for role in role_list:
            self.addRole(role)

    def removeRole(self, role: str):
        validated_role = Role.validateRole(role)
        if validated_role == Role.INVALID:
            logger.error(f"Role {role} is invalid")
            raise RoleIsInvalid(role)
        if validated_role not in self.roles:
            logger.error(f"Role {validated_role.name} is not registered")
            return
        logger.info(f"Role {validated_role} has been removed")
        self.roles.remove(validated_role)

    def removeRoles(self, role_list: list[str]):
        for role in role_list:
            self.removeRole(role)

    def getRoles(self):
        return [role.name for role in self.roles]

    @property
    def id(self):
        return self._id

    @property
    def name(self):
        return self._name

    # Encoding Player() object for MongoDB queries
    def encode_player(self):
        return {"_id": self._id, "name": self._name, "roles": self.getRoles(), "elo": self.elo,
                "summoner_name": self.summoner_name}


from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
import logging

_logger = logging.getLogger(__name__)

class StudentTranscript(models.TransientModel):
    _name = "student.transcript"
    _description = "Student Transcript"
    from_date = fields.Date(
        'From Date', required=True, default=lambda self: fields.Date.today())
    to_date = fields.Date(
        'To Date', required=True, default=lambda self: fields.Date.today())
    course_id = fields.Many2one('op.course', 'Course', required=True, default=lambda self: self._get_default_course())
    batch_id = fields.Many2one(
        'op.batch', 'Term', required=True, default=lambda self: self._get_default_batch())
    semester_id = fields.Many2one('pm.semester', 'Semester')
    final = fields.Boolean('Final Transcript')

    def _get_default_batch(self):
        student_id = self.env.context.get('active_id', False)
        student_course = self.env['op.student.course'].search([('student_id', '=', student_id),
                                                               ('p_active', '=', True)], limit=1)
        return student_course.batch_id.id

    def _get_default_course(self):
        student_id = self.env.context.get('active_id', False)
        student_course = self.env['op.student.course'].search([('student_id', '=', student_id),
                                                               ('p_active', '=', True)], limit=1)
        return student_course.course_id.id

    def print_report(self):
        student_id = self.env.context.get('active_id', False)
        order = self.semester_id.semester_order
        is_internship = self.semester_id.is_internship
        if self.semester_id:
            print("self.semester_id.start_date")
            semester_start_date = self.semester_id.start_date.strftime('%d/%m/%Y')
            semester_end_date = self.semester_id.end_date.strftime('%d/%m/%Y')
        else:
            semester_start_date = ''
            semester_end_date = ''

        res = {
            "student_id": student_id,
            "semester_order": order,
            "semester_start_date": semester_start_date,
            "semester_end_date": semester_end_date,
            "course_id": self.course_id.id,
            "batch_id": self.batch_id.id,
            "semester_id": self.semester_id.id,
            "is_final": self.final
        }
        if self.final:
            print("check validation")
            internship_semester_count = self.env['pm.semester'].search_count([('batch_id', '=', self.batch_id.id),
                                                                              ('is_internship', '=', True)])

            normal_semester_count = self.env['pm.semester'].search_count([('batch_id', '=', self.batch_id.id),
                                                                          ('is_internship', '=', False)])

            student_internship_count = self.env['op.placement.offer'].search_count([('student_id', '=', student_id),
                                                                                ('batch_id', '=', self.batch_id.id),
                                                                                ('p_completed', '=', True)])

            mark_sheets_count = self.env['pm.semester.marksheet.register'].search_count(
                [('batch_id', '=', self.batch_id.id),
                 ('semester_id.is_internship', '=', False),
                 ('state', '=', 'validated')])

            _logger.info("---------------*****************************---------------")
            _logger.info('internship_semester_count %s', internship_semester_count)
            _logger.info('normal_semester_count %s', normal_semester_count)
            _logger.info('student_internship_count %s', student_internship_count)
            _logger.info('mark_sheets_count %s', mark_sheets_count)

            # if internship_semester_count != student_internship_count:
            #     raise ValidationError(
            #         "Internship hasn't finished yet. Please completed before generating transcript")

            if normal_semester_count != mark_sheets_count:
                raise ValidationError("Please validate semester result before generating transcript")

            return self.env.ref(
                'pm_students.action_get_report_transcript') \
                .report_action(self, data=res)

        elif is_internship:
            print('internshpip')
            count = self.env['op.placement.offer'].search_count(
                [('student_id', '=', student_id), ('semester_id', '=', self.semester_id.id),
                 ('p_completed', '=', True)])
            if count <= 0:
                raise ValidationError("Internship hasn't finised yet. Please completed before generating transcript")
            else:
                return self.env.ref(
                    'pm_students.action_get_report_transcript') \
                    .report_action(self, data=res)
        else:
            print("check validation")
            count = self.env['pm.semester.marksheet.register'].search_count(
                [('semester_id', '=', self.semester_id.id), ('state', '=', 'validated')])
            if count <= 0:
                raise ValidationError("Please validate semester result before generating transcript")
            else:
                return self.env.ref(
                    'pm_students.action_get_report_transcript') \
                    .report_action(self, data=res)


import numpy as np
import sys

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.batchnorm = nn.BatchNorm2d(3, affine=False)
        self.pad2 = nn.ConstantPad2d(2, 0)
        self.pad1 = nn.ConstantPad2d(1, 0)

        self.conv1 = nn.Conv2d(3, 32, 3)
        self.conv2 = nn.Conv2d(32, 64, 3)
        self.conv3 = nn.Conv2d(64, 32, 3)
        self.conv4 = nn.Conv2d(32, 16, 3)
        self.conv5 = nn.Conv2d(16, 1, 5)

    def forward(self, x):
        x = self.batchnorm(x.float())
        x = self.pad1(F.relu(self.conv1(x)))
        x = self.pad1(F.relu(self.conv2(x)))
        x = self.pad1(F.relu(self.conv3(x)))
        x = self.pad1(F.relu(self.conv4(x)))
        x = self.pad2(F.relu(self.conv5(x)))

        x = x.view(-1, 225)

        return x

def toTripleBoard(board, side):
    curBoard = np.zeros(shape=(3, 15, 15), dtype=np.float)
    blackBoard = np.zeros(shape=(15, 15), dtype=np.float)
    whiteBoard = np.zeros(shape=(15, 15), dtype=np.float)

    if side == 1:
        blackTurnBoard = np.ones(shape=(15, 15), dtype=np.float)
    else:
        blackTurnBoard = -np.ones(shape=(15, 15), dtype=np.float)
    curBoard[0,:] = blackTurnBoard
    for i in range(15):
        for j in range(15):
            if board[i, j] == 1:
	            blackBoard[i, j] = 1
            if board[i, j] == -1:
                whiteBoard[i, j] = -1
    curBoard[1,:] = blackBoard
    curBoard[2,:] = whiteBoard
    return curBoard

def isGg(board, color):
    if color == 0:
        size = 4
    else:
        size = -4
    for i in range(15):
        for j in range(10):
            if board[i, j] + board[i, j + 1] + board[i, j + 2] + board[i, j + 3] + board[i, j + 4] == size:
                for k in range(5):
                    if board[i, j + k] == 0:
                        return i, j + k

    for i in range(10):
        for j in range(15):
            if board[i, j] + board[i + 1, j] + board[i + 2, j] + board[i + 3, j] + board[i + 4, j] == size:
                for k in range(5):
                    if board[i + k, j] == 0:
                        return i + k, j

    for i in range(10):
        for j in range(10):
            if board[i, j] + board[i + 1, j + 1] + board[i + 2, j + 2] + board[i + 3, j + 3] + board[i + 4, j + 4] == size:
                for k in range(5):
                    if board[i + k, j + k] == 0:
                        return i + k, j + k
    for i in range(5, 15):
        for j in range(10):
            if board[i, j] + board[i - 1, j + 1] + board[i - 2, j + 2] + board[i - 3, j + 3] + board[i - 4, j + 4] == size:
                for k in range(5):
                    if board[i - k, j + k] == 0:
                        return i - k, j + k
    return (-1, -1)

def toTurn(turn):
    letter = ord(turn[0]) - ord('a')
    num = int(turn[1:]) - 1
    return letter, num

def to_move(pos):
    return idx2chr[pos[0]] + str(pos[1] + 1)

def to_pos(move):
    return chr2idx[move[0]], int(move[1:]) - 1

if __name__ == "__main__":
	device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
	torch.backends.cudnn.benchmark = True
	idx2chr = 'abcdefghjklmnop'
	chr2idx = {letter: pos for pos, letter in enumerate(idx2chr)}
	net = Net()
	net = net.to(device)
	net.load_state_dict(torch.load("finalNN.txt"))

	while True:
		gameStr = sys.stdin.readline()#input()#
		if not gameStr:
			break
		gameStr = gameStr.strip().split()
		curBoard = np.zeros(shape=(15, 15), dtype=np.float)

		for i in range(len(gameStr)):
		    x, y = to_pos(gameStr[i])
		    if i % 2 == 0:
		        curBoard[x, y] = 1
		    else:
		        curBoard[x, y] = -1

		color = len(gameStr) % 2

		board = toTripleBoard(curBoard, color)
		with torch.no_grad():
		    outputs = net(torch.unsqueeze(torch.from_numpy(board), 0))
		    _, netTurn = torch.max(outputs, 1)
		    netTurn = int(netTurn)
		    turnX, turnY = netTurn // 15, netTurn % 15
		    while curBoard[turnX, turnY] != 0:
		        outputs[netTurn] = 0
		        _, netTurn = torch.max(outputs, 1)
		        netTurn = int(netTurn)
		        turnX, turnY = netTurn // 15, netTurn % 15

		ggTest = isGg(curBoard, color)
		if ggTest != (-1, -1):
		    turnX, turnY = ggTest

		myTurn = to_move((turnX, turnY))
		sys.stdout.write(myTurn + '\n')
		sys.stdout.flush()


import matplotlib.pyplot as plt
import numpy as np
import scipy.optimize as spy

from . import AstroLib_Basic as AL_BF

################################################################################
# CREATE BODY WITH ITS CHARACTERISTICS
################################################################################
class Spacecraft:
    """
    Spacecraft class: create spacecraft with its mass and propulsion parameters
    """
    def __init__(self, *args, **kwargs):
        """
        Constructor: input the main characteristics
        INPUTS:
        ADDITIONAL INPUTS:
            Isp: specific impulse
            m_dry: dry mass of the spacecraft
            T: thrust of the spacecraft
            g0: acceleration of gravity
        """
        # Propulsive parameters
        self.Isp = kwargs.get('Isp', 3100)
        self.m_dry = kwargs.get('m_dry', 747)
        self.T = kwargs.get('T', 90e-3)
        self.g0 = kwargs.get('g0', AL_BF.g0)

    def MassChange(self, m, deltav):
        """
        MassChange: change (decrease) mass of the spacecraft when an impulse is
        applied
        INPUTS:
            m: current mass of the spacecraft (fuel+drymass)
            deltav: magnitude of the impulse applied
        OUTPUTS:
            mf: final mass
        """
        mf = m * np.exp( -deltav/(self.g0 * self.Isp) )
        return mf

    def MassChangeInverse(self, m, deltav):
        """
        MassChangeInverse: change (increase) the mass to obtain the mass of the
            spacecraft before an impulse was applied
        IMPUTS:
            m: current mass of the spacecraft (fuel+drymass)
            deltav: magnitude of the impulse applied
        OUTPUTS:
            mi: final mass
        """
        mi = m/np.exp( -deltav /(self.g0 * self.Isp) )
        return mi

class Body:
    """
    Body: contains the main characteristics of a celestial body.
    """
    def __init__(self, name, color = 'black', Mass = 0, radius = 0, *args, **kwargs):
        """
        Constructor:
        INPUTS:
            name: name of the body.
        ADDITIONAL INPUTS:
            Mass: mass of the body
            radius: radius of the body
            color: color to plot the body
            mu = gravitational parameter of the body. If it is not given, it
            is calculated as G*Mass
            orb: orbit of the body. Orbit is an object.
        """
        self.name = name
        self.R = radius
        self.M = Mass
        self.color = color
        #if mu it is not given, it is calculated with the mass:
        self.mu = kwargs.get('mu', self.M * AL_BF.ConstantsBook().G)
        # Orbit of the body:
        self.orb = kwargs.get('orbit', 'No information of the orbit')

    def addOrbit(self, orbit):
        """
        addOrbit: include an orbit to the body
        INPUTS:
            orbit: orbit object
        """
        self.orb = orbit

################################################################################
# CREATE ORBIT AND ITS CHARACTERISTICS
# Valid for an elliptical orbit. Application of the 2 Body Problem.
################################################################################
class CircularOrbit:
    """
    CircularOrbit: creation of a circular object around a central body.
    """
    def __init__(self, a, CentralBody, *args, **kwargs):
        """
        Constructor:
        INPUTS:
            a: semi-major axis of the orbit. Same as the radius of the orbit.
            CentralBody: central body about which the orbit is performed.
        ADDITIONAL INPUTS:
            Body: body object that is orbitting.
        """
        self.a = a
        self.CentralBody = CentralBody
        self.Body = kwargs.get('Body', 'Unknown')

        # Mean motion, period of the orbit
        self.n = np.sqrt(self.CentralBody.mu / self.a**3)
        self.T = 2*np.pi / self.n

#Review Kepler 2000: not working
class BodyOrbit:
    """
    BodyOrbit: creation of an orbit object around a central body. Contains the
    main characteristics of an ELLIPTICAL orbit. Application of the 2 Body problem.
    """
    def __init__(self, x, typeParam, Body, *args, **kwargs):
        """
        Constructor:
        INPUTS:
            x: Two main cases are possible:
                Keplerian elements as inputs:
                    a: semi-major axis (m)
                    e: eccentricity of the orbit
                    i: inclination of the orbit
                    RAAN: right ascension of the ascending node
                    omega: argument of the periapsis
                    M: mean anomaly
                    units: 'deg' indicates that the angles have to be
                    transformed to radians
                Cartesian elements:
                    x,y,z,xdot,ydot,zdot: position and velocity at a given time
            typeParam:
                'Keplerian_J2000': x is a matrix with:
                    row 0: Keplerian elements at J2000
                    row 1: centennial rates of change of those elements
                    row 2: date in Julian centuries
                'Keplerian': to use Keplerian parameters as inputs
                'Cartesian': to use Cartesian parameters as inputs
            Body: central Body of the orbit
        ADDITIONAL INPUTS:
            unitsI: units of the angles of x: 'rad' or 'deg'
    """
        self.CentralBody = Body

        ######################################
        #### Case 1: Keplerian Elements J2000 + centennial rates
        ######################################
        # Obtain Keplerian elements at a given date (in Julian Centuries)

        units_input = kwargs.get('unitsI', 'rad')

        if typeParam == 'Keplerian_J2000':
            a,e,i,L,varpi,RAAN = x[0,:] + x[1,:]*x[2,0]
            omega = varpi - RAAN
            M = L - varpi

        ######################################
        #### Case 1: Keplerian Elements given
        ######################################
        elif typeParam == 'Keplerian':
            self.a,self.e,self.i,self.RAAN,self.omega,self.M = x

        if typeParam == 'Keplerian' or typeParam == 'Keplerian_J2000':

            if units_input == 'deg': #Operations are done in radians
                [self.i,self.RAAN,self.omega,self.M] = [AL_BF.deg2rad(angle) \
                for angle in [self.i,self.RAAN,self.omega,self.M]]

            #Obtain the eccentric and true anomaly
            self.theta, self.E, self.M = Kepler(self.M, self.e,'Mean')

            #Obtain the cartesian coordinates
            x = self.Kepl2Cart()
            self.r = x[0:3]
            self.v = x[3:]
            self.h = np.cross(self.r, self.v)

        ######################################
        #### Case 3: Cartesian Elements given"
        ######################################
        elif typeParam == 'Cartesian':
            state = x #in m and m/s

            self.r = state[0:3] # position vector
            self.v = state[3:] # velocity vector
            self.r_mag = np.linalg.norm(self.r) # magnitude of the position vector
            self.v_mag = np.linalg.norm(self.v) # magnitude of the velocity vector

            self.h = np.cross(self.r, self.v)
            self.h_mag = np.linalg.norm(self.h)
            self.N = np.cross(np.array([0,0,1]),self.h)
            self.N_mag = np.linalg.norm(self.N)

            #Keplerian elements
            #semi-major axis. Negative if e>1:
            self.a = 1 / (2/self.r_mag-self.v_mag**2/Body.mu)
            #eccentricity vector:
            self.e_vec = np.cross(self.v,self.h)/Body.mu - self.r/self.r_mag
            self.e = np.linalg.norm(self.e_vec) #eccentricity of the orbit

            self.i = np.arccos(self.h[2]/self.h_mag) #inclination of the orbit

            N_xy = np.sqrt(self.N[0]**2 + self.N[1]**2)
            if N_xy == 0.:
                self.RAAN = 0
            else:
                #right ascension of the ascending node:
                self.RAAN = np.arctan2(self.N[1]/N_xy , self.N[0]/N_xy)

            if self.e == 0.0 or self.N_mag == 0.0:
                self.omega = 0.0
            else:
                #argument of the perihelium:
                self.omega = np.arccos(np.dot(self.e_vec/self.e , self.N/self.N_mag))
                if (np.dot(np.cross(self.N/self.N_mag , self.e_vec),self.h)) <= 0:
                    self.omega = -self.omega

            if self.r_mag == 0.0 or self.e == 0.0:
                self.theta = 0.0
            else:
                 #true anomaly:
                self.theta = np.arccos(np.dot(self.r/self.r_mag , self.e_vec/self.e))
                if (np.dot(np.cross(self.e_vec,self.r),self.h)) <= 0:
                    self.theta = -self.theta

            if self.e < 1:
                #Mean anomaly:
                self.theta, self.E, self.M = Kepler(self.theta,self.e,'True')
            else:
                #Mean anomaly:
                self.theta, self.E, self.M = Keplerh(self.theta,self.e,'True')

        else:
            print('Warning: Specify the type of elements')

        ######################################
        ## More parameters of the orbit
        ######################################
        #Create a vector with all the keplerian elements

        self.KeplerElem = np.array([self.a,self.e,self.i,self.RAAN,self.omega,self.M])
        self.KeplerElem_deg = np.zeros(6)
        self.KeplerElem_deg[0:2] = [self.a,self.e]
        for item, angle in enumerate([self.i,self.RAAN,self.omega,self.M]):
            self.KeplerElem_deg[2+item] = AL_BF.rad2deg(angle)

        #Mean motion, period of the orbit
        self.n = np.sqrt(Body.mu/self.a**3)
        self.T = 2*np.pi * np.sqrt(self.a**3/Body.mu)

        #Geometry of the elliptic trajectory
        self.p = self.a * (1-self.e**2)
        self.rp = self.a * (1-self.e)
        self.ra = self.a * (1+self.e)
        self.b = self.a * np.sqrt(1-self.e**2)

    def Kepl2Cart(self, *args, **kwargs):
        """
        Kepl2Cart: transform from Keplerian elements to cartesian coordinates.
        INPUTS:
        ADDITIONAL INPUTS:
            givenElements: option to input a vector to be transformed. If false,
            the elements from the class are used.

        OUTPUTS:
            [x,y,z,xdot,ydot,zdot]: state of the body given by its position and
            velocity vectors.
        """
        givenElements = kwargs.get('givenElements', 'False')
        if type(givenElements) != str:
            [a,e,i,RAAN,omega,M,theta] = givenElements
        else:
            [a,e,i,RAAN,omega,M,theta] = np.array([self.a,self.e,self.i, \
                self.RAAN,self.omega,self.M,self.theta])

        l1 = np.cos(RAAN)*np.cos(omega) - np.sin(RAAN)*np.sin(omega)*np.cos(i)
        l2 = -np.cos(RAAN)*np.sin(omega) - np.sin(RAAN)*np.cos(omega)*np.cos(i)
        m1 = np.sin(RAAN)*np.cos(omega) + np.cos(RAAN)*np.sin(omega)*np.cos(i)
        m2 = -np.sin(RAAN)*np.sin(omega) + np.cos(RAAN)*np.cos(omega)*np.cos(i)
        n1 = np.sin(omega)*np.sin(i)
        n2 = np.cos(omega)*np.sin(i)

        #magnitude of the angular momentum:
        H = np.sqrt(self.CentralBody.mu*a*(1-e**2))
        #magnitude of the position vector at a given moment:
        r = a*(1-e*np.cos(self.E))

        #Position vector
        x = l1*r*np.cos(theta) + l2*r*np.sin(theta)
        y = m1*r*np.cos(theta) + m2*r*np.sin(theta)
        z = n1*r*np.cos(theta) + n2*r*np.sin(theta)

        #Velocity vector
        xdot = self.CentralBody.mu/H * (-l1*np.sin(theta) + l2*(e+np.cos(theta)))
        ydot = self.CentralBody.mu/H * (-m1*np.sin(theta) + m2*(e+np.cos(theta)))
        zdot = self.CentralBody.mu/H * (-n1*np.sin(theta) + n2*(e+np.cos(theta)))

        return [x,y,z,xdot,ydot,zdot]

    def Propagation(self, t, typeParam, *args, **kwargs):
        """
        Propagation: propagate the mean anomaly M in time.
        INPUTS:
            t: time in seconds at which the position wants to be known with
                respect to the given state in the init
            typeParam: type fo the elements: "Keplerian", "Cartesian".
        OUTPUTS:
            M: new mean anomaly
        """
        self.M += self.n*t # Propagate in time
        self.M = AL_BF.correctAngle(self.M, 'rad') # Correct for more than 306deg

        if self.e < 1:
            self.theta, self.E, self.M = Kepler(self.M, self.e,'Mean')
        else:
            self.theta, self.E, self.M = Keplerh(self.M, self.e,'Mean')
        # self.theta = Kepler(self.M, self.e,'Mean')[0]

        x = np.array([self.a,self.e,self.i,self.RAAN,self.omega,self.M,self.theta])
        if typeParam == 'Keplerian':
            return x
        else:
            #Obtain the cartesian coordinates
            x = self.Kepl2Cart(givenElements = x)
            return x

    def atPeriapsis(self):
        """
        atPeriapsis: find the radius of the periapsis, velocity at periapsis and
        time since passage through periapsis
        """
        self.rp, self.vp = VisVivaEq(self.CentralBody, self.a, r = self.rp)
        self.tp = self.M / self.n # time since passage through periapsis

    # def solveKeplerUniversal(self, Xi, x_add):
    #     [t, alpha] = x_add
    #     self.atPeriapsis()
    #     r0 = self.rp
    #     vr0 = self.vp
    #     t0 = self.tp
    #     f = - np.sqrt(self.CentralBody.mu) * (t + t0) + \
    #         r0*vr0 / np.sqrt(self.CentralBody.mu) * Xi**2 * Stumpff_C(alpha* Xi**2) +\
    #         (1 - alpha* r0) * Xi**3 * Stumpff_S( alpha* Xi**2) +\
    #         r0 * Xi
    #     return f

    # def PropagationUniversal(self, t, typeParam, *args, **kwargs):
    #     """
    #     Propagation: propagate the mean anomaly M in time. Valid for ellipses,
    #     hyperbola, parabola
    #     Inputs:
    #         t: time in seconds at which the position wants to be known with
    #             respect to the given state in the init
    #     Outputs:
    #         M: new mean anomaly
    #     """
    #     # Find value of XI: universal anomaly
    #     alpha = 1/self.a
    #     Xi_0 = np.sqrt(self.CentralBody.mu) * abs(alpha) * t
    #     Xi = spy.fsolve(self.solveKeplerUniversal, Xi_0, args = [t, alpha, ])

    def PlotOrbit(self):
        """
        PlotOrbit: plot the relative position of the planets at a given date for
         each one
        INPUTS:
            Body: central Body
        OUTPUTS:
            xEl_E: x coordinate of the ellipse that represents the orbit
            yEl_E: y coordinate of the ellipse that represents the orbit
        """
        # Plot ellipse
        xEllipse = np.linspace(-self.a,self.a, num = 2500)
        yPos = np.sqrt(self.b**2*(1-xEllipse**2/self.a**2))
        yNeg = -np.sqrt(self.b**2*(1-xEllipse**2/self.a**2))
        xEl_E = np.append(xEllipse,xEllipse[::-1])
        xEl_E -= self.a*self.e*np.ones(len(xEl_E))
        yEl_E = np.append(yPos,yNeg)

    #     # Rotate ellipse
    #     ELL0=np.zeros((len(xEl_E0),3)) #Ellipse coordinates
    #     ELL0[:,0] = xEl_E0
    #     ELL0[:,1] = yEl_E0
    #     Q, nan,nan = transforMatrix(x,(x[4]-x[3])*np.pi/180,[0,0,0],[0,0,0])

    #     ELL = np.dot(ELL0,np.transpose(Q))
    #     xEl_E = ELL[:,0]
    #     yEl_E = ELL[:,1]

        fig, ax = plt.subplots(1,1, figsize = (9,8))
        # Plot orbit
        plt.plot(xEl_E,yEl_E,color = 'black',linestyle = '--') # Plot ellipse
        # Plot line from planet to the Sun:
        plt.plot([self.r[0],0],[self.r[1],0],color = 'black')
        plt.plot(self.r[0],self.r[1], marker = "o",markersize = 20, \
             color = 'black', alpha = 0.5,label = 'Position') # Plot planet

        plt.plot(0, 0, Markersize = 695508/15000, color= self.CentralBody.color,\
             marker="o")

        plt.title('2D Trajectory',size = 22)

        plt.axis("equal")
#         plt.xticks(np.arange(-3e11, 3e11, 1.0e11))
#         plt.yticks(np.arange(-3e11, 3e11, 1.0e11))
        plt.xlabel('(m)',size = 25)
        plt.ylabel('(m)',size = 25)

        plt.tick_params(axis ='both', which='major', labelsize = 15)
        plt.tick_params(axis ='both', which='minor', labelsize = 15)
        text = ax.xaxis.get_offset_text()
        text.set_size(15) #
        text = ax.yaxis.get_offset_text()
        text.set_size(15) #

#         lgnd = plt.legend(bbox_to_anchor=(0.5, 0), loc='lower left',ncol=2, \
#               mode="expand", borderaxespad=0.,fontsize=16)
#         lgnd.legendHandles[0]._legmarker.set_markersize(20)

    #     plt.savefig("_%i.png"%i,dpi=200,bbox_inches='tight')
        plt.show()

################################################################################
# STUMPFF FUNCTIONS
################################################################################
def Stumpff_C(z):
    """
    Stumpff_C: Stumpff function C for the propagation with universal variables
    INPUTS:
        z: Stumpff variable
    OUTPUTS:
        f: value of the function
    """
    if z > 0:
        f = ( 1 - np.cos(np.sqrt(z)) ) / z
    elif z == 0:
        f = 1/2
    else:
        f = ( np.cosh(np.sqrt(-z)) - 1 ) / (-z)
    return f

def Stumpff_S(z):
    """
    Stumpff_S: Stumpff function S for the propagation with universal variables
    INPUTS:
        z: Stumpff variable
    OUTPUTS:
        f: value of the function
    """
    if z > 0:
        f = ( np.sqrt(z) - np.sin(np.sqrt(z)) ) / z**(3/2)
    elif z == 0:
        f = 1/6
    else:
        f = ( np.sinh(np.sqrt(-z)) - np.sqrt(-z)  ) / (-z)**(3/2)
    return f

################################################################################
# PROPAGATE ALONG ORBIT FG PARAMS
################################################################################
def fgSolveE(E,x):
    """
    fgSolveE: solve Kepler's equation to obtain the Eccentric anomaly
    INPUTS:
        E: initial guess of the eccentric anomaly
        x: arguments to put in the equation
            x[0]: E0. Initial eccentric anomaly of the spacecraft
            x[1]: M0. Initial mean nomaly of the spacecraft
            x[2]: M. Current mean anomaly of the spacecraft
            x[3]: e. Eccentricity of the orbit
            x[4]: a. Semi-major axis of the propagation orbit
            x[5]: SV. State vector of the planet. [x,y,z,vx,vy,vz,m0,ind]
                x: position in the x axis
                y: position in the y axis
                z: position in the z ais
                vx : velocity in the x axis
                vy : velocity in the y axis
                vz : velocity in the z axis
                m0: mass at that point
                ind: indicator to know if there is an impulse
    OUTPUTS:
        f: function to solve
    """
    [E0,M0,M,e,a,SV,mu] = x
    r = np.linalg.norm(SV[0:3])

    sigma0 = np.dot(SV[0:3],SV[3:6])/np.sqrt(mu) # Value to put in next equation
    # Modified Kepler's equation:
    f = E-E0-(1-r/a)*np.sin(E-E0)+sigma0/np.sqrt(a)*(1-np.cos(E-E0))-(M-M0)
    # for the case in which we are not in perigee
    return f

def fgPropagate(orbit,SV,t):
    """
    fgPropagate: propagate the mean anomaly M in time.
    INPUTS:
        param: list of parameters. [a,e,E0,nu0,M0,n]
            E0: previous eccentric anomaly
            nu0: previous true anomaly
            M0: previous mean anomaly
            n: proper motion
        SV: state vector at the previous point [x,y,z,vx,vy,vz,m0,ind]
                x: position in the x axis
                y: position in the y axis
                z: position in the z ais
                vx : velocity in the x axis
                vy : velocity in the y axis
                vz : velocity in the z axis
        t: difference in time from the previous point
    OUTPUTS:
            E: new eccentric anomaly
            M: new mean anomaly
    """

    M = orbit.M + orbit.n * t
    E = spy.fsolve(fgSolveE, orbit.E, [orbit.E, orbit.M, M, orbit.e, orbit.a, \
        SV, orbit.Body.mu]) # Obtain new E
    E = float(E)

    return [E,M]

def fgSV(orbit,param,t):
    """
    fgSV: obtain new state vector from the previous one and the parameters of
    the orbit.
    INPUTS:
        SV0: state vector at the previous point [x,y,z,vx,vy,vz,m,ind]
            x: position in the x axis
            y: position in the y axis
            z: position in the z ais
            vx: velocity in the x axis
            vy: velocity in the y axis
            vz: velocity in the z axis
            m: mass at that point
            ind: indicator to know if there is an impulse
        param: parameters of the point of the propagation
        paramF: parametrs of the next point of the propagation
            a: semi-major axis of the propagation orbit
            e: eccentricity of the propagation orbit
            E: new eccentric anomaly
            nu: new true anomaly
            M: new mean anomaly
            n: proper motion
        t: difference in time from the previous time
    OUTPUTS:
        SV: new state vector
    """
    E0 = orbit.E
    E = param[0]

    r0 = np.linalg.norm(orbit.r)

    # f and g coefficients
    sigma0 = np.dot(orbit.r,orbit.v)/np.sqrt(orbit.Body.mu)
    f = 1 - orbit.a / r0*(1-np.cos(E-E0))
    g = (orbit.a * sigma0 * (1-np.cos(E-E0)) + r0 * np.sqrt(orbit.a) * \
        np.sin(E-E0)) / np.sqrt(orbit.Body.mu)

    #New position
    SV = np.zeros(6)
    SV[0:3] = f*orbit.r + g*orbit.v
    r = np.linalg.norm(SV[0:3])

    #f',g'
    ff = -np.sqrt(orbit.Body.mu * orbit.a) / (r*r0)*(np.sin(E-E0))
    gg = 1 - orbit.a / r *(1-np.cos(E-E0))

    #New velocity
    SV[3:6] = ff*orbit.r + gg*orbit.v

    return SV

################################################################################
# MORE
################################################################################
def VisVivaEq(Body,a,*args,**kwargs):
    """
    VisVivaEq: obtain position from velocity or vice versa with the vis viva or
    energy equation.
    INPUTS:
        Body: body around which the orbit is performed
        a: semi-major axis of the orbit
        **kwargs:
            r: magnitude of the position vector
            v: magnitude of the velocity vector
    OUTPUTS:
        r: magnitude of the position vector
        v: magnitude of the velocity vector
    """
    r = kwargs.get('r', None)
    v = kwargs.get('v', None)

    if r == None and v != None: #v is given
        r = Body.mu / (Body.mu/(2*a)+v**2/2)
    elif  r != None and v == None: #r is given
        v = 2*np.sqrt(-Body.mu/(2*a) + Body.mu/r)
    else:
        print('Warning: Not enough arguments or too many arguments given')
    return r,v

def KeplerSolve(E0, x):
    """
    Kepler: function to solve Kepler's equation knowing the mean anomaly. To
    obtain the eccentric anomaly.
    INPUTS:
        E0: initial guess of the solution
        x: vector containing other parameters of the equation:
            x[0]: Mean anomaly (rad)
            x[1]: Eccentricity
    OUTPUTS:
        F: function to solve (rad)
    """
    #Parametrs of the equation
    M = x[0]
    e = x[1]

    #Equation to solve
    F = E0-e*np.sin(E0)-M
    F = AL_BF.correctAngle(F, 'rad')

    return F

def Kepler(angle,e,name):
    """
    Kepler: function to calculate true, eccentric and mean anomaly given one of
    them and the eccentricity of the orbit.
    INPUTS:
        angle: initial angle known. It can be any of the three mentioned
        e: eccentricity of the orbit (magnitude)
        name: specify which the initial angle given is:
            'Mean': mean anomaly
            'True': true anomaly
            'Eccentric': eccentric anomaly
    OUTPUTS:
        theta: true anomaly
        E: eccentric anomaly
        M: mean anomaly
    """
    if name == 'Mean':
        M = angle
        E = spy.fsolve(KeplerSolve,M,[M,e]) #Solve Kepler's equation
        E = E[0]
        theta = 2*np.arctan(np.sqrt((1+e)/(1-e))*np.tan(E/2)) #Calculate true anomaly
#         if abs(theta) >= 2*np.pi: #Correction in case is higher than 360
#             a = theta//(2*np.pi)
#             theta = theta-2*np.pi*a
        return(theta,E,M)

    elif name == 'Eccentric':
        E = angle
        # Calculate true anomaly
        theta = 2*np.arctan(np.sqrt((1+e)/(1-e))*np.tan(E/2))
        M = E-e*np.sin(E) #Kepler's equation
        return(theta,E,M)

    elif name == 'True':
        theta = angle
        # Calculate eccentric anomaly:
        E = 2*np.arctan(np.sqrt((1-e)/(1+e))*np.tan(theta/2))
        M = E-e*np.sin(E) #Kepler's equation
        return(theta,E,M)

    else:
        print('Warning: Include a valid name for the angle given')

def KeplerhSolve(E0, x):
    """
    Kepler: function to solve Kepler's equation knowing the mean anomaly. To
    obtain the eccentric anomaly.
    INPUTS:
        E0: initial guess of the solution
        x: vector containing other parameters of the equation:
            x[0]: Mean anomaly (rad)
            x[1]: Eccentricity
    OUTPUTS:
        F: function to solve (rad)
    """
    #Parametrs of the equation
    M = x[0]
    e = x[1]

    #Equation to solve
    F = e*np.sinh(E0) - E0- M
    F = AL_BF.correctAngle(F, 'rad')
    return F

def Keplerh(angle, e, name):
    """
    Keplerh: function to calculate true, eccentric and mean anomaly given one of
    them and the eccentricity of the orbit. For an orbit of e >= 1
    INPUTS:
        angle: initial angle known. It can be any of the three mentioned
        e: eccentricity of the orbit (magnitude)
        name: specify which the initial angle given is:
            'Mean': mean anomaly
            'True': true anomaly
            'Eccentric': eccentric anomaly
    OUTPUTS:
        theta: true anomaly
        E: eccentric anomaly
        M: mean anomaly
    """
    print("Hyperbola")

    if name == 'Mean':
        M = angle
        E = spy.fsolve(KeplerhSolve, M, [M,e]) #Solve Kepler's equation
        E = E[0]
        theta = 2*np.arctan( np.sqrt( (e+1) / (e-1) ) * np.tan(E/2) )
        return(theta, E, M)

    elif name == 'Eccentric':
        E = angle
        # Calculate true anomaly:
        theta = 2 * np.arctan( np.sqrt( (e+1) / (e-1) ) * np.tan(E/2) )
        M = e * np.sinh(E) - E #Kepler's equation
        return(theta, E, M)

    elif name == 'True':
        theta = angle
        # Calculate eccentric anomaly:
        E = 2 * np.arctanh( np.sqrt( (e-1) / (e+1) ) * np.tan(theta/2) )
        M = e * np.sinh(E) - E #Kepler's equation
        return(theta, E, M)

    else:
        print('Warning: Include a valid name for the angle given')


#
#
# Font Lato https://fonts.google.com/specimen/Lato
# https://opengameart.org/content/4-chiptunes-adventure
#
# scanlines less mem
## https://www.reddit.com/r/pygame/comments/6yk6zk/least_memory_intensive_way_to_implement_scanlines/
# Not used:
# Audio Voices by MadamVicious (https://freesound.org/people/MadamVicious/sounds/238641)
#
# @todo turn this into "kawaii clock" game for github?
# @todo refactor 'em all ...
# @todo spritesheet
# @todo create the wordle game -> no words, winning/losing
# @todo scope of the game -> intro/menu/options/languages?
#
#
#   OMG refactor me
#   Particles List-> got better :)
#   ✔   Lock -> Class
#   Typing sounds?
#   easingFunc
#   naming conventions
#   moving in update, for exp. "scrollX"
#   ✔   handleInput
#   ✔   fix assets colors -> indicator
#   ✔   Window Icon
#   ✔   Delete/Replace Letters
#   init methode
#   ✔   gameOver -> Transition back to the startscreen
#   check "glitch" performance on raspi
#   intro with "fake" loading screen or fade in effect for the main menu
#   compare diff screens to find design errors - like missing shadows
#   create a short docu
#       - show the "drawn" hierarchy
#   clean up the assets and upload them
#   escape key back to menu and on the second press exit the game
#   turn debug on/off -> log printing
#   create /utils/ folder for texture atlas and effect editor
#   juicify the effects/animations -> intro? "special effects" on the first start?
#
#
#   'BUGS'
#   ✔   word to short -> index out of bounds
#   ✔   Lock on the title screen is not bouncing after winning
#   ✔   overlapping background
#   multiple letters are displayed wrong -> "lllll" four orange and one green -> correct -> one green
#
#
#   Unclear:
#   Easings -why "wrong" values
#
#
#
#
import itertools
import random

import pygame
from easing_functions import easing

from background import Background
from gamestate_manager import GamestateManager
from letter import Letter
from lock import Lock
from part_confetti import create_confetti
from particle import Particle
from phrase import Phrase
from screen_game import ScreenGame
from screen_menu import ScreenMenu
from screen_over import ScreenOver
from screen_transition import ScreenTransitions
from settings import Settings
from sky import Sky
from scanline import Scanline
from glitch import Glitch
from dataclasses import dataclass

@dataclass
class WordleGame:
    # init
    # Setup Pygame / Display
    pygame.init()
    manager = GamestateManager()
    settings = Settings()
    pygame.display.set_caption(settings.str_title)

    particles_new = []
    particles_confetti = []

    # surfaceOrg = pygame.display.set_mode((927, 750), pygame.HWSURFACE)
    surfaceOrg = pygame.display.set_mode((settings.screen_width, settings.screen_height), pygame.HWSURFACE)
    lock = Lock()
    pygame_icon = pygame.image.load('tex/icon.png')
    pygame.display.set_icon(pygame_icon)

    # surfaceOrg = pygame.display.set_mode((0,0), pygame.FULLSCREEN)
    surfaceOrg.fill(color=(255, 66, 112))
    surface = surfaceOrg.copy()
    surface.fill(color=(255, 66, 112))

    TexBg = pygame.image.load('tex/tiles/bg_fields.png').convert_alpha()
    TexBgSky = pygame.image.load('tex/bg_sky.png').convert_alpha()
    TexBgScroll = pygame.image.load('tex/bg2.png').convert_alpha()
    TexLock2 = pygame.image.load('tex/lock/lock2.png').convert_alpha()
    TexLockShine = pygame.image.load('tex/tiles/lock_shine.png').convert_alpha()
    TexLockShine2 = pygame.image.load('tex/tiles/lock_shine_2.png').convert_alpha()
    TexScanline = pygame.image.load('tex/scanline.png').convert_alpha()
    TexScanline2 = pygame.image.load('tex/scanline2.png').convert_alpha()
    TexScanlineOverlaySkull = pygame.image.load('tex/scanline_overlay_skull.png').convert_alpha()
    TexStartscreen = pygame.image.load('tex/startscreen.png').convert_alpha()
    TexStartscreenLock = pygame.image.load('tex/lock/startscreen_lock.png').convert_alpha()

    TexWordTileYellow = pygame.image.load('tex/tiles/tile_yellow.png').convert_alpha()
    TexWordTileGreen = pygame.image.load('tex/tiles/tile_green.png').convert_alpha()
    TexWordTileGrey = pygame.image.load('tex/tiles/tile_grey.png').convert_alpha()
    TexWordTileGrey2 = pygame.image.load('tex/tiles/tile_grey2.png').convert_alpha()
    TexWordTileDefault = pygame.image.load('tex/tiles/tile_default.png').convert_alpha()

    TexTileIndicatorGreen = pygame.image.load('tex/tiles/indicator_green.png').convert_alpha()
    TexTileIndicatorGrey = pygame.image.load('tex/tiles/indicator_grey.png').convert_alpha()
    TexTileIndicatorYellow = pygame.image.load('tex/tiles/indicator_yellow.png').convert_alpha()
    TexTileIndicatorDefault = pygame.image.load('tex/tiles/indicator_default.png').convert_alpha()

    TexTileFlash1 = pygame.image.load('tex/tiles/tile_flash_1.png').convert_alpha()

    background = Background(TexBgScroll)
    skies = Sky(TexBgSky)
    phrase = Phrase(settings.str_title_waving)
    screen_menu = ScreenMenu(TexStartscreen, TexStartscreenLock)
    # screen_game = ScreenGame()
    screen_over = ScreenOver()
    screen_transition = ScreenTransitions()
    dt=0

    # SOUNDS
    sound_enabled = False
    if sound_enabled:
        snd_hit_1 = pygame.mixer.Sound('../snd/1.mp3')
        snd_hit_2 = pygame.mixer.Sound('../snd/2.mp3')
        snd_hit_3 = pygame.mixer.Sound('../snd/3.mp3')
        snd_hit_4 = pygame.mixer.Sound('../snd/4.mp3')
        snd_hit_5 = pygame.mixer.Sound('../snd/5.mp3')
        hit_sounds = [snd_hit_1, snd_hit_2, snd_hit_3, snd_hit_4, snd_hit_5]

    def gen_smoke(self, x, y):
        self.particles_new.extend(Particle.generate_smoke_list_white(
            x, y, 60, color="#fafafa"))

    def show_hit_screen(self):
        self.settings.offsetShake = self.shake()

        self.settings.showScanlines = not self.settings.showScanlines
        self.settings.scanlineTimer = 10
        self.settings.scanlineEffectTimer = 450
        self.settings.glitch_enabled = True

    def shoot_confetti(self):
        self.particles_confetti.extend(create_confetti())

    def show_lose_screen(self):
        print("show lose")
        self.settings.init_over_screen()
        self.screen_over = ScreenOver(4)
        self.manager.set_gamestate(4)

    def show_win_screen(self):
        self.settings.hero_replacement_showIntro = True
        self.lock.bar_popup = True
        self.settings.won = True
        self.settings.win_timer = 1900

        # confetti
        # shoot_confetti()

        # self.settings.show_blend_screen = True
        if self.lock.bar_popup:
            self.lock.eyes_flash_timer = 150

    #
    # Seperate the gameLogic into its own class
    #
    def checkGuess(self, current_guess):
        print(f"guesses_count: {self.settings.guesses_count}")
        print(f"currentPosition: {self.settings.currentPosition}")
        tmpLen = len(self.settings.guesses)
        print(f"tmpLen: {tmpLen}")
        self.settings.checkedGuess = True
        GOAL = self.settings.goal_word  # useless variable :)

        if self.sound_enabled:
            self.snd_hit_2.play()
        check_won = True
        for i in range(5):
            # lock the letter
            current_guess[i].locked = True

            # gen smoke
            self.gen_smoke(current_guess[i].x, current_guess[i].y)

            lower = current_guess[i].key
            if current_guess[i].key in GOAL:
                if lower == GOAL[i]:
                    current_guess[i].flash = i * -1
                    current_guess[i].flash_timer = 0
                    current_guess[i].tile = self.TexWordTileGreen
                    self.settings.indicator[lower] = self.TexTileIndicatorGreen
                else:
                    current_guess[i].tile = self.TexWordTileYellow
                    self.settings.indicator[lower] = self.TexTileIndicatorYellow
                    check_won = False
            else:
                current_guess[i].fontcolor = "#ebe8da"
                current_guess[i].tile = self.TexWordTileGrey
                self.settings.indicator[lower] = self.TexTileIndicatorGrey
                check_won = False

        if check_won:
            self.show_win_screen()
        else:
            if self.settings.guesses_count >= 3:
                self.show_lose_screen()
            self.show_hit_screen()

        self.settings.glitch_flip = not self.settings.glitch_flip
        self.settings.currentPosition = 0
        self.settings.guesses_count += 1

    # LINE += 1

    # https://stackoverflow.com/questions/23633339/pygame-shaking-window-when-loosing-lifes
    # slightly modified
    #
    def shake(self, hori=False, times=5, power=10):
        s = -1
        for _ in range(0, times):
            for x in range(power, 0, 2):
                if hori:
                    yield x * s, x * s
                else:
                    yield 0, x * s
            for x in range(0, power, 2):
                if hori:
                    yield x * s, x * s
                else:
                    yield 0, x * s
            s *= -1
        while True:
            yield 0, 0

    def change_indicators(self, guess):
        self.settings.usedLetters.append(guess)

    def init_indicators(self, settings):
        for i in range(3):
            for let in settings.alphabet[i]:
                settings.indicator.update({let: self.TexTileIndicatorDefault})

    scanline = Scanline(settings.screen_width, settings.screen_height)
    glitch = Glitch(settings.screen_width, settings.screen_height, TexScanlineOverlaySkull)

    def add_new_letter(self, key_pressed):
        x = 465 + (self.settings.currentPosition * 67)
        y = 47 + (self.settings.guesses_count * 79)
        letter = Letter(key_pressed, (x, y), self.TexWordTileDefault)
        self.settings.guesses[self.settings.guesses_count].append(letter)
        self.settings.currentPosition += 1

    def add_new_letter_effects(self):
        self.settings.offsetShake = self.shake()
        self.lock.eyes_blink_timer = 60
        self.lock.flash = True
        self.lock.eyes_flash_timer = 20

        x = 455 + (self.settings.currentPosition * 67)
        y = 100 + (self.settings.guesses_count * 80)
        self.particles_new.extend(Particle.generate_smoke_list(
            x, y, 80,
        ))

    def handle_input(self, events):
        for event in events:
            if event.type == pygame.KEYDOWN:
                if self.manager.gamestate == 1:
                    if self.sound_enabled:
                        # snd_typing.play()
                        pass
                    key_pressed = event.unicode.upper()
                    if key_pressed in self.settings.alphabet_sorted:
                        if len(self.settings.guesses[self.settings.guesses_count]) <= 4:
                            self.add_new_letter(key_pressed)
                            self.add_new_letter_effects()

                    elif event.key == pygame.K_BACKSPACE:
                        print("Please dont crash")
                        if len(self.settings.guesses[self.settings.guesses_count]) >= 1:
                            print(len(self.settings.guesses[self.settings.guesses_count]))
                            self.settings.guesses[self.settings.guesses_count].pop()
                            self.settings.currentPosition -= 1
                    elif event.key == pygame.K_RETURN:
                        self.settings.offsetShake = self.shake(power=8, times=5)
                        if len(self.settings.guesses[self.settings.guesses_count]) == 5:
                            print("over 5!")
                        if len(self.settings.guesses[self.settings.guesses_count]) == 5:
                            self.checkGuess(self.settings.guesses[self.settings.guesses_count])
                    elif event.key == pygame.K_ESCAPE:
                        # running = False
                        self.manager.set_gamestate(0)
                        self.screen_menu = ScreenMenu(self.TexStartscreen, self.TexStartscreenLock)
                else:
                    if event.key == pygame.K_SPACE:
                        print("Space")
                        # shoot_confetti()

                        self.manager.set_gamestate(2)
                        screen_game = ScreenGame()
                        # if sound_enabled:
                        #    pygame.mixer.music.load('music/stage.mp3')
                        #    pygame.mixer.music.play(-1, 0.0)

                    elif event.key == pygame.K_ESCAPE:
                        self.settings.game_is_running = False
                        # gamestate_mgr.gamestate = 0

            if event.type == pygame.QUIT:
                self.settings.game_is_running = False

            if event.type == pygame.MOUSEBUTTONDOWN:
                x, y = event.pos
                print(f"Mouse   x: {x}  y:{y}")

    def draw_letters(self):
        for guess in self.settings.guesses:
            for letter in guess:
                letter.update(self.dt, self.settings.easing_frame)
                letter.draw(self.surface, self.settings, self.TexTileFlash1)

    def draw_indicators(self):  # own class !
        y_offset = 0
        if self.settings.hero_replacement_showIntro:
            self.settings.hero_replacement_y_offset_indicator += self.dt * 2
            y_offset = self.settings.hero_replacement_y_offset_indicator

        offset = 0

        self.settings.indication_easing_amount = self.settings.easingFunction(self.settings.easing_frame * 0.01)
        self.settings.indication_easing_amount = min(1, self.settings.indication_easing_amount)
        self.settings.indication_y_bounce = (
                                                    self.settings.indication_easing_end_pos - self.settings.indication_easing_start_pos) * self.settings.indication_easing_amount

        for i in range(3):
            spacer = 0

            y_offset += 5
            if i == 1:
                offset = 23
            elif i == 2:
                offset = 73
            for letter in self.settings.alphabet[i]:
                x = 222 + offset + (spacer * 49)
                y = 1000 + y_offset + (i * 50)  # + 545
                spacer += 1
                tempRect = (x, y + self.settings.indication_y_bounce, 42, 42)

                if not self.settings.indicator[letter] == self.TexTileIndicatorDefault:
                    text_surface = self.settings.font_small.render(letter, True, "#ebe8da")
                else:
                    text_surface = self.settings.font_small.render(letter, True, "#304059")
                textRect = text_surface.get_rect(center=(x + 21, self.settings.indication_y_bounce + y + 18))

                self.surface.blit(self.settings.indicator[letter], tempRect)
                self.surface.blit(text_surface, textRect)

    def update_particles(self, dt):
        for part in itertools.chain(self.particles_new, self.particles_confetti):
            if part.isActive:
                part.update(dt)

        self.remove_particles()

    # how to refactor me? itertools chain?
    def remove_particles(self):
        for part in self.particles_new:
            if not part.isActive:
                self.particles_new.remove(part)

        for part in self.particles_confetti:
            if not part.isActive:
                self.particles_confetti.remove(part)

    # move me into screen_game
    def update_scanlines(self, dt):
        if self.settings.showScanlines:
            self.settings.scanlineTimer -= dt / 2
            self.settings.scanlineEffectTimer -= dt / 2
            if self.settings.scanlineEffectTimer < 0:
                self.settings.showScanlines = False
                self.settings.glitch_enabled = False
            if self.settings.scanlineTimer < 0:
                self.settings.scanlinesFlash = not self.settings.scanlinesFlash
                self.settings.scanlineTimer = 10

    def update_movement(self, dt):

        self.background.update(dt)
        self.skies.update(dt)
        self.update_particles(dt)
        self.update_scanlines(dt)
        self.screen_transition.update(dt, self.settings, self.manager.gamestate)
        self.screen_menu.update(dt, self.manager.gamestate)

        if self.settings.won:  # rename me -> wait for screen_to_end ?
            self.settings.win_timer -= dt
            self.settings.show_blend_screen = True
            if self.screen_transition.radius > 1050:
                self.settings.won = False
                # reset 'em all
                self.lock = Lock()
                self.phrase.x_pos = 150
                self.screen_menu = ScreenMenu(self.TexStartscreen, self.TexStartscreenLock)
                self.manager.set_gamestate(3)
                self.screen_over = ScreenOver(3)
                self.particles_confetti.extend(create_confetti())

        if self.settings.currentPosition <= 4:
            self.lock.update_lock_shine_pos(self.settings.currentPosition * 67,
                                            self.settings.guesses_count * 80)

    def draw_background(self):
        self.background.draw(self.surface)
        self.skies.draw(self.surface)
        self.surface.blit(self.scanline.surface, (0, 0))

    def draw_gamefield(self):
        self.surface.blit(self.TexBg, (self.settings.gamefield_pos_x, 0))

    def draw_particles(self):
        for part in itertools.chain(self.particles_new, self.particles_confetti):
            if part.isActive:
                part.draw(self.surface)

    def draw_scanlines(self):
        if self.settings.showScanlines:
            if self.settings.scanlinesFlash:
                self.surface.blit(self.TexScanline, (0, 0))
            else:
                self.surface.blit(self.TexScanline2, (0, 0))

    def update_easing_game_screen(self):
        # mixture of lock & gamefield easing :(
        easing_amount = self.settings.easingFunction(self.settings.easing_frame * 0.01)
        easing_amount = min(1, easing_amount)
        self.settings.easing_frame += 1

        # end - start pos
        gamefield_easing_x = (930 - 0) * easing_amount
        self.settings.gamefield_pos_x = 930 - gamefield_easing_x

    def show_highlight_current_field(self):
        if self.settings.currentPosition <= 4:
            if self.lock.shineTime and self.settings.gamefield_pos_x == 0:
                self.surface.blit(self.TexLockShine, (self.lock.TexLockShine_x, self.lock.TexLockShine_y))

    def load_sounds(self):
        if self.sound_enabled:
            snd_hit_1 = pygame.mixer.Sound('sounds/hit_1.mp3')
            snd_hit_2 = pygame.mixer.Sound('sounds/hit_2.mp3')
            snd_typing = pygame.mixer.Sound('sounds/typing.mp3')

            pygame.mixer.music.load('music/menu.mp3')
            pygame.mixer.music.play(-1, 0.0)

    def draw_screen_game(self):
        self.draw_gamefield()
        # confetti behind the hero banner
        for con in self.particles_confetti:
            con.draw(self.surface)
        self.lock.draw(self.surface, self.lock.bar_popup_offset)

        self.draw_indicators()  # optimize me
        self.draw_letters()

    def draw_screen_win_lose(self, dt):
        self.screen_over.update(dt)
        self.settings.flip_update(dt)
        self.screen_over.draw(self.surface, self.settings.blink, self.settings.flip)
        if self.manager.gamestate == 4:
            # don't like the way the offsetting is handled
            self.surface.blit(self.settings.lose_surface, (540, 554 + (self.screen_over.y * -1)))

        for con in self.particles_confetti:
            con.draw(self.surface)

    def create_new_game(self):
        self.init_indicators(self.settings)

        self.manager.new_game(self.settings)

    # refactor me!
    def reset_game(self):
        # called when ever menu is loaded, resets all objects

        self.manager.set_gamestate(0)

        self.lock = Lock()
        self.settings.hero_replacement_showIntro = False
        self.settings.hero_replacement_y_offset_indicator = 0
        self.settings.gamefield_pos_x = 930  # gamefield reset to spawn outside of the viewport
        self.settings.easing_frame = 0

        self.screen_menu = ScreenMenu(self.TexStartscreen, self.TexStartscreenLock)

    def start_game(self):
        self.init_indicators(self.settings)

        print("starting game")
        while self.settings.game_is_running:
            dt = self.settings.clock.tick(60)
            # print(clock.get_fps())

            # START UPDATE
            self.update_movement(dt)

            if self.manager.gamestate == 1:
                self.update_easing_game_screen()
                self.lock.update(dt)

            elif self.manager.gamestate == 2:
                if self.screen_menu.startscreen_y_lock < -1000:
                    self.create_new_game()

            # START DRAWING
            self.draw_background()

            if self.manager.gamestate == 1:
                self.draw_screen_game()
                self.show_highlight_current_field()
            elif self.manager.gamestate == 3 or self.manager.gamestate == 4:
                self.draw_screen_win_lose(dt)

                if not self.screen_over.change_to_next_screen:
                    self.reset_game()

            else:
                # if gamestate is menu or transition
                self.screen_menu.draw(self.surface)
                self.phrase.draw(self.surface, self.screen_menu.startscreen_y, self.settings)

            self.draw_particles()
            self.draw_scanlines()

            self.surfaceOrg.blit(self.surface, next(self.settings.offsetShake))

            self.glitch.draw(self.surfaceOrg, self.settings.glitch_enabled, self.settings.glitch_flip)

            self.screen_transition.draw(self.surfaceOrg, self.settings)

            pygame.display.update()
            self.handle_input(pygame.event.get())

        pygame.quit()
        print("DONE")

# easy todo
# the colors - correct names -> put them in inkscape on the color scheme

# next try
# global are killing it
if __name__ == '__main__':
    wordle = WordleGame()
    wordle.start_game()


"""Calculate the expected detection rates for apertif."""
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
from frbpoppy import CosmicPopulation, Survey, SurveyPopulation, hist

from tests.convenience import plot_aa_style, rel_path
from alpha_real import EXPECTED, poisson_interval

N_DAYS = 1  # Not used in eventual result
SCALE_TO = 'parkes-htru'

pop = CosmicPopulation.complex(n_srcs=1e5, n_days=N_DAYS)
pop.generate()

apertif = Survey('wsrt-apertif', n_days=N_DAYS)
apertif.set_beam(model='apertif_real')

if SCALE_TO == 'parkes-htru':
    htru = Survey('parkes-htru', n_days=N_DAYS)
    htru.set_beam(model='parkes')
if SCALE_TO == 'askap':
    askap = Survey('askap-fly', n_days=N_DAYS)
    askap.set_beam(model='gaussian', n_sidelobes=0.5)

days_per_frbs = []
for i in tqdm(range(2000), desc='Survey Run'):

    apertif_pop = SurveyPopulation(pop, apertif, mute=True)

    if SCALE_TO == 'parkes-htru':
        htru_pop = SurveyPopulation(pop, htru, mute=True)
        n_frbs_htru = EXPECTED['parkes-htru'][0]
        n_days_htru = EXPECTED['parkes-htru'][1]
        scaled_n_days = n_days_htru*(htru_pop.source_rate.det / n_frbs_htru)

    if SCALE_TO == 'askap':
        askap_pop = SurveyPopulation(pop, askap, mute=True)
        n_frbs_askap = EXPECTED['askap-fly'][0]
        n_days_askap = EXPECTED['askap-fly'][1]
        scaled_n_days = n_days_askap*(askap_pop.source_rate.det / n_frbs_askap)

    days_per_frb = scaled_n_days / apertif_pop.source_rate.det
    # print(f'{days_per_frb} days per frb')
    days_per_frbs.append(days_per_frb)

days_per_frbs = np.array(days_per_frbs)
mean = np.mean(days_per_frbs)
std = np.std(days_per_frbs)
poisson_std = poisson_interval(mean)
print(f'Mean rate for apertif is {mean}')
print(f'Standard deviation of {std}')

# Plot
rates, values = hist(days_per_frbs, bin_type='lin')
plot_aa_style()
plt.step(rates, values, where='mid')
plt.xlabel(f'Apertif days per burst scaled to {SCALE_TO}')
plt.savefig(rel_path('./plots/rate_apertif_dist.pdf'))


"""Utility script to be used to cleanup the notebooks before git commit

This a mix from @minrk's various gists.

"""

import time
import sys
import os
import io
try:
    from queue import Empty
except:
    # Python 2 backward compat
    from Queue import Empty

try:
    from ipyparallel import Client
except ImportError:
    # Backward compat prior to IPython / Jupyter split
    from IPython.parallel import Client
try:
    import nbformat
except ImportError:
    # Backward compat prior to IPython / Jupyter split
    from IPython import nbformat
try:
    from jupyter_client.manager import KernelManager
except ImportError:
    # Backward compat prior to IPython / Jupyter split
    from IPython.kernel import KernelManager

def remove_outputs(nb):
    """Remove the outputs from a notebook"""
    if hasattr(nb, 'worksheets'):
        # nobody uses more than 1 worksheet
        ws = nb.worksheets[0]
    else:
        # no more worksheet level in new format
        ws = nb
    for cell in ws.cells:
        if cell.cell_type == 'code':
            cell.outputs = []
            if 'prompt_number' in cell:
                del cell['prompt_number']
            if 'execution_count' in cell:
                # notebook format v4
                cell['execution_count'] = None

def remove_signature(nb):
    """Remove the signature from a notebook"""
    if 'signature' in nb.metadata:
        del nb.metadata['signature']

def run_cell(kc, cell, timeout=300):
    if not hasattr(cell, 'input'):
        return [], False
    kc.execute(cell.input)
    # wait for finish, maximum 5min by default
    reply = kc.get_shell_msg(timeout=timeout)['content']
    if reply['status'] == 'error':
        failed = True
        print("\nFAILURE:")
        print(cell.input)
        print('-----')
        print("raised:")
        print('\n'.join(reply['traceback']))
    else:
        failed = False

    # Collect the outputs of the cell execution
    outs = []
    while True:
        try:
            msg = kc.get_iopub_msg(timeout=0.2)
        except Empty:
            break
        msg_type = msg['msg_type']
        if msg_type in ('status', 'pyin', 'execute_input'):
            continue
        elif msg_type == 'clear_output':
            outs = []
            continue

        content = msg['content']

        # IPython 3 writes pyerr/pyout in the notebook format but uses
        # error/execute_result in the message spec. This does the translation
        # needed for tests to pass with IPython 3
        notebook3_format_conversions = {
            'error': 'pyerr',
            'execute_result': 'pyout'
        }
        msg_type = notebook3_format_conversions.get(msg_type, msg_type)
        out = nbformat.NotebookNode(output_type=msg_type)

        if 'execution_count' in content:
            cell['prompt_number'] = content['execution_count']
            out.prompt_number = content['execution_count']

        if msg_type == 'stream':
            out.stream = content['name']
            # in msgspec 5, this is name, text
            # in msgspec 4, this is name, data
            if 'text' in content:
                out.text = content['text']
            else:
                out.text = content['data']
        elif msg_type in ('display_data', 'pyout'):
            for mime, data in content['data'].items():
                attr = mime.split('/')[-1].lower()
                # this gets most right, but fix svg+html, plain
                attr = attr.replace('+xml', '').replace('plain', 'text')
                setattr(out, attr, data)
        elif msg_type == 'pyerr':
            out.ename = content['ename']
            out.evalue = content['evalue']
            out.traceback = content['traceback']
        else:
            print("unhandled iopub msg: %s" % msg_type)

        outs.append(out)

    # Special handling of ipcluster restarts
    if '!ipcluster stop' in cell.input:
        # wait some time for cluster commands to complete
        for i in range(10):
            try:
                if len(Client()) == 0:
                    break
            except OSError:
                pass
            sys.stdout.write("@")
            sys.stdout.flush()
            time.sleep(5)
    if '!ipcluster start' in cell.input:
        # wait some time for cluster commands to complete
        for i in range(10):
            try:
                if len(Client()) > 0:
                    break
            except OSError:
                pass
            sys.stdout.write("#")
            sys.stdout.flush()
            time.sleep(5)
    return outs, failed

def run_notebook(nb):
    km = KernelManager()
    km.start_kernel(stderr=open(os.devnull, 'w'))

    kc = km.client()
    kc.start_channels()
    try:
        kc.wait_for_ready()
    except AttributeError:
        # IPython < 3
        kc.kernel_info()
        while True:
            msg = kc.get_shell_msg(block=True, timeout=30)
            if msg['msg_type'] == 'kernel_info_reply':
                break

        # Flush IOPub channel
        while True:
            try:
                msg = kc.get_iopub_msg(block=True, timeout=0.2)
            except Empty:
                break

    # simple ping:
    kc.execute("pass")
    kc.get_shell_msg()

    cells = 0
    failures = 0
    if hasattr(nb, 'worksheets'):
        # nobody uses more than 1 worksheet
        ws = nb.worksheets[0]
    else:
        # no more worksheet level in new format
        ws = nb

    for cell in ws.cells:
        if cell.cell_type != 'code':
            continue

        outputs, failed = run_cell(kc, cell)
        cell.outputs = outputs
        cell['prompt_number'] = cells
        failures += failed
        cells += 1
        sys.stdout.write('.')
        sys.stdout.flush()

    print()
    print("ran %3i cells" % cells)
    if failures:
        print("    %3i cells raised exceptions" % failures)
    kc.stop_channels()
    km.shutdown_kernel()
    del km

def process_notebook_file(fname, action='clean', output_fname=None):
    print("Performing '{}' on: {}".format(action, fname))
    orig_wd = os.getcwd()
    # XXX: Ugly hack to preserve backward compat for now
    cmd = ('ipython nbconvert --to notebook --nbformat 3'
           ' --output="%s" "%s"') % (fname, fname)
    os.system(cmd)
    with io.open(fname, 'r') as f:
        nb = nbformat.read(f, nbformat.NO_CONVERT)

    if action == 'check':
        os.chdir(os.path.dirname(fname))
        run_notebook(nb)
        remove_outputs(nb)
        remove_signature(nb)
    elif action == 'render':
        os.chdir(os.path.dirname(fname))
        run_notebook(nb)
    else:
        # Clean by default
        remove_outputs(nb)
        remove_signature(nb)

    os.chdir(orig_wd)
    if output_fname is None:
        output_fname = fname
    with io.open(output_fname, 'w') as f:
        nb = nbformat.write(nb, f, nbformat.NO_CONVERT)

if __name__ == '__main__':
    # TODO: use argparse instead
    args = sys.argv[1:]
    targets = [t for t in args if not t.startswith('--')]
    action = 'check' if '--check' in args else 'clean'
    action = 'render' if '--render' in args else action

    rendered_folder = os.path.join(os.path.dirname(__file__),
                                   'rendered_notebooks')
    if not os.path.exists(rendered_folder):
        os.makedirs(rendered_folder)
    if not targets:
        targets = [os.path.join(os.path.dirname(__file__), 'notebooks')]

    for target in targets:
        if os.path.isdir(target):
            fnames = [os.path.abspath(os.path.join(target, f))
                      for f in sorted(os.listdir(target))
                      if f.endswith('.ipynb')]
        else:
            fnames = [target]
        for fname in fnames:
            if action == 'render':
                output_fname = os.path.join(rendered_folder,
                                            os.path.basename(fname))
            else:
                output_fname = fname
            process_notebook_file(fname, action=action,
                                  output_fname=output_fname)


import numpy as np
import torch
import torch.nn.functional as F
import torch.nn as nn
import os
import cv2 as cv

def rgb2gray(rgb):
    '''
    Transforms frame into a grayscale

    Args:
        rgb (numpy array) : numpy array grayscaled
    '''

    r, g, b = rgb[:,:,0], rgb[:,:,1], rgb[:,:,2]
    gray = 0.2989 * r + 0.5870 * g + 0.1140 * b

    return gray

def transform_frame(frame, n):
    '''
    Transform the size of the frame. Note that the frame should be shaped M*M*3 (square)

    Args:
        frame (numpy array) : shaped M*M*3
        n (int)             : new size of the frame (n*n*3)

    Returns:
        frame (torch tensor) : shaped n*n*3

    '''
    shape = frame.shape
    assert len(shape) == 3
    assert shape[0] == shape[1] and shape[2] == 3

    color0 = frame[:,:,0]
    color1 = frame[:,:,1]
    color2 = frame[:,:,2]
    current_frame = np.array([color0, color1, color2])
    batch = torch.from_numpy(current_frame).unsqueeze(0)
    batch = F.interpolate(batch.float(), (n, n))
    small_frame = batch.squeeze(0)
    #print(small_frame.permute(1,2,0).shape)
    return small_frame.permute(1,2,0)
    #return -small_frame[0,:,:].unsqueeze(dim = -1)

def discount_rewards(r, gamma):
    '''
    Discounted sum of the rewards : r_t + gamma * r_{t+1} + gamma^2 * r_{t+2} ...

    Args:
        r (list)      : List of rewards returned by the environment
        gamma (float) : discount factor

    Return:
        dr (float) : discounted sum of the rewards
    '''
    discounted_r = torch.zeros_like(r)
    running_add = 0
    for t in reversed(range(0, r.size(-1))):
        running_add = running_add * gamma + r[t]
        discounted_r[t] = running_add
    return discounted_r

class PrintLayer(nn.Module):
    '''
    Print layer shapes inside a sequential structure
    '''
    def __init__(self):
        super(PrintLayer, self).__init__()

    def forward(self, x):
        # Do your print / debug stuff here
        print(x.shape)
        return x

def listdir_nohidden(path):
    '''
    Extension to os.listdir to ignore files starting with '.' or '_'
    '''
    for f in os.listdir(path):
        if not (f.startswith('.') or f.startswith('_')):
            yield f

def render_video(path):
    '''
    Renders a video provided as a path.

    Args:
        path (numpy array, compressed format) : array of frames
    '''
    # TODO: support for compressed format
    video = np.load(path)

    for frame in video:
        cv.imshsow("", frame)
        cv.waitKey(33)


#!/usr/bin/env python

import requests
import base64
import random
import sys

def getPicText_bdOcr(pic_binary, type_index = 1):
    '''
        利用百度 ocr 接口识别文字
        pic_binary  是图片文件的二进制数据
        type_index  是百度提供的识别类型 0 表示一般识别， 1 表示精准识别
        如果成功，则返回识别出的文字字符串
        如果出错，则返回错误信息
    '''

    # 获取 appid 的 cookie
    cookie = requests.get('http://ai.baidu.com/tech/ocr/general').headers["Set-Cookie"]
    baiduid_cookie = cookie[:cookie.find(';')]

    # 准备头部
    headers = {
        'Cookie': baiduid_cookie,
        'Referer': "http://ai.baidu.com/tech/ocr/general"
    }

    # 准备数据
    post_type_l = ['general_location' , 'https://aip.baidubce.com/rest/2.0/ocr/v1/accurate']
    if type_index is None:
        post_type = post_type_l[random.randint(0, len(post_type_l) - 1)]  # 两个类型随机选个类型
    else:
        post_type = post_type_l[type_index]

    post_data = {
        'type': post_type,
        'detect_direction': 'false',
        'image_url': None,
        'image': 'data:image/jpeg;base64,' + base64.b64encode(pic_binary).decode('utf-8'),
        'language_type': 'CHN_ENG'
    }

    # 发送 post 请求
    response = requests.post('http://ai.baidu.com/aidemo', data = post_data, headers = headers)

    # 解析请求结果
    response_json = response.json()
    errno = response_json['errno']
    if errno != 0:
        return '我这边出错啦！' + response_json['msg']

    data = response_json['data']
    words_result_num = data['words_result_num']
    words_result = data['words_result']

    result = ''
    for w in words_result:
        result += w['words'] + '\n'
    return result.strip()

if __name__ == '__main__':
    # 检查参数长度
    if len(sys.argv) < 2:
        sys.stderr.write('缺少文件位置参数\n')
        exit(1)

    pic_binary = open(sys.argv[1], 'rb').read()
    print(getPicText_bdOcr(pic_binary, 1))


from datetime import datetime
from blockCountries import blockCountryList

def minOfGame(isPlaying,isBreak : bool,  startPeriod :datetime, startGame :datetime):
    '''
    :param startPeriod: время начала периода
    :param startGame: время начала игры
    :return: minOfGame - минута игры, isBreak - перерыв
    '''
    minGame = -999999
    dPeriodGameTime = startPeriod - startGame  #разница между началом тайма(перерыва) и началом игры
    minPeriod  = int((datetime.now() - startPeriod).total_seconds() // 60) #минута тайма
    if not isPlaying: # игра окончена
        minGame=-1
    elif isBreak:
        minGame =45
    elif dPeriodGameTime.total_seconds() < 1800: #в первом тайме
        minGame=minPeriod
    elif (dPeriodGameTime.total_seconds() > 3300): #во втором тайме
        minGame=(45+minPeriod)
    #if isPlaying:
    #    print(isPlaying, isBreak, startGame, startPeriod , dPeriodGameTime.total_seconds())
    #if minGame==-999999:
    #    print(isPlaying, isBreak, startPeriod, startGame,  dPeriodGameTime.total_seconds())
    #    print(minGame,isBreak)
    return minGame

def isCorrectGame(game: dict) -> bool:
    '''
    :param game: словарь, заполненный данными матча
    :return: True, если матч удовлетворяет фильтрам по времени, голам
    '''
    def getGoalFilter(game:dict) ->bool:
        if (type(game['score1']) == str) and (type(game['score2']) == str):
            return  (game['score1'] == '0') and (game['score2'] == '0')
        else:
            return  False
    def getTimeFilter(game:dict) ->bool:
        minGame = minOfGame(game['isplaying'], game['isbreak'], game['startperiod'], game['startdate'])
        return  (minGame > 30) and (minGame < 60)

    countryFilter = not (game['country'].lower() in blockCountryList)
    gameIsPlaying = game['isplaying']
    timeFilter    = getTimeFilter(game)
    goalFilter    = getGoalFilter(game)
    #
    if gameIsPlaying and timeFilter and goalFilter and countryFilter:
        return True
    else:
        return False

def gameToStr(game : dict) -> str:
    def minOrBreak(isPlaying,isBreak : bool, startPeriod:datetime, startGame : datetime) -> str:
        minGame = minOfGame(isPlaying,isBreak,startPeriod, startGame)
        if isBreak:
            minStr='Перерыв'
        else:
            minStr = '{}\' мин'.format(minGame)
        return minStr
    return '{} {}. {}. {} {} - {} {}'.format(game['startdate'].strftime('%H:%M'),game['startperiod'].strftime('%H:%M'),
                                                  minOrBreak(game['isplaying'],game['isbreak'], game['startperiod'],game['startdate']),
                                                  game['team1'],game['score1'], game['score2'], game['team2'])

def addNewCountry(game : dict):
    outlist=[]
    outlist.append('*{}*'.format(game['country']))
    outlist.append('*{}*'.format(game['tourney']))
    outlist.append(gameToStr(game))
    return  outlist

def addNewTourney(game : dict):
    outlist=[]
    outlist.append('*{}*'.format(game['tourney']))
    outlist.append(gameToStr(game))
    return  outlist

def addGame(game : dict):
    outlist=[]
    outlist.append(gameToStr(game))
    return  outlist


import os

from war3structs.storage import MPQ
from .common import PipeTransformer
from ..liquid import Liquid

class MapExtractorPipe(PipeTransformer):
  def gate(self, build, liquid):
    map_liquids = []
    map_path = os.path.join(build['etcdir'], 'temp%s' % liquid.name)

    # Begin by writing the liquid's contents to disk
    liquid.write_to(map_path)

    # Create folder to extract map files to
    map_dir = os.path.join(build['etcdir'], 'temp%s' % liquid.stem)
    os.makedirs(map_dir)

    # Open the result as an MPQ and extract files
    map_mpq = MPQ(map_path)

    for found_file in map_mpq.find('*'):
      try:
        out_path = os.path.join(map_dir, found_file.filename)
        found_file.extract(out_path)
        map_liquids.append(Liquid(out_path))
      except Exception:
        pass

    map_mpq.close()

    return map_liquids


"""

.. attribute:: whole_cache

    Used when specifying the scope for a cache invalidation operation to
    indicate that the whole cache should be cleared.

.. moduleauthor:: Martí Congost <marti.congost@whads.com>
"""
from typing import Iterable, Union

from cocktail.modeling import (
    OrderedSet,
    ListWrapper,
    SetWrapper
)

class WholeCache:
    pass

whole_cache = WholeCache()

ScopeSelector = Union[str, Iterable[str]]
Scope = Union[WholeCache, Iterable[ScopeSelector]]

def normalize_scope(scope: Scope) -> Scope:
    if scope is whole_cache:
        return whole_cache
    else:
        return set(selector for selector in resolve_selector(scope))

def resolve_selector(selector: ScopeSelector) -> Iterable[ScopeSelector]:

    if isinstance(selector, (str, tuple)):
        yield selector
    elif isinstance(selector, (
        list,
        set,
        frozenset,
        OrderedSet,
        ListWrapper,
        SetWrapper
    )):
        for selector_part in selector:
            for resolved_selector in resolve_selector(selector_part):
                yield resolved_selector
    else:
        raise TypeError(
            "Scope selectors must be strings, tuples or collections, got %r "
            "instead."
            % selector
        )


from django.db import models
from livesettings import config_value_safe, config_choice_values, SettingNotSet

def shipping_choices():
    try:
        return config_choice_values('SHIPPING','MODULES')
    except SettingNotSet:
        return ()

class ShippingChoiceCharField(models.CharField):

    def __init__(self, choices="__DYNAMIC__", *args, **kwargs):
        if choices == "__DYNAMIC__":
            kwargs['choices'] = shipping_choices()

        super(ShippingChoiceCharField, self).__init__(*args, **kwargs)

try:
    # South introspection rules for our custom field.
    from south.modelsinspector import add_introspection_rules, matching_details

    # get the kwargs for a Field instance
    # we're using Field, as CharField doesn't change __init__()
    _args, kwargs = matching_details(models.Field())

    add_introspection_rules([(
        (ShippingChoiceCharField, ),
        [],
        kwargs,
    )], ['shipping\.fields'])
except ImportError:
    pass


import sys
from typing import List, Optional, Tuple
import unittest

def all_construct(target_string: str, strings: List[str], memo=None) -> List[List[str]]:
    # Memo.
    if memo is None:
        memo = {}
    if target_string in memo:
        return memo[target_string]
    # Base case.
    if len(target_string) == 0:
        return [[]]
    # Count the number of solutions.
    memo[target_string] = []
    for string in strings:
        length_is_ok = len(string) <= len(target_string)
        match_at_start = target_string[:len(string)] == string
        if length_is_ok and match_at_start:
            remainder = target_string[len(string):]
            solution = all_construct(remainder, strings, memo)
            if len(solution) > 0:
                solution = [[string, *s] for s in solution]
                memo[target_string].extend(solution)
    return memo[target_string]

class SolutionTest(unittest.TestCase):
    def test_solution(self):
        sys.setrecursionlimit(10000)

        fixtures = [
            (
                ("abcdef", ["ab", "abc", "cd", "def", "abcd"]),
                [["abc", "def"]],
            ),
            (
                ("abcdef", ["ab", "abc", "cd", "def", "abcd", "ef", "c"]),
                [
                    ["ab", "cd", "ef"],
                    ["abc", "def"],
                    ["ab", "c", "def"],
                    ["abcd", "ef"],
                ],
            ),
            (
                ("skateboard", ["bo", "rd", "ate", "t", "ska", "sk", "boar"]),
                [],
            ),
            (
                ("", ["cat", "dog", "mouse"]),
                [[]],
            ),
            (
                ("purple", ["purp", "p", "ur", "le", "purpl"]),
                [["purp", "le"], ["p", "ur", "p", "le"]],
            ),
        ]

        def contains(solution, target):
            for s in solution:
                try:
                    self.assertEqual(s, target)
                    return True
                except AssertionError:
                    pass
            return False

        for inputs, output in fixtures:
            solution = all_construct(*inputs)
            self.assertEqual(len(output), len(solution))
            for target in output:
                self.assertTrue(contains(solution, target),
                                f"solution does not contain {target}\n\nSolution: {solution}")


# !/usr/local/python/bin/python
# -*- coding: utf-8 -*-
# (C) Wu Dong, 2020
# All rights reserved
# @Author: 'Wu Dong <wudong@eastwu.cn>'
# @Time: '2020-03-19 10:49'
""" 演示 pre-request 框架如何使用长度校验，仅针对字符串有效
"""
from flask import Flask
from pre_request import pre, Rule

app = Flask(__name__)
app.config["TESTING"] = True
client = app.test_client()

# 指定email=True，此时框架会自动判断用户入参是否符合email正则
length_params = {
    "params": Rule(gt=2, lt=4)
}

@app.route("/length", methods=["GET", "POST"])
@pre.catch(length_params)
def example_length_handler(params):
    return str(params)

def example_length_filter():
    """ 演示字符串长度验证
    """
    resp = client.get("/length", data={
        "params": "abc"
    })
    print(resp.data)

    resp = client.get("/length", data={
        "params": "a"
    })
    print(resp.data)

    resp = client.get("/length", data={
        "params": "abcde"
    })
    print(resp.data)

if __name__ == "__main__":
    example_length_filter()