from __future__ import annotations

import logging
import os
from typing import TYPE_CHECKING
from dataclasses import dataclass
from functools import cached_property

import numpy as np
from numba import jit, generated_jit

try:
    from coloredlogs import ColoredFormatter as Formatter
except ImportError:
    from logging import Formatter

__version__ = '0.2.0'

logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
logger.addHandler(handler)
handler.setFormatter(Formatter('%(name)s %(levelname)s %(message)s'))
try:
    level = os.environ.get('COSCONLOGLEVEL', logging.WARNING)
    logger.setLevel(level=level)
except ValueError:
    logger.setLevel(level=logging.WARNING)
    logger.error(f'Unknown COSCONLOGLEVEL {level}, set to default WARNING.')

@jit(nopython=True, nogil=True, cache=True)
def lastcol_quat_to_canonical(quat: np.ndarray[np.float_]) -> np.ndarray[np.float_]:
    """Convert from real-part-in-last-column to real-part-in-first-column"""
    return np.ascontiguousarray(quat[..., np.array([3, 0, 1, 2])])

@jit(nopython=True, nogil=True, cache=True)
def canonical_quat_to_lastcol(quat: np.ndarray[np.float_]) -> np.ndarray[np.float_]:
    """Convert from real-part-in-first-column to real-part-in-last-column"""
    return np.ascontiguousarray(quat[..., np.array([1, 2, 3, 0])])

@jit(nopython=True, nogil=True, cache=True)
def float64_to_complex128(array: np.ndarray[np.float64]) -> np.ndarray[np.complex128]:
    return array.view(np.complex128)

@jit(nopython=True, nogil=True, cache=True)
def float32_to_complex64(array: np.ndarray[np.float32]) -> np.ndarray[np.complex64]:
    return array.view(np.complex64)

@jit(nopython=True, nogil=True, cache=True)
def complex128_to_float64(array: np.ndarray[np.complex128]) -> np.ndarray[np.float64]:
    return array.view(np.float64)

@jit(nopython=True, nogil=True, cache=True)
def complex64_to_float32(array: np.ndarray[np.complex64]) -> np.ndarray[np.float32]:
    return array.view(np.float32)

@generated_jit(nopython=True, nogil=True, cache=True)
def float_to_complex(array: np.ndarray[np.float_]) -> np.ndarray[np.complex_]:
    dtype = str(array.dtype)
    if dtype == 'float64':
        return float64_to_complex128
    elif dtype == 'float32':
        return float32_to_complex64

@generated_jit(nopython=True, nogil=True, cache=True)
def complex_to_float(array: np.ndarray[np.complex_]) -> np.ndarray[np.float_]:
    dtype = str(array.dtype)
    if dtype == 'complex128':
        return complex128_to_float64
    elif dtype == 'complex64':
        return complex64_to_float32

@jit(nopython=True, nogil=True, cache=True)
def mul(p: np.ndarray[np.complex_], q: np.ndarray[np.complex_]) -> np.ndarray[np.complex_]:
    """Perform quarternion multiplication using complex multiplication"""
    A = p[..., 0]
    B = p[..., 1]
    C = q[..., 0]
    D = q[..., 1]
    real_i_part = A * C - B * np.conjugate(D)
    jk_part = B * np.conjugate(C) + A * D
    return np.stack((real_i_part, jk_part), -1)

@jit(nopython=True, nogil=True, cache=True)
def matmul(p: np.ndarray[np.complex_], q: np.ndarray[np.complex_]) -> np.ndarray[np.complex_]:
    """Perform quarternion matrix multiplication using complex matrix multiplication"""
    A = np.ascontiguousarray(p[..., 0])
    B = np.ascontiguousarray(p[..., 1])
    C = np.ascontiguousarray(q[..., 0])
    D = np.ascontiguousarray(q[..., 1])
    real_i_part = A @ C - B @ np.conjugate(D)
    jk_part = B @ np.conjugate(C) + A @ D
    return np.stack((real_i_part, jk_part), -1)

@jit(nopython=True, nogil=True, cache=True)
def quat_to_rotation_matrix(quats: np.ndarray[np.complex_]) -> np.ndarray[np.float_]:
    """Convert quaternion to rotation matrix.
    """
    I = np.array(
        (
            [1.j, 0.],
            [0. , 1.],
            [0., 1.j],
        ),
        dtype=quats.dtype,
    )
    res = rotate_2d(quats, I)
    return np.stack(
        (
            complex_to_float(res[0])[..., 1:],
            complex_to_float(res[1])[..., 1:],
            complex_to_float(res[2])[..., 1:],
        ),
        -1,
    )

@jit(nopython=True, nogil=True, cache=True)
def rotation_matrix_to_quat(m: np.ndarray[np.float_]) -> np.ndarray[np.complex_]:
    """Convert rotation matrix to quaternion.

    See https://en.wikipedia.org/wiki/Rotation_matrix#Quaternion
    """
    Q_xx = m[..., 0, 0]
    Q_xy = m[..., 0, 1]
    Q_xz = m[..., 0, 2]
    Q_yx = m[..., 1, 0]
    Q_yy = m[..., 1, 1]
    Q_yz = m[..., 1, 2]
    Q_zx = m[..., 2, 0]
    Q_zy = m[..., 2, 1]
    Q_zz = m[..., 2, 2]
    # abs is for guarding against floating point error
    wx = 0.5                          * np.sqrt(np.abs(1. + Q_xx + Q_yy + Q_zz)) + \
        (0.5j * np.sign(Q_zy - Q_yz)) * np.sqrt(np.abs(1. + Q_xx - Q_yy - Q_zz))
    yz = (0.5 * np.sign(Q_xz - Q_zx)) * np.sqrt(np.abs(1. - Q_xx + Q_yy - Q_zz)) + \
        (0.5j * np.sign(Q_yx - Q_xy)) * np.sqrt(np.abs(1. - Q_xx - Q_yy + Q_zz))
    return np.stack((wx, yz), -1)

@jit(nopython=True, nogil=True, cache=True)
def conjugate(p: np.ndarray[np.complex_]) -> np.ndarray[np.complex_]:
    res = np.empty_like(p)
    res[..., 0] = np.conjugate(p[..., 0])
    res[..., 1] = -p[..., 1]
    return res

@jit(nopython=True, nogil=True, cache=True)
def norm(p: np.ndarray[np.complex_]) -> np.ndarray[np.float_]:
    return (p * np.conjugate(p)).sum(axis=-1)

@jit(nopython=True, nogil=True, cache=True)
def abs(p: np.ndarray[np.complex_]) -> np.ndarray[np.float_]:
    return np.sqrt(norm(p))

@jit(nopython=True, nogil=True, cache=True)
def inverse(p: np.ndarray[np.complex_]) -> np.ndarray[np.complex_]:
    return conjugate(p) / norm(p).reshape(*p.shape[:-1], 1)

@jit(nopython=True, nogil=True, cache=True)
def rotate(p: np.ndarray[np.complex_], v: np.ndarray[np.complex_]) -> np.ndarray[np.complex_]:
    """Rotate v by p respecting Numpy broadcasting rule."""
    p_inv = inverse(p)
    return mul(mul(p, v), p_inv)

@jit(nopython=True, nogil=True, cache=True)
def rotate_2d(p: np.ndarray[np.complex_], v: np.ndarray[np.complex_]) -> List[np.ndarray[np.complex_]]:
    """Rotate each row of v by p and stack at an axis.

    :param v: 2d-array
    """
    p_inv = inverse(p)
    return [mul(mul(p, row), p_inv) for row in v]

@jit(nopython=True, nogil=True, cache=True)
def quat_to_azimuthal_equidistant_projection_polar_with_orientation(quats: np.ndarray[np.complex_]) -> np.ndarray[np.float_]:
    """Convert from detector pointing to Azimuthal equidistant projection in polar coordinate with orientation.

    Returned array is in radian,
    has the last dimension with 3 elements,
    1st as the angular distance to North pole,
    2nd as the azimuth,
    3rd as the orientation in angle.
    """
    xz_axes = np.array(
        (
            [1.j, 0.],
            [0., 1.j],
        ),
        dtype=quats.dtype,
    )
    # rotation from boresight
    orients, r = rotate_2d(quats, xz_axes)
    ds = np.arccos(r[:, 1].imag)
    angles = np.arctan2(r[:, 1].real, r[:, 0].imag)

    return np.stack(
        (
            ds,
            angles,
            np.arctan2(orients[:, 1].real, orients[:, 0].imag)
        ),
        -1,
    )

@jit(nopython=True, nogil=True, cache=True)
def quat_to_azimuthal_equidistant_projection_with_orientation(quats: np.ndarray[np.complex_]) -> np.ndarray[np.float_]:
    """Convert from detector pointing to Azimuthal equidistant projection in cartesian coordinate with orientation.

    Returned array is in radian,
    has the last dimension with 3 elements,
    1st as the horizontal angular position,
    2nd as the vertical angular position,
    3rd as the orientation in angle.
    """
    xz_axes = np.array(
        (
            [1.j, 0.],
            [0., 1.j],
        ),
        dtype=quats.dtype,
    )
    # rotation from boresight
    orients, r = rotate_2d(quats, xz_axes)
    ds = np.arccos(r[:, 1].imag)
    angles = np.arctan2(r[:, 1].real, r[:, 0].imag)

    return np.stack(
        (
            ds * np.cos(angles),
            ds * np.sin(angles),
            np.arctan2(orients[:, 1].real, orients[:, 0].imag)
        ),
        -1,
    )

@jit(nopython=True, nogil=True, cache=True)
def azimuthal_equidistant_projection_polar_with_orientation_to_rotation_matrix(array: np.ndarray[np.float_]) -> np.ndarray[np.float_]:
    """Convert Azimuthal equidistant projection in polar coordinate with orientation to detector pointing.

    Input array is in radian,
    has the last dimension with 3 elements,
    1st as the angular distance to North pole,
    2nd as the azimuth,
    3rd as the orientation in angle.
    """
    theta = array[..., 0]
    phi = array[..., 1]
    alpha = array[..., 2]

    sin_theta = np.sin(theta)
    Rz = np.stack(
        (
            sin_theta * np.cos(phi),
            sin_theta * np.sin(phi),
            np.cos(theta),
        ),
        -1,
    )
    # t is cot(beta)
    t = -np.tan(theta) * np.cos(phi - alpha)
    sin_beta = np.power(1. + np.square(t), -0.5)
    Rx = np.stack(
        (
            sin_beta * np.cos(alpha),
            sin_beta * np.sin(alpha),
            t * sin_beta,
        ),
        -1,
    )

    return np.stack((Rx, np.cross(Rz, Rx), Rz), -1)

@jit(nopython=True, nogil=True, cache=True)
def dist_spherical(p: np.ndarray[np.complex_], q: np.ndarray[np.complex_]) -> float:
    """Great circle distance between 2 detector quaternions."""
    z = np.array([0., 1.j], dtype=p.dtype)
    p_z = complex_to_float(rotate(p.reshape(1, 2), z))
    q_z = complex_to_float(rotate(q.reshape(1, 2), z))
    dot = (p_z[0, 1:] * q_z[0, 1:]).sum()
    # protect against floating point error
    if dot > 1.:
        assert dot - 1. < 1e-3
        return 0.
    elif dot < -1.:
        assert -dot - 1. < 1e-3
        return np.pi
    else:
        return np.arccos(dot)

@jit(nopython=True, nogil=True, cache=True)
def dist_spherical_pairwise(ps: np.ndarray[np.complex_]) -> np.ndarray[np.float_]:
    """Pair-wise great circle distances between detector quaternions.

    Assume input is a 1-dim array of quarternions (2d-array)
    and return pairwise distance in 1d-array,
    ordered in "row-major" and "j>i" directions.
    E.g. for 3 detectors, [(0, 1), (0, 2), (1, 2)] ordering.
    """
    # make sure it is 2d-array
    n, _ = ps.shape
    size = (n * (n - 1)) // 2
    res = np.empty(size)
    k = 0
    for i in range(n):
        for j in range(i + 1, n):
            res[k] = dist_spherical(ps[i], ps[j])
            k += 1
    return res

@jit(nopython=True, nogil=True, cache=True)
def dist_spherical_pairwise_from_lastcol_array(ps: np.ndarray[np.float_]) -> np.ndarray[np.float_]:
    return dist_spherical_pairwise(float_to_complex(lastcol_quat_to_canonical(ps)))

@dataclass
class Quaternion:
    array_complex: np.ndarray[np.complex_]

    def __post_init__(self):
        assert self.array_complex.shape[-1] == 2

    def clear_cache(self):
        try:
            del self.array
        except AttributeError:
            pass
        try:
            del self.lastcol_array
        except AttributeError:
            pass
        try:
            del self.azimuthal_equidistant_projection_polar_with_orientation
        except AttributeError:
            pass
        try:
            del self.azimuthal_equidistant_projection_with_orientation
        except AttributeError:
            pass
        try:
            del self.to_rotation_matrix
        except AttributeError:
            pass

    @cached_property
    def array(self) -> np.ndarray[np.float_]:
        return complex_to_float(self.array_complex)

    @cached_property
    def lastcol_array(self) -> np.ndarray[np.float_]:
        return canonical_quat_to_lastcol(complex_to_float(self.array_complex))

    def __add__(self, other: Quaternion) -> Quaternion:
        return Quaternion(self.array + other.array)

    def __iadd__(self, other: Quaternion):
        self.clear_cache()
        self.array += other.array

    def __mul__(self, other: Quaternion) -> Quaternion:
        return Quaternion(mul(self.array_complex, other.array_complex))

    def __imul__(self, other: Quaternion):
        self.clear_cache()
        self.array_complex = mul(self.array_complex, other.array_complex)

    def __matmul__(self, other: Quaternion) -> Quaternion:
        return Quaternion(matmul(self.array_complex, other.array_complex))

    def __imatmul__(self, other: Quaternion) -> Quaternion:
        self.clear_cache()
        self.array_complex = matmul(self.array_complex, other.array_complex)

    @property
    def conjugate(self) -> Quaternion:
        return Quaternion(conjugate(self.array))

    @property
    def norm(self) -> np.ndarray[np.float_]:
        return norm(self.array)

    @property
    def abs(self) -> np.ndarray[np.float_]:
        return abs(self.array)

    @property
    def normalize(self) -> Quaternion:
        return Quaternion(self.array_complex / self.abs[..., None])

    @property
    def inverse(self) -> Quaternion:
        return Quaternion(inverse(self.array_complex))

    def rotate(self, other: Quaternion) -> Quaternion:
        return Quaternion(rotate(self.array_complex, other.array_complex))

    @cached_property
    def azimuthal_equidistant_projection_polar_with_orientation(self) -> np.ndarray[np.float_]:
        """Convert from detector pointing to Azimuthal equidistant projection in polar coordinate with orientation.

        Returned array is in radian,
        has the last dimension with 3 elements,
        1st as the angular distance to North pole,
        2nd as the azimuth,
        3rd as the orientation in angle.
        """
        return quat_to_azimuthal_equidistant_projection_polar_with_orientation(self.array_complex)

    @cached_property
    def azimuthal_equidistant_projection_with_orientation(self) -> np.ndarray[np.float_]:
        """Convert from detector pointing to Azimuthal equidistant projection in cartesian coordinate with orientation.

        Returned array is in radian,
        has the last dimension with 3 elements,
        1st as the horizontal angular position,
        2nd as the vertical angular position,
        3rd as the orientation in angle.
        """
        return quat_to_azimuthal_equidistant_projection_with_orientation(self.array_complex)

    @classmethod
    def from_array(cls, array: np.ndarray[np.float_]) -> Quaternion:
        """Create Quaternion from real array with last axis as w, x, y, z.
        """
        return cls(float_to_complex(array))

    @classmethod
    def from_lastcol_array(cls, array: np.ndarray[np.float_]) -> Quaternion:
        """Create Quaternion from real array with last axis as x, y, z, w.

        Convention used in TOAST.
        """
        return cls(float_to_complex(lastcol_quat_to_canonical(array)))

    @classmethod
    def from_rotation_matrix(cls, array: np.ndarray[np.float_]) -> Quaternion:
        return cls(rotation_matrix_to_quat(array))

    @classmethod
    def from_azimuthal_equidistant_projection_polar_with_orientation(cls, array: np.ndarray[np.float_]) -> Quaternion:
        m = azimuthal_equidistant_projection_polar_with_orientation_to_rotation_matrix(array)
        return cls.from_rotation_matrix(m)

    @cached_property
    def to_rotation_matrix(self) -> np.ndarray[np.float_]:
        return quat_to_rotation_matrix(self.array_complex)


import numpy as np
import matplotlib.pyplot as plt
import sys
import torch
from torch import nn
from torch import optim

from environment import environment

#https://towardsdatascience.com/breaking-down-richard-suttons-policy-gradient-9768602cb63b

#https://towardsdatascience.com/learning-reinforcement-learning-reinforce-with-pytorch-5e8ad7fc7da0

class policy_estimator():
    def __init__(self, env):
        self.n_inputs = len(env.cur_state)
        self.n_outputs = 2

        # Define network
        self.network = nn.Sequential(
            nn.Linear(self.n_inputs, 16),
            nn.ReLU(),
            nn.Linear(16, self.n_outputs),
            nn.Softmax(dim=-1))

    def predict(self, state):
        action_probs = self.network(torch.FloatTensor(state))
        return action_probs

def discount_rewards(rewards, gamma=0.99):
    r = np.array([gamma**i * rewards[i]
        for i in range(len(rewards))])
    # Reverse the array direction for cumsum and then
    # revert back to the original order
    r = r[::-1].cumsum()[::-1]
    return r - r.mean()

print(discount_rewards([1,2,3,4]))                         #########################################

def read_action(action):
        if action == 0:
            return False
        else:
            return True

def reinforce(env, policy_estimator, num_episodes=2000,
              batch_size=10, gamma=0.99):
    # Set up lists to hold results
    total_rewards = []
    batch_rewards = []
    batch_actions = []
    batch_states = []
    batch_counter = 1

    # Define optimizer
    optimizer = optim.Adam(policy_estimator.network.parameters(),
                           lr=0.00005)

    action_space = 2
    action_space = np.arange(action_space)
    ep = 0
    while ep < num_episodes:
        env.restart()
        s_0 = env.cur_state
        states = []
        rewards = []
        actions = []
        done = False
        while done == False:
            # Get actions and convert to numpy array
            action_probs = policy_estimator.predict(
                s_0).detach().numpy()
            action = np.random.choice(action_space,
                p=action_probs)

            env.update(read_action(action))
            #s_1, r, done, _ = env.step(action)

            s_1 = env.cur_state

            if env.isGameOver:
                r = -100
            else:
                r = 1

            states.append(s_0)
            rewards.append(r)
            actions.append(action)
            s_0 = s_1

            # If done, batch data
            #if done
            if env.isGameOver:
                batch_rewards.extend(discount_rewards(
                    rewards, gamma))
                batch_states.extend(states)
                batch_actions.extend(actions)
                batch_counter += 1
                total_rewards.append(sum(rewards))

                # If batch is complete, update network
                if batch_counter == batch_size:
                    optimizer.zero_grad()
                    state_tensor = torch.FloatTensor(batch_states)
                    reward_tensor = torch.FloatTensor(
                        batch_rewards)
                    # Actions are used as indices, must be
                    # LongTensor
                    action_tensor = torch.LongTensor(
                       batch_actions)

                    # Calculate loss
                    logprob = torch.log(
                        policy_estimator.predict(state_tensor))
                    selected_logprobs = reward_tensor * torch.gather(logprob, 1,
                        action_tensor).squeeze()
                    loss = -selected_logprobs.mean()

                    # Calculate gradients
                    loss.backward()
                    # Apply gradients
                    optimizer.step()

                    batch_rewards = []
                    batch_actions = []
                    batch_states = []
                    batch_counter = 1

                avg_rewards = np.mean(total_rewards[-100:])
                # Print running average
                print("\rEp: {} Average of last 100:" +
                     "{:.2f}".format(
                     ep + 1, avg_rewards), end="")
                ep += 1

    return total_rewards

env = environment(289,511,52,320,34,24,112,difficulty = 4)
policy_est = policy_estimator(env)
rewards = reinforce(env, policy_est)

plt.plot(rewards)
plt.show()

#TODO:
#1. Debug
#2. Save Network
#3. Implement epsilon-greedy???


#######################################################
############## Reshaped Wirtinger Flow ################
#######################################################
import torch
import numpy as np
import math

cuda_opt = True
if torch.cuda.is_available() & cuda_opt:
    DEVICE = "cuda"
else:
    DEVICE = "cpu"
n2 = 1
n = 64
m = 10 * n
cplx_flag = 1
mu = 0.8 + 0.4 * cplx_flag
T = 800
npower_iter = 30

def A(Amatrix, X):
    return torch.linalg.matmul(Amatrix, X)

def Ah(Amatrix, Z):
    return torch.linalg.matmul(Amatrix.adjoint(), Z)

def generator():
    x = (
        torch.randn((n, 1), dtype=torch.double)
        + torch.randn((n, 1), dtype=torch.double) * 1j
    )
    Amatrix = (
        torch.randn((m, n), dtype=torch.double)
        + torch.randn((m, n), dtype=torch.double) * 1j
    ) / math.sqrt(2) ** cplx_flag
    y = torch.abs(A(Amatrix, x))
    return Amatrix, x, y

def rwf(Amatrix, x, y):
    Relerrs = np.zeros((T + 1, 1))
    z0 = torch.randn((n, n2), dtype=torch.cdouble)
    torch.linalg.norm(z0)
    n_r = math.sqrt(math.pi / 2) * (1 - cplx_flag)
    n_c = math.sqrt(4 / math.pi) * cplx_flag
    n_y = torch.sum(y) / m
    normest = (n_r + n_c) * n_y
    ytr = torch.multiply(y, (torch.abs(y) > 1 * normest))
    for tt in range(npower_iter):
        z0 = Ah(Amatrix, torch.multiply(ytr, (A(Amatrix, z0))))
        z0 = z0 / torch.linalg.norm(z0)
    z0 = normest * z0
    z = z0
    Relerrs[0] = torch.linalg.norm(
        x - torch.exp(-1j * torch.angle(torch.trace(x.H * z))) * z
    ) / torch.linalg.norm(x)
    for t in range(T + 1):
        yz = Amatrix @ z
        yz_abs = torch.abs(yz)
        first_divide = torch.divide(yz, yz_abs)
        first_multi = torch.multiply(y, first_divide)
        sub = yz - first_multi
        second_multi = Ah(Amatrix, sub)
        second_divide = torch.divide(second_multi, m)
        z = z - mu * second_divide
        ang_tr = torch.angle(torch.trace(x.H * z))
        diff = x - torch.exp(-1j * ang_tr) * z
        norm = torch.linalg.norm(x)
        Relerrs[t] = torch.linalg.norm(diff) / norm
    return z, Relerrs

Amatrix, x, y = generator()
z, err = rwf(Amatrix, x, y)
print(err, "\n", err[-1])


import datetime
import io
import json
import os
import urllib.error
import urllib.request
import zipfile

from typing import Tuple, Optional, Any, Dict, List

FABRIC_YARN_V2_META_URL = 'https://meta.fabricmc.net/v2/versions/yarn'
FABRIC_YARN_V2_URL = 'https://maven.fabricmc.net/net/fabricmc/yarn/%s+build.%s/yarn-%s+build.%s-v2.jar'
FABRIC_INTERMEDIARY_URL = 'https://raw.githubusercontent.com/FabricMC/intermediary/master/mappings//%s.tiny'
MCP_BOT_MCP_MAVEN_URL = 'http://export.mcpbot.bspk.rs/mcp_snapshot/%s-%s/mcp_snapshot-%s-%s.zip'
FORGE_MCP_MAVEN_URL = 'https://files.minecraftforge.net/maven/de/oceanlabs/mcp/mcp_snapshot/%s-%s/mcp_snapshot-%s-%s.zip'
GIGAHERTZ_MCP_MAVEN_URL = 'https://www.dogforce-games.com/maven/de/oceanlabs/mcp/mcp_snapshot/%s-%s/mcp_snapshot-%s-%s.zip'
TTERRAG_MCP_MAVEN_URL = 'https://maven.tterrag.com/de/oceanlabs/mcp/mcp_snapshot/%s-%s-%s/mcp_snapshot-%s-%s-%s.zip'
FORGE_MCP_CONFIG_URL = 'https://files.minecraftforge.net/maven/de/oceanlabs/mcp/mcp_config/%s/mcp_config-%s.zip'
OFFICIAL_MANIFEST_URL = 'https://launchermeta.mojang.com/mc/game/version_manifest.json'
FORGE_MCP_SPREADSHEET_URL = 'https://docs.google.com/spreadsheets/u/0/d/14knNUYjYkKkGpW9VTyjtlhaCTUsPWRJ91GLOFX2d23Q/export?gid=1460654099&format=csv'

# API link to get the spreadsheet url: https://spreadsheets.google.com/feeds/worksheets/{fileId}/private/full

DEFAULT_MCP_MAVENS = [GIGAHERTZ_MCP_MAVEN_URL, FORGE_MCP_MAVEN_URL, MCP_BOT_MCP_MAVEN_URL]

CACHE_ROOT = '../build/'
FABRIC_YARN_V2_CACHE = 'yarn_v2-%s+build.%s.tiny'
FABRIC_INTERMEDIARY_CACHE = 'yarn_intermediary-%s.tiny'
MCP_CACHE = 'mcp_snapshot-%s-%s'
MCP_CONFIG_CACHE = 'mcpconfig-%s'
OFFICIAL_MANIFEST_CACHE = 'official-manifest.json'
OFFICIAL_VERSION_MANIFEST_CACHE = 'official-manifest-%s.json'
OFFICIAL_MAPPING_CACHE = 'official-%s'
FORGE_SPREADSHEET_CACHE = 'forge-mms-spreadsheet-%s.csv'
CORRECTIONS_CACHE = 'corrections-%s.json'

def get_cache_root() -> str:
    global CACHE_ROOT
    return CACHE_ROOT

def set_cache_root(cache: str):
    global CACHE_ROOT
    CACHE_ROOT = cache
    # Verify that the cache location exists
    if not os.path.isdir(CACHE_ROOT):
        os.makedirs(CACHE_ROOT)

set_cache_root(CACHE_ROOT)

def load_yarn_v2(mc_version: str, yarn_version: Optional[str] = None) -> str:
    if yarn_version is None:
        # find the newest build
        yarn_mappings = sorted([f for f in os.listdir(CACHE_ROOT) if os.path.isfile(CACHE_ROOT + f) and f.startswith('yarn_v2-%s' % mc_version)])
        if yarn_mappings:
            with open(CACHE_ROOT + yarn_mappings[-1]) as f:
                return f.read()
    else:
        # find exact match
        path = CACHE_ROOT + FABRIC_YARN_V2_CACHE % (mc_version, yarn_version)
        if os.path.isfile(path):
            with open(path) as f:
                return f.read()

    # fetch yarn metadata in order to determine available build numbers
    with urllib.request.urlopen(FABRIC_YARN_V2_META_URL) as request:
        versions = json.loads(request.read().decode('utf-8'))

    # filter versions based on passed in mc and yarn version numbers
    versions = [v for v in versions if v['gameVersion'] == mc_version and (yarn_version is None or v['build'] == yarn_version)]
    if versions is None:
        raise RuntimeError('Unable to find a version matching %s and %s' % (mc_version, str(yarn_version)))
    if yarn_version is None:
        versions.sort(key=lambda k: -k['build'])
        yarn_version = versions[0]['build']

    # download specified yarn build
    url = FABRIC_YARN_V2_URL % (mc_version, yarn_version, mc_version, yarn_version)
    with urllib.request.urlopen(url) as request:
        tiny_jar = request.read()
    with io.BytesIO(tiny_jar) as fio:
        with zipfile.ZipFile(fio, 'r') as tiny_zip:
            with tiny_zip.open('mappings/mappings.tiny') as f:
                mappings = sanitize_utf8(f.read())

    # save to local cache
    path = CACHE_ROOT + FABRIC_YARN_V2_CACHE % (mc_version, yarn_version)
    with open(path, 'w', encoding='utf-8') as f:
        f.write(mappings)

    return mappings

def load_yarn_intermediary(mc_version: str) -> str:
    path = CACHE_ROOT + FABRIC_INTERMEDIARY_CACHE % mc_version
    if os.path.isfile(path):
        with open(path) as f:
            return f.read()

    url = FABRIC_INTERMEDIARY_URL % mc_version
    with urllib.request.urlopen(url) as request:
        intermediary = sanitize_utf8(request.read())

    # save to local cache
    with open(path, 'w') as f:
        f.write(intermediary)
    return intermediary

def load_mcp_mappings(mc_version: str, mcp_date: Optional[str] = None, mcp_version: Optional[str] = None, urls: Optional[List[str]] = None, cache_location: Optional[str] = None) -> Tuple[str, str, str]:
    # Determine default arguments and versions
    if mcp_date is None:
        mcp_date = str(datetime.date.today()).replace('-', '')
    if mcp_version is None:
        mcp_version = mcp_date
    if cache_location is None:
        cache_location = CACHE_ROOT + MCP_CACHE % (mcp_version, mc_version)
    if urls is None:
        urls = [u % (mcp_version, mc_version, mcp_version, mc_version) for u in DEFAULT_MCP_MAVENS]

    # Check cache first
    if os.path.isdir(cache_location):
        with open(cache_location + '/methods.csv') as f:
            methods = f.read()
        with open(cache_location + '/fields.csv') as f:
            fields = f.read()
        with open(cache_location + '/params.csv') as f:
            params = f.read()
        return methods, fields, params

    # Sequentially try all provided urls for the given version
    export = error = None
    while export is None and urls:
        url = urls.pop(0)
        export, error = try_download(url)
    if export is None:
        raise error

    # Read the successful export
    with io.BytesIO(export) as fio:
        with zipfile.ZipFile(fio, 'r') as export_zip:
            with export_zip.open('methods.csv') as f:
                methods = sanitize_utf8(f.read())
            with export_zip.open('fields.csv') as f:
                fields = sanitize_utf8(f.read())
            try:
                with export_zip.open('params.csv') as f:
                    params = sanitize_utf8(f.read())
            except KeyError:
                params = ''  # Some mappings (yarn2mcp) do not include params

    # Save all files to the local cache
    if not os.path.isdir(cache_location):
        os.mkdir(cache_location)
    with open(cache_location + '/methods.csv', 'w') as f:
        f.write(methods)
    with open(cache_location + '/fields.csv', 'w') as f:
        f.write(fields)
    with open(cache_location + '/params.csv', 'w') as f:
        f.write(params)
    return methods, fields, params

def load_mcpconfig(mc_version: str) -> Tuple[str, str, str]:
    path = CACHE_ROOT + MCP_CONFIG_CACHE % mc_version

    # Check local cache first
    if os.path.isdir(path):
        with open(path + '/joined.tsrg') as f:
            joined = f.read()
        with open(path + '/static_methods.txt') as f:
            static_methods = f.read()
        with open(path + '/constructors.txt') as f:
            constructors = f.read()
        return joined, static_methods, constructors

    # Cache miss, download from forge maven
    url = FORGE_MCP_CONFIG_URL % (mc_version, mc_version)
    with urllib.request.urlopen(url) as request:
        mcp_config = request.read()
    with io.BytesIO(mcp_config) as fio:
        with zipfile.ZipFile(fio, 'r') as mcp_config_zip:
            with mcp_config_zip.open('config/joined.tsrg') as f:
                joined = sanitize_utf8(f.read())
            with mcp_config_zip.open('config/static_methods.txt') as f:
                static_methods = sanitize_utf8(f.read())
            with mcp_config_zip.open('config/constructors.txt') as f:
                constructors = sanitize_utf8(f.read())

    # Save to local cache
    if not os.path.isdir(path):
        os.mkdir(path)
    with open(path + '/joined.tsrg', 'w') as f:
        f.write(joined)
    with open(path + '/static_methods.txt', 'w') as f:
        f.write(static_methods)
    with open(path + '/constructors.txt', 'w') as f:
        f.write(constructors)
    return joined, static_methods, constructors

def load_official(mc_version: str) -> Tuple[str, str]:
    # Need to inspect the manifest
    def load_manifest(use_cache: bool = True) -> Tuple[Dict, bool]:
        official_manifest = CACHE_ROOT + OFFICIAL_MANIFEST_CACHE
        if os.path.isfile(official_manifest) and use_cache:
            # Load manifest from cache
            with open(official_manifest) as f_:
                manifest = f_.read()
            return json.loads(manifest), True
        else:
            # Download and save manifest
            with urllib.request.urlopen(OFFICIAL_MANIFEST_URL) as request_:
                manifest = sanitize_utf8(request_.read())
            with open(official_manifest, 'w') as f_:
                f_.write(manifest)
            return json.loads(manifest), False

    def find_game_version_manifest_matching(manifest_json_in: Dict, mc_version_in: str) -> Optional[str]:
        for game_version_json in manifest_json_in['versions']:
            if game_version_json['id'] == mc_version_in:
                return game_version_json['url']
        return None

    manifest_json = None
    was_cached = False
    if mc_version is None:
        manifest_json, was_cached = load_manifest()
        mc_version = manifest_json['latest']['release']

    # Check the official mapping cache
    mapping_path = CACHE_ROOT + OFFICIAL_MAPPING_CACHE % mc_version
    if os.path.isdir(mapping_path):
        # Official cache found
        with open(mapping_path + '/client.txt') as f:
            client = sanitize_utf8(f.read())
        with open(mapping_path + '/server.txt') as f:
            server = sanitize_utf8(f.read())
        return client, server

    # Need to download the official mappings. Check if the version manifest is present
    version_meta_path = CACHE_ROOT + OFFICIAL_VERSION_MANIFEST_CACHE % mc_version
    if os.path.isfile(version_meta_path):
        # Load the version manifest, in order to get the mapping urls
        with open(version_meta_path) as f:
            version_meta_json = json.loads(f.read())
    else:
        # No version manifest, so load the full manifest
        if manifest_json is None:
            manifest_json, was_cached = load_manifest()

        # Find the version manifest matching the mc version
        version_manifest_url = find_game_version_manifest_matching(manifest_json, mc_version)

        # If not found, and the manifest was cached, then reload the manifest without caching and try again
        if version_manifest_url is None and was_cached:
            manifest_json, was_cached = load_manifest(False)
            version_manifest_url = find_game_version_manifest_matching(manifest_json, mc_version)

        # If still not none, throw an error
        if version_manifest_url is None:
            raise ValueError('No manifest entry for game version %s' % mc_version)

        # Download and save manifest
        with urllib.request.urlopen(version_manifest_url) as request:
            version_manifest = sanitize_utf8(request.read())
        with open(version_meta_path, 'w') as f:
            f.write(version_manifest)

        version_meta_json = json.loads(version_manifest)

    # Identify urls for mappings
    client_url = version_meta_json['downloads']['client_mappings']['url']
    server_url = version_meta_json['downloads']['server_mappings']['url']

    # Load official mappings
    with urllib.request.urlopen(client_url) as request:
        client = sanitize_utf8(request.read())
    with urllib.request.urlopen(server_url) as request:
        server = sanitize_utf8(request.read())

    # And save to cache
    if not os.path.isdir(mapping_path):
        os.mkdir(mapping_path)
    with open(mapping_path + '/client.txt', 'w') as f:
        f.write(client)
    with open(mapping_path + '/server.txt', 'w') as f:
        f.write(server)
    return client, server

def load_mcp_spreadsheet(mc_version: str) -> str:
    path = CACHE_ROOT + FORGE_SPREADSHEET_CACHE % mc_version
    if os.path.isfile(path):
        with open(path) as f:
            return f.read()

    url = FORGE_MCP_SPREADSHEET_URL
    with urllib.request.urlopen(url) as request:
        text = sanitize_utf8(request.read())
    with open(path, 'w') as f:
        f.write(text)

    return text

def load_manual_corrections(mc_version: str) -> Dict[str, str]:
    path = CACHE_ROOT + CORRECTIONS_CACHE % mc_version
    if os.path.isfile(path):
        with open(path) as f:
            text = sanitize_utf8(f.read())
            return json.loads(text)

    with open(path, 'w') as f:
        f.write('{\n}')

    return {}

def try_download(url: str) -> Optional[Any]:
    try:
        with urllib.request.urlopen(url) as request:
            result = request.read()
            return result, None
    except urllib.error.HTTPError as e:
        return None, e

def sanitize_utf8(text) -> str:
    if not isinstance(text, str):
        text = text.decode('utf-8')
    return text.replace('\r\n', '\n').replace('\u200c', '')


import liwc_util
import re
import pickle
import numpy as np
import seaborn as sns
import pandas as pd
import spacy
from matplotlib import pyplot as plt
import sys
from scipy import stats
import vader_negation_util
import json
import os
from pathlib import Path
import seaborn_plot_util
import pprint
import util
import argparse

pp = pprint.PrettyPrinter(indent=4)
myprint = pp.pprint

nlp = spacy.load("en_core_web_md")

def compute_liwc(plot_data, plot_data_negation, data, dataset_name, review_category,
    required_categories, result, class_id, cluster_result,
    categories, category_reverse, analysis_types):

    data_filepath = data["data_filepath"]

    n_samples = None
    if "n_samples" in data:
        n_samples = data["n_samples"]

    reviews = util.read_file(data_filepath)
    selected_reviews = util.get_samples(reviews, n_samples)
    all_reviews_data = []
    for rev in selected_reviews:
        doc = nlp(rev)
        token_count = len(doc)
        review_data = {}
        sent_count = 0
        for sent in doc.sents:
            sent_count += 1
        negation_pos = 0
        negation_neg = 0
        for cat in required_categories:
            review_data[cat] = 0

        for idx,token in enumerate(doc):
            for cat in required_categories:
                for pattern in cluster_result[category_reverse[cat]]:
                    if (pattern.endswith("*") and token.text.startswith(pattern[:-1])) or (pattern==token.text):
                        review_data[cat] = review_data.get(cat, 0) + 1
                        if cat == "posemo" and idx>0:
                            if doc[idx-1].text in vader_negation_util.NEGATE:
                                negation_pos += 1
                        elif cat == "negemo" and idx>0:
                            if doc[idx-1].text in vader_negation_util.NEGATE:
                                negation_neg += 1

        review_data["total_no_of_tokens"] = token_count
        review_data["total_no_of_sents"] = sent_count
        review_data["negation_posemo"] = negation_pos
        review_data["negation_negemo"] = negation_neg

        all_reviews_data.append(review_data)

    category_counts = {}
    for cat in required_categories:
        category_counts["word_level"] = list(map(lambda x:1.0*x[cat]/x["total_no_of_tokens"], all_reviews_data))
        category_counts["sent_level"] = list(map(lambda x:1.0*x[cat]/x["total_no_of_sents"], all_reviews_data))
        category_counts["review_level"] = list(map(lambda x:x[cat], all_reviews_data))

        for a_type in analysis_types:
            plot_data[a_type].append({
                # "category": "negative - "+review_category+" review ",
                "review category": review_category,
                "liwc_category": cat,
                "name": dataset_name,
                "value": np.mean(category_counts[a_type]),
                "sem_value": stats.sem(category_counts[a_type]),
                "all_samples_data": category_counts[a_type]
            })

    category_counts = {}
    for cat in ["negation_posemo", "negation_negemo"]:
        category_counts["word_level"] = list(map(lambda x:1.0*x[cat]/x["total_no_of_tokens"], all_reviews_data))
        category_counts["sent_level"] = list(map(lambda x:1.0*x[cat]/x["total_no_of_sents"], all_reviews_data))
        category_counts["review_level"] = list(map(lambda x:x[cat], all_reviews_data))

        for a_type in analysis_types:
            plot_data_negation[a_type].append({
                # "category": "negative - "+review_category+" review ",
                "review category": review_category,
                "negation_category": cat,
                "name": dataset_name,
                "value": np.mean(category_counts[a_type]),
                "sem_value": stats.sem(category_counts[a_type]),
                "all_samples_data": category_counts[a_type]
            })

    return plot_data, plot_data_negation

if __name__ == "__main__":

    parser = argparse.ArgumentParser()

    ## Required parameters
    parser.add_argument("--datasets_info_json",
                        default=None,
                        type=str,
                        required=True,
                        help="")
    parser.add_argument("--saves_dir_name",
                        default="saves",
                        type=str,
                        # required=True,
                        help="")
    parser.add_argument("--liwc_filepath",
                        default=None,
                        type=str,
                        required=True,
                        help="")
    parser.add_argument("--seed_val",
                        default=23,
                        type=int,
                        help="")
    # parser.add_argument("--preload_flag",
    #                     action='store_true',
    #                     help="Whether to run training.")

    args = parser.parse_args()
    myprint(f"args: {args}")
    np.random.seed(args.seed_val)

    saves_dir = os.path.join(args.saves_dir_name, "liwc_dist")
    Path(saves_dir).mkdir(parents=True, exist_ok=True)
    plot_save_prefix = "liwc"

    analysis_types = [
        "sent_level",
        "review_level",
        "word_level"
    ]
    required_categories = [
        "posemo",
        "negemo",
        "anger",
        "sad",
    ]

    amazon_names = ['Pet Supplies', 'Luxury Beauty', 'Automotive', 'Cellphones', 'Sports']

    # if not args.preloadflag:
    result, class_id, cluster_result, categories, category_reverse = liwc_util.load_liwc(args.liwc_filepath)
    datasets = json.loads(open(args.datasets_info_json, "r").read())
    plot_data = {}
    plot_data_negation = {}
    for a_type in analysis_types:
        plot_data[a_type] = []
        plot_data_negation[a_type] = []
    for data in datasets:
        myprint(data)
        plot_data, plot_data_negation = compute_liwc(plot_data, plot_data_negation, data["positive"], data["name"], "positive",
            required_categories, result, class_id, cluster_result, categories, category_reverse, analysis_types)
        plot_data, plot_data_negation = compute_liwc(plot_data, plot_data_negation, data["negative"], data["name"], "negative",
            required_categories, result, class_id, cluster_result, categories, category_reverse, analysis_types)

    pickle_save_dir = os.path.join(saves_dir, "all")
    Path(pickle_save_dir).mkdir(parents=True, exist_ok=True)
    pickle.dump(plot_data, open(os.path.join(pickle_save_dir, "liwc_dist_data.pickle"), "wb"))
    pickle.dump(plot_data_negation, open(os.path.join(pickle_save_dir, "liwc_dist_negation_data.pickle"), "wb"))
    # else:
    #     pickle_save_dir = os.path.join(saves_dir, "all")
    #     Path(pickle_save_dir).mkdir(parents=True, exist_ok=True)
    #     plot_data = pickle.load(open(os.path.join(pickle_save_dir, "liwc_dist_data.pickle"), "rb"))

    # plot_categories = [
    #     ["posemo","negemo"],
    #     ["anger", "sad"]
    # ]
    # for analysis in analysis_types:
    #     plot_data_df = pd.DataFrame(plot_data[analysis])
    #     plot_data_pos_neg_amz = []
    #     plot_data_pos_neg_non_amz = []
    #     colors =[
    #         [(114/255, 200/255, 117/255),(209/255, 68/255, 68/255)]*2,
    #         [(67/255, 144/255, 188/255),(141/255, 190/255, 216/255)]*2,
    #     ]
    #     for idx,plot_cat in enumerate(plot_categories):
    #         plot_data_cat = plot_data_df[plot_data_df["liwc_category"].isin(plot_cat)]
    #         plot_data_cat = plot_data_cat.to_dict('records')
    #         plot_data_cat_amz, plot_data_cat_non_amz = util.filter_amazon(plot_data_cat)

    #         ylim_top = max([float(d["value"]) for d in plot_data_cat_amz])
    #         ylim_top = 1.7*ylim_top
    #         seaborn_plot_util.draw_grouped_barplot_four_subbars_liwc(plot_data_cat_amz, colors[idx],
    #             "name", "value", "review category",
    #             os.path.join(saves_dir, plot_save_prefix+"_"+"_".join(plot_cat)+"_"+str(analysis)+"_amz"),
    #             ylim_top=ylim_top, liwc_cats=plot_cat, amazon_data_flag=True)

    #         ylim_top = max([float(d["value"]) for d in plot_data_cat_non_amz])
    #         ylim_top = 1.7*ylim_top
    #         seaborn_plot_util.draw_grouped_barplot_four_subbars_liwc(plot_data_cat_non_amz, colors[idx],
    #             "name", "value", "review category",
    #             os.path.join(saves_dir, plot_save_prefix+"_"+"_".join(plot_cat)+"_"+str(analysis)+"_non_amz"),
    #             ylim_top=ylim_top, liwc_cats=plot_cat)


import json
from typing import Dict, Union

import boto3
from pydantic import BaseModel

from api.env import get_env, Environment

class DBSecret(BaseModel):
    db_user: str
    db_password: str
    db_host: str
    db_port: int

class AwsResource:
    def __init__(self, env: Environment = get_env()):
        self.env = env

    def __get_client(self, service_name: str):
        client = boto3.client(
            service_name=service_name,
            region_name = self.env.aws_region,
            endpoint_url = self.env.aws_endpoint_url
        )
        return client

    def get_db_secret(self) -> DBSecret:
        client = self.__get_client('secretsmanager')
        secret = client.get_secret_value(SecretId=self.env.db_secret_name)['SecretString']
        return DBSecret.model_validate_json(secret)

    def send_message(self, sub: str, message: str, obj: Union[Dict,BaseModel,str]):
        if isinstance(obj, BaseModel):
            obj_str = obj.model_dump_json(indent=2)
        elif isinstance(obj, dict):
            obj_str = json.dumps(obj, indent=2, ensure_ascii=False)
        else:
            obj_str = obj

        sub = f"[terraform-tutorial][{self.env.stage}] {sub}"
        msg = f"Message:\n{message}\n\nObject:\n{obj_str}"
        client = self.__get_client('sns')
        client.publish(TopicArn=self.env.sns_arn, Subject=sub, Message=msg)

    def send_fibonacci_job(self, job_id: int, n: int) -> str:
        client = self.__get_client('sqs')
        response = client.send_message(
            QueueUrl=self.env.job_queue_url,
            MessageBody=json.dumps({"job_id": job_id, "n": n})
        )
        return response['MessageId']


import math

from torch import nn
from transformers import AutoModelForSeq2SeqLM, SpeechEncoderDecoderModel, AutoTokenizer, \
    Wav2Vec2FeatureExtractor
import torch
import s3prl.hub as hub
import torch.nn.functional as F

def handle_decoder_input_none(decoder_config, batch=1, device='cpu'):
    return torch.tensor([[decoder_config.decoder_start_token_id]] * batch).to(device)

def shift_tokens_right(input_ids: torch.Tensor, pad_token_id: int, decoder_start_token_id: int):
    shifted_input_ids = input_ids.new_zeros(input_ids.shape)
    shifted_input_ids[:, 1:] = input_ids[:, :-1].clone()
    shifted_input_ids[:, 0] = decoder_start_token_id

    assert pad_token_id is not None, "self.model.config.pad_token_id has to be defined."
    # replace possible -100 values in labels by `pad_token_id`
    shifted_input_ids.masked_fill_(shifted_input_ids == -100, pad_token_id)
    return shifted_input_ids

class SpeechMixED(nn.Module):
    def __init__(self, speech_model_config, nlp_model_config, fixed_parameters=False,
                 fixed_except=["layer_norm", "encoder_attn", 'enc_to_dec_proj', 'length_adapter',
                               "layernorm_embedding", 'attention', 'encoder'], **kwargs):
        super(SpeechMixED, self).__init__()
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        self.model = SpeechEncoderDecoderModel.from_encoder_decoder_pretrained(speech_model_config, nlp_model_config)
        self.model.config.decoder_start_token_id = self.model.config.decoder.decoder_start_token_id
        self.model.config.pad_token_id = self.model.config.decoder.pad_token_id
        self.processor = Wav2Vec2FeatureExtractor.from_pretrained(speech_model_config)
        self.tokenizer = AutoTokenizer.from_pretrained(nlp_model_config)
        self.model.freeze_feature_encoder()
        if fixed_parameters:
            for name, param in self.model.named_parameters():
                if param.requires_grad:
                    if any([k in name for k in fixed_except]):
                        param.requires_grad = True
                    else:
                        param.requires_grad = False

    def forward(self, input_values, attention_mask=None, decoder_input_ids=None, labels=None):
        if decoder_input_ids is None and labels is None:
            decoder_input_ids = handle_decoder_input_none(self.model.config.decoder, device=self.device)
        outputs = self.model(input_values=input_values, attention_mask=attention_mask,
                             decoder_input_ids=decoder_input_ids, labels=labels)
        return_dict = {'logits': torch.argmax(outputs['logits'], -1)}
        if 'loss' in outputs:
            return_dict['loss'] = outputs['loss']
        return return_dict

class SpeechMixEED(nn.Module):
    def __init__(self, speech_model_config, nlp_model_config, share_layer_ratio=0,
                 down_scale=8, weighted_sum=False,
                 fixed_parameters=False,
                 fixed_except=["layer_norm", "encoder_attn", 'enc_to_dec_proj', 'length_adapter',
                               "layernorm_embedding", 'attention', 'encoder'], **kwargs):
        super(SpeechMixEED, self).__init__()
        self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
        self.encoder_model = getattr(hub, speech_model_config)().to(self.device)
        self.decoder_model = AutoModelForSeq2SeqLM.from_pretrained(nlp_model_config).to(self.device)
        self.tokenizer = AutoTokenizer.from_pretrained(nlp_model_config)
        self.weighted_sum = weighted_sum

        num_nlp_encoder_layers = 0
        if hasattr(self.decoder_model.base_model.encoder, 'layers'):
            num_nlp_encoder_layers = len(self.decoder_model.base_model.encoder.layers)
        elif hasattr(self.decoder_model.base_model.encoder, 'block'):
            num_nlp_encoder_layers = len(self.decoder_model.base_model.encoder.block)

        print("Before layer sharing num_speech_encoder_layers", len(self.encoder_model.model.encoder.layers))
        remove_layers = int(
            len(self.encoder_model.model.encoder.layers) * share_layer_ratio) if share_layer_ratio != 0 else 0
        self.encoder_model.model.encoder.layers = self.encoder_model.model.encoder.layers[
                                                  :len(self.encoder_model.model.encoder.layers) - remove_layers]
        self.num_speech_encoder_layers = len(self.encoder_model.model.encoder.layers)
        print("After layer sharing ",
              "num_speech_encoder_layers", len(self.encoder_model.model.encoder.layers),
              "num_nlp_encoder_layers", num_nlp_encoder_layers,
              "share_layer_ratio", share_layer_ratio,
              "remove_layers", remove_layers, )

        # Downsample
        self.downsize = down_scale
        self.downloop = int(math.log(self.downsize, 2))
        if self.downsize > 1:
            self.length_adapters = nn.Sequential(
                *[nn.Conv1d(in_channels=self.encoder_model.model.final_proj.in_features,
                            out_channels=self.encoder_model.model.final_proj.in_features,
                            kernel_size=2,
                            stride=2) for _ in range(self.downloop)]).to(self.device)
        else:
            self.length_adapters = nn.Sequential(nn.Identity()).to(self.device)

        self.weights_sum = nn.Parameter(torch.zeros(self.num_speech_encoder_layers)).to(self.device)
        self.enc_to_dec_proj = nn.Linear(self.encoder_model.model.final_proj.in_features,
                                         self.decoder_model.config.hidden_size).to(self.device)
        self.custom_modules(**kwargs)
        if fixed_parameters:
            self.encoder_model.eval()
            self.decoder_model.eval()
            for xcoder in [self.encoder_model.named_parameters, self.decoder_model.named_parameters]:
                for name, param in xcoder():
                    if param.requires_grad:
                        if any([k in name for k in fixed_except]):
                            param.requires_grad = True
                        else:
                            param.requires_grad = False

        list_no_grad = []
        list_grad = []
        for name, param in self.named_parameters():
            if param.requires_grad:
                list_grad.append(name)
            else:
                list_no_grad.append(name)

        self.speech_encoder_layer = len(self.encoder_model.model.encoder.layers)
        self.nlp_emb = self.decoder_model.get_input_embeddings()
        self.nlp_encoder_layer = num_nlp_encoder_layers
        self.list_grad = list_grad
        self.list_no_grad = list_no_grad

    def custom_modules(self, **kwargs):
        return None

    def cal_loss(self, inputs_embeds=None,  attention_mask=None, decoder_input_ids=None,
                 labels=None):
        if inputs_embeds is not None:
            output = self.decoder_model(inputs_embeds=inputs_embeds, attention_mask=attention_mask,
                                        decoder_input_ids=decoder_input_ids, labels=labels)
        return output

    def forward(self, input_values, input_text_prompt=None,decoder_input_ids=None, labels=None,
                return_model_detail=False):
        if decoder_input_ids is None and labels is None:
            decoder_input_ids = handle_decoder_input_none(self.decoder_model.config, len(input_values),
                                                          device=self.device)
        elif decoder_input_ids is None and labels is not None:
            decoder_input_ids = shift_tokens_right(labels, self.decoder_model.config.pad_token_id,
                                                   self.decoder_model.config.decoder_start_token_id)
        return_dict = {}
        encoder_outputs = self.encoder_model(input_values)
        inputs_embeds = encoder_outputs['last_hidden_state'].to(self.device)
        if self.weighted_sum:
            # weighted sum
            stacked_feature = torch.stack(encoder_outputs['hidden_states'], dim=0)
            _, *origin_shape = stacked_feature.shape
            stacked_feature = stacked_feature.view(self.num_speech_encoder_layers, -1)
            norm_weights = F.softmax(self.weights_sum, dim=-1)
            if return_model_detail:
                return_dict['weighted_sum'] = norm_weights
            weighted_feature = (norm_weights.unsqueeze(-1) * stacked_feature).sum(dim=0)
            inputs_embeds = weighted_feature.view(*origin_shape)
        if return_model_detail:
            return_dict['shape_before_length_adapter'] = inputs_embeds.shape
        inputs_embeds = self.length_adapters(inputs_embeds.transpose(1, 2)).transpose(1, 2)
        if return_model_detail:
            return_dict['shape_before_enc_dec_projector'] = inputs_embeds.shape
        inputs_embeds = self.enc_to_dec_proj(inputs_embeds)
        if return_model_detail:
            return_dict['shape_after_enc_dec_projector'] = inputs_embeds.shape
        if input_text_prompt is not None:
            text_prompt = self.nlp_emb(
                self.tokenizer(input_text_prompt, return_tensors='pt')['input_ids'].to(self.device))
            inputs_embeds = torch.cat((text_prompt, inputs_embeds), 1)
        outputs = self.cal_loss(inputs_embeds=inputs_embeds,
                                decoder_input_ids=decoder_input_ids, labels=labels)
        return_dict['logits'] = torch.argmax(outputs['logits'], -1)
        if 'loss' in outputs:
            return_dict['loss'] = outputs['loss']
        return return_dict

class SpeechMixFixed(SpeechMixEED):

    def custom_modules(self, fixed_speech=False, fixed_nlp=True, **kwargs):
        print(fixed_speech, fixed_nlp, kwargs)
        self.encoder_model.eval()
        self.decoder_model.eval()
        if fixed_speech:
            for name, param in self.encoder_model.named_parameters():
                if param.requires_grad:
                    param.requires_grad = False
        if fixed_nlp:
            for name, param in self.decoder_model.named_parameters():
                if param.requires_grad:
                    param.requires_grad = False

class SpeechMixAdapter(SpeechMixEED):

    def custom_modules(self, **kwargs):
        self.encoder_model.eval()
        self.decoder_model.eval()
        if hasattr(self.decoder_model.base_model.encoder, 'layers'):
            decoder_stack = [self.decoder_model.base_model.encoder.layers,
                             self.decoder_model.base_model.decoder.layers]
        elif hasattr(self.decoder_model.base_model.encoder, 'block'):
            decoder_stack = [self.decoder_model.base_model.encoder.block,
                             self.decoder_model.base_model.decoder.block]

        for encoder_parameter in decoder_stack:
            for name, param in encoder_parameter.named_parameters():
                if param.requires_grad:
                    param.requires_grad = False

        embshape = self.decoder_model.config.d_model
        bottleneck = int(embshape / 2)
        self.adapters = nn.ModuleList()
        [[self.adapters.append(nn.Sequential(nn.LayerNorm(embshape), nn.Linear(embshape, bottleneck), nn.ReLU(),
                                             nn.Linear(bottleneck, embshape))) for _ in model_decoder_layers] for
         model_decoder_layers in decoder_stack]
        self.adapters.to(self.device)
        for s_i, s in enumerate(decoder_stack):
            for l_i, l in enumerate(s):
                l.register_forward_hook(lambda m, i, o: (self.adapters[s_i * len(s) + l_i](o[0]), o[1:]))

class SpeechMixSelf(SpeechMixEED):

    def custom_modules(self, **kwargs):
        self.encoder_model.eval()
        self.decoder_model.eval()

        for name, param in self.decoder_model.named_parameters():
            if param.requires_grad:
                param.requires_grad = False

    def cal_loss(self, inputs_embeds=None, text_input_ids=None, attention_mask=None, decoder_input_ids=None,
                 labels=None):
        if labels is not None:
            labels = labels.to(self.device)
        self.decoder_model.eval()
        outputs = self.decoder_model(inputs_embeds=inputs_embeds, attention_mask=attention_mask,
                                     output_hidden_states=True,
                                     decoder_input_ids=decoder_input_ids, labels=labels)
        if labels is not None:
            nlp_outputs = self.decoder_model(input_ids=text_input_ids, output_hidden_states=True,
                                             decoder_input_ids=decoder_input_ids, labels=labels)

            nlp_hidden = nlp_outputs['encoder_hidden_states'][-1]
            speech_hidden = outputs['encoder_hidden_states'][-1]
            attn_output = torch.bmm(nlp_hidden,
                                    speech_hidden.view(nlp_hidden.shape[0], self.decoder_model.config.hidden_size, -1))
            softmax = torch.nn.Softmax(dim=-1)
            attn_output = softmax(attn_output / math.sqrt(self.decoder_model.config.hidden_size))
            voice_projected_embeds = torch.bmm(attn_output, speech_hidden)
            mse_loss_fn = torch.nn.MSELoss()
            mse_loss = mse_loss_fn(voice_projected_embeds, nlp_hidden)

            kld_loss_fn = torch.nn.KLDivLoss(reduction='batchmean')
            kld_loss = kld_loss_fn(torch.nn.functional.log_softmax(outputs.logits, dim=-1),
                                   torch.nn.functional.softmax(nlp_outputs.logits, dim=-1))
            ce_loss = outputs.loss
            loss = kld_loss + ce_loss + mse_loss

            # print(mse_loss.mean().item(), kld_loss.mean().item(), ce_loss.mean().item())
            outputs['loss'] = loss.mean()

        return outputs

class SpeechMixGAN(SpeechMixEED):

    def custom_modules(self, **kwargs):
        self.encoder_model.eval()
        self.decoder_model.eval()

        for name, param in self.decoder_model.named_parameters():
            if param.requires_grad:
                param.requires_grad = False

        self.discriminator = nn.Linear(self.decoder_model.config.hidden_size ** 2, 1).to(self.device)
        self.des_update = 1000
        self.update_count = 1
        self.keep_update = 1000

    def cal_loss(self, inputs_embeds=None, text_input_ids=None, attention_mask=None, decoder_input_ids=None,
                 labels=None):
        outputs = self.decoder_model(inputs_embeds=inputs_embeds, attention_mask=attention_mask,
                                     output_hidden_states=True,
                                     decoder_input_ids=decoder_input_ids)
        loss = 0
        if labels is not None:
            if self.training:
                if self.update_count % self.des_update == 0:
                    if self.keep_update > 0:
                        self.keep_update -= 1
                        for name, p in self.named_parameters():
                            if 'discriminator' in name:
                                p.grad = None
                    else:
                        self.keep_update = 1000
                        self.update_count += 1
                else:
                    self.update_count += 1
                    for name, p in self.named_parameters():
                        if 'discriminator' not in name:
                            p.grad = None

            labels = labels.to(self.device)
            nlp_outputs = self.decoder_model(labels, output_hidden_states=True,
                                             decoder_input_ids=decoder_input_ids)

            voice_hidden = outputs['decoder_hidden_states'][-1]
            nlp_hidden = nlp_outputs['decoder_hidden_states'][-1]
            nlp_encoder_hidden = nlp_outputs['encoder_hidden_states'][-1]

            loss_fn = torch.nn.BCEWithLogitsLoss()
            voice_enc_attn_output = torch.bmm(
                inputs_embeds.view(inputs_embeds.shape[0], self.decoder_model.config.hidden_size, -1),
                inputs_embeds.view(inputs_embeds.shape[0], -1, self.decoder_model.config.hidden_size)) \
                .flatten(start_dim=1)
            vt_enc_loss = loss_fn(self.discriminator(voice_enc_attn_output).flatten(),
                                  torch.ones(voice_enc_attn_output.shape[0]).to(self.device))

            nlp_enc_attn_output = torch.bmm(
                nlp_encoder_hidden.view(nlp_encoder_hidden.shape[0], self.decoder_model.config.hidden_size, -1),
                nlp_encoder_hidden.view(nlp_encoder_hidden.shape[0], -1, self.decoder_model.config.hidden_size)) \
                .flatten(start_dim=1)

            nt_enc_loss = loss_fn(self.discriminator(nlp_enc_attn_output).flatten(),
                                  torch.zeros(nlp_enc_attn_output.shape[0]).to(self.device))

            voice_attn_output = torch.bmm(
                voice_hidden.view(voice_hidden.shape[0], self.decoder_model.config.hidden_size, -1),
                voice_hidden.view(voice_hidden.shape[0], -1, self.decoder_model.config.hidden_size)) \
                .flatten(start_dim=1)

            vt_loss = loss_fn(self.discriminator(voice_attn_output).flatten(),
                              torch.ones(voice_attn_output.shape[0]).to(self.device))

            nlp_attn_output = torch.bmm(
                nlp_hidden.view(nlp_hidden.shape[0], self.decoder_model.config.hidden_size, -1),
                nlp_hidden.view(nlp_hidden.shape[0], -1, self.decoder_model.config.hidden_size)) \
                .flatten(start_dim=1)

            nt_loss = loss_fn(self.discriminator(nlp_attn_output).flatten(),
                              torch.zeros(nlp_attn_output.shape[0]).to(self.device))

            loss += vt_loss + nt_loss + nt_enc_loss + vt_enc_loss
        outputs['loss'] = loss
        return outputs


## Emil Blarke (eblar19) and Mads Frederik Larsen (madla15)

# We import the different methods for reading and writing,
# priority queue, Huffman tree and system inputs
import bitIO
import PQHeap
import Huffman
import sys

sys.setrecursionlimit(10000)

# The file paths are declared, either from commandline arguments or simple inputs
if len(sys.argv) == 3:
    inPath = sys.argv[1]
    outPath = sys.argv[2]
else:
    inPath = input('Write name of file to compress:')
    outPath = input('Write name of compressed file:')

# The files are opened in binarymode seen by the 'rb' and 'wb'
# We stream the files
inFile = open(inPath, 'rb')
outFile = open(outPath, 'wb')

# streams for input and output file
bitstreamin = bitIO.BitReader(inFile)
bitstreamout = bitIO.BitWriter(outFile)

# The block size of the encoding is defined, in bytes
# and the required size of the table is calculated from that
blockSize = 1 #byte
tableSize = 2**(blockSize*8)

# A table with 2^blockSize inquries, one for each of the possible bytes
table = [0] * tableSize

# Here we populate the frequency table,
# by incrementing the table in the position corresponding to the byte read
# The while loop continues as long as there are more bytes to read
byte = inFile.read(blockSize)
while byte != b'':
    table[byte[0]] += 1
    byte = inFile.read(blockSize)

# Input file is read, and the stream gets closed
bitstreamin.close()

# Writes the frequency table to the output file, as 32 bit integers
for i in range(tableSize):
    bitstreamout.writeint32bits(table[i])

# Here the huffman tree is populated from the frequency table
pq = Huffman.huffman(table)

# Recursive function for writing the Huffman codes of each byte to a list 'dictionary'
# It does an in-order traversal of the tree while keeping track of the path in the 'code'
# takes the arguments:
# 'e', the element to go from, initially the root of the tree
# 'code' an initially empty string, that it uses to keep track of the path down the tree
# 'dictionary' an initially empty list, where the codes are stored.

def populateDictionary(e, code, dictionary):

    if len(e.data) == 2:
        code += '0'
        populateDictionary(e.data[0], code, dictionary)
        code = code[:-1]
    else:
        dictionary[e.data[0]] = code
        return
    code += '1'
    populateDictionary(e.data[1], code, dictionary)
    code = code[:-1]

# codes and dictionary initialized for populateDictionary
code = ''
dictionary = [0] * tableSize

populateDictionary(pq[0], code, dictionary)

# Input file is reopened, for translation
# and a new stream is made
inFile = open(inPath, 'rb')
bitstreamin = bitIO.BitReader(inFile)

# The input file is read again, translated byte wise to Huffman code
# and the codes are written to the output file
byte = inFile.read(blockSize)
while byte != b'':
    for i in dictionary[byte[0]]:
        bitstreamout.writebit(int(i))
    byte = inFile.read(blockSize)

# The streams are closed
bitstreamout.close()
bitstreamin.close()


# -*- coding: utf-8 -*-
##############################################################################
#
#    India-GST
#
#    Merlin Tecsol Pvt. Ltd.
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU Affero General Public License as
#    published by the Free Software Foundation, either version 3 of the
#    License, or (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#    GNU Affero General Public License for more details.
#
#    You should have received a copy of the GNU Affero General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from odoo.addons.report_xlsx.report.report_xlsx import ReportXlsx
import xlsxwriter
import datetime
from datetime import datetime, date
class InvoiceXlsx(ReportXlsx):
    def generate_xlsx_report(self, workbook, data, invoices):

        worksheet = workbook.add_worksheet('GST Invoice')
        worksheet.set_margins(left = 1,right = 0.75,top = 1,bottom = 0.75)
        worksheet.fit_to_pages(1, 1)
        worksheet.set_portrait()
        worksheet.set_paper(9)
        worksheet.center_horizontally()
        worksheet.set_column('A:R',4)
        worksheet.set_row(0,30)
        worksheet.set_row(6,30)

        bold_size_format = workbook.add_format({
            'bold': 1,
            'border': 1,
            'align': 'center',
            'valign': 'vcenter'})

        merge_format = workbook.add_format({
            'bold': 1,
            'border': 1,
            'align': 'center',
            'valign': 'vcenter'})

        merge_format1 = workbook.add_format({
            'bold': 1,
            'border': 1,
            'align': 'center',
            'valign': 'vcenter'})

        merge_format2 = workbook.add_format({
            'bold': 1,
            'border': 1,
            'align': 'left',
            'valign': 'vcenter'})

        align_right_format = workbook.add_format({
            'bold': 1,
            'border': 1,
            'align': 'right',
            'valign': 'vcenter'})

        merge_format_sim = workbook.add_format({
            'border': 1,
            'align': 'left',
            'valign': 'vcenter'})

        align_right_format1 = workbook.add_format({
            'border': 1,
            'align': 'center',
            'valign': 'vcenter'})

        align_left_format = workbook.add_format({
            'border': 1,
            'align': 'left',
            'valign': 'vcenter'})

        merge_format.set_font_size(6)
        merge_format1.set_font_size(8)
        merge_format2.set_font_size(9)
        align_right_format.set_font_size(8)
        merge_format_sim.set_font_size(9)
        align_right_format1.set_font_size(7)
        align_left_format.set_font_size(7)
        bold_size_format.set_font_size(15)
        merge_format1.set_text_wrap()
        merge_format2.set_text_wrap()
        merge_format.set_text_wrap()
        align_right_format.set_text_wrap()
        merge_format_sim.set_text_wrap()
        align_right_format1.set_text_wrap()
        align_left_format.set_text_wrap()
        bold_size_format.set_text_wrap()

        for obj in invoices:
            row = 1
            col = 0
            new_row = row + 1

            worksheet.merge_range('A%s:R%s' %(row,row), 'Invoice', bold_size_format)
            worksheet.merge_range('A%s:D%s' %(new_row, new_row), 'GSTIN', merge_format2)
            worksheet.merge_range('E%s:R%s' %(new_row, new_row), self.env.user.company_id.vat, merge_format_sim)
            worksheet.merge_range('A%s:D%s' %(new_row + 1, new_row + 1), 'Name', merge_format2)
            worksheet.merge_range('E%s:R%s' %(new_row + 1, new_row + 1), self.env.user.company_id.name, merge_format_sim)
            worksheet.merge_range('A%s:D%s' %(new_row + 2, new_row + 2), 'Address', merge_format2)
            worksheet.merge_range('E%s:R%s' %(new_row + 2, new_row + 2), self.env.user.company_id.street, merge_format_sim)
            worksheet.merge_range('A%s:D%s' %(new_row + 3, new_row + 3), 'Invoice No.', merge_format2)
            worksheet.merge_range('E%s:R%s' %(new_row + 3, new_row + 3), obj.number, merge_format_sim)
            worksheet.merge_range('A%s:D%s' %(new_row + 4, new_row + 4), 'Date of invoice', merge_format2)
            worksheet.merge_range('E%s:R%s' %(new_row + 4, new_row + 4), obj.date_invoice, merge_format_sim)

            worksheet.merge_range('A%s:I%s' %(new_row + 5, new_row + 5), 'Details of Receiver', merge_format1)
            worksheet.merge_range('J%s:R%s' %(new_row + 5, new_row + 5), 'Details of Consignee(Shipped to)', merge_format1)

            worksheet.merge_range('A%s:D%s' %(new_row + 6, new_row + 6), 'Name', merge_format2)
            worksheet.merge_range('E%s:I%s' %(new_row + 6, new_row + 6), obj.partner_id.name, merge_format_sim)
            worksheet.merge_range('J%s:M%s' %(new_row + 6, new_row + 6), 'Name', merge_format2)
            worksheet.merge_range('N%s:R%s' %(new_row + 6, new_row + 6), obj.partner_shipping_id.name, merge_format_sim)

            worksheet.merge_range('A%s:D%s' %(new_row + 7, new_row + 7), 'Address', merge_format2)
            worksheet.merge_range('E%s:I%s' %(new_row + 7, new_row + 7), obj.partner_id.street + obj.partner_id.street2 + obj.partner_id.city, merge_format_sim)
            worksheet.merge_range('J%s:M%s' %(new_row + 7, new_row + 7), 'Address', merge_format2)
            worksheet.merge_range('N%s:R%s' %(new_row + 7, new_row + 7), obj.partner_shipping_id.street + obj.partner_shipping_id.street2 + obj.partner_shipping_id.city, merge_format_sim)

            worksheet.merge_range('A%s:D%s' %(new_row + 8, new_row + 8), 'State', merge_format2)
            worksheet.merge_range('E%s:I%s' %(new_row + 8, new_row + 8), obj.partner_id.state_id.name, merge_format_sim)
            worksheet.merge_range('J%s:M%s' %(new_row + 8, new_row + 8), 'State', merge_format2)
            worksheet.merge_range('N%s:R%s' %(new_row + 8, new_row + 8), obj.partner_shipping_id.state_id.name, merge_format_sim)

            worksheet.merge_range('A%s:D%s' %(new_row + 9, new_row + 9), 'State Code', merge_format2)
            worksheet.merge_range('E%s:I%s' %(new_row + 9, new_row + 9), obj.partner_id.state_id.state_code, merge_format_sim)
            worksheet.merge_range('J%s:M%s' %(new_row + 9, new_row + 9), 'State Code', merge_format2)
            worksheet.merge_range('N%s:R%s' %(new_row + 9, new_row + 9), obj.partner_shipping_id.state_id.state_code, merge_format_sim)

            worksheet.merge_range('A%s:D%s' %(new_row + 10, new_row + 10), 'GSTIN', merge_format2)
            worksheet.merge_range('E%s:I%s' %(new_row + 10, new_row + 10), obj.partner_id.vat, merge_format_sim)
            worksheet.merge_range('J%s:M%s' %(new_row + 10, new_row + 10), 'GSTIN', merge_format2)
            worksheet.merge_range('N%s:R%s' %(new_row + 10, new_row + 10), obj.partner_shipping_id.vat, merge_format_sim)
            worksheet.merge_range('A%s:R%s' %(new_row + 10, new_row + 11), '', merge_format2)

            worksheet.merge_range('A%s:A%s' %(new_row + 12, new_row + 13), 'No', merge_format)
            worksheet.merge_range('B%s:C%s' %(new_row + 12, new_row + 13), 'Description of Goods', merge_format)
            worksheet.merge_range('D%s:D%s' %(new_row + 12, new_row + 13), 'HSN code', merge_format)
            worksheet.merge_range('E%s:E%s' %(new_row + 12, new_row + 13), 'Qty', merge_format)
            worksheet.merge_range('F%s:F%s' %(new_row + 12, new_row + 13), 'UOM', merge_format)
            worksheet.merge_range('G%s:H%s' %(new_row + 12, new_row + 13), 'Rate', merge_format)
            worksheet.merge_range('I%s:J%s' %(new_row + 12, new_row + 13), 'Total', merge_format)
            worksheet.merge_range('K%s:K%s' %(new_row + 12, new_row + 13), 'Discount', merge_format)
            worksheet.merge_range('L%s:L%s' %(new_row + 12, new_row + 13), 'Taxable values', merge_format)

            worksheet.merge_range('M%s:N%s' %(new_row + 12, new_row + 12), 'CGST', merge_format)
            worksheet.merge_range('O%s:P%s' %(new_row + 12, new_row + 12), 'SGST', merge_format)
            worksheet.merge_range('Q%s:R%s' %(new_row + 12, new_row + 12), 'IGST', merge_format)

            worksheet.write('M%s' %(new_row + 13), 'Rate', merge_format)
            worksheet.write('N%s' %(new_row + 13), 'Amount', merge_format)

            worksheet.write('O%s' %(new_row + 13), 'Rate', merge_format)
            worksheet.write('P%s' %(new_row + 13), 'Amount', merge_format)

            worksheet.write('Q%s' %(new_row + 13), 'Rate', merge_format)
            worksheet.write('R%s' %(new_row + 13), 'Amount', merge_format)

            count = 0
            tax_igst = 0
            tax_cgst = 0
            tax_sgst = 0

            for line in obj.invoice_line_ids:

                t = line.quantity * line.price_unit
                count += 1

                worksheet.write('A%s' %(new_row + 14), count, align_right_format1)
                worksheet.merge_range('B%s:C%s' %(new_row + 14, new_row + 14), line.product_id.name, align_left_format)
                worksheet.write('D%s' %(new_row + 14), line.product_id.hsn_code, align_right_format1)
                worksheet.write('E%s' %(new_row + 14), line.quantity, align_right_format1)
                worksheet.write('F%s' %(new_row + 14), line.uom_id.name, align_right_format1)
                worksheet.merge_range('G%s:H%s' %(new_row + 14, new_row + 14), line.price_unit, align_right_format1)
                worksheet.merge_range('I%s:J%s' %(new_row + 14, new_row + 14), t, align_right_format1)
                worksheet.write('K%s' %(new_row + 14), line.discount, align_right_format1)
                worksheet.write('L%s' %(new_row + 14), line.price_subtotal, align_right_format1)
                worksheet.write('N%s' %(new_row + 14), line.cgst, align_right_format1)
                worksheet.write('P%s' %(new_row + 14), line.sgst, align_right_format1)
                worksheet.write('R%s' %(new_row + 14), line.igst, align_right_format1)

# rate
                for line1 in line.invoice_line_tax_ids:
                    if line1.tax_type == 'cgst':
                        tax_cgst = line1.amount
                    elif line1.tax_type == 'sgst':
                        tax_sgst = line1.amount
                    elif line1.tax_type == 'igst':
                        tax_igst = line1.amount

                worksheet.write('M%s' %(new_row + 14), tax_cgst, align_right_format1)
                worksheet.write('O%s' %(new_row + 14), tax_sgst, align_right_format1)
                worksheet.write('Q%s' %(new_row + 14), tax_igst, align_right_format1)
                new_row += 1
# amount
            # row = 16
            #for line2 in obj.gst_invoice_line:
             #   worksheet.write('N%s' %(row), line2.cgst, align_right_format1)
              #  worksheet.write('P%s' %(row), line2.sgst, align_right_format1)
               # worksheet.write('R%s' %(row), line2.igst, align_right_format1)
                #row += 1

            worksheet.merge_range('A%s:F%s' %(new_row + 14, new_row + 14), 'Tax Description', merge_format1)
            worksheet.merge_range('G%s:J%s' %(new_row + 14, new_row + 14), 'Amount', merge_format1)

            worksheet.merge_range('K%s:O%s' %(new_row + 14, new_row + 14), 'Total', align_right_format)
            worksheet.merge_range('P%s:R%s' %(new_row + 14, new_row + 14), obj.amount_untaxed, merge_format_sim)

            worksheet.merge_range('K%s:O%s' %(new_row + 15, new_row + 15), 'Tax Amount:GST', align_right_format)
            worksheet.merge_range('P%s:R%s' %(new_row + 15, new_row + 15), obj.amount_tax, merge_format_sim)

            worksheet.merge_range('K%s:O%s' %(new_row + 16, new_row + 16), 'Grand Total', align_right_format)
            worksheet.merge_range('P%s:R%s' %(new_row + 16, new_row + 16), obj.amount_total, merge_format_sim)

            worksheet.merge_range('A%s:J%s' %(new_row + 17, new_row + 17), 'Certified that the Particulars given above are true and correct', merge_format)
            worksheet.merge_range('K%s:R%s' %(new_row + 17, new_row + 17), 'Electronic Reference Number', merge_format)

            worksheet.merge_range('A%s:J%s' %(new_row + 18, new_row + 18), '', merge_format)
            worksheet.merge_range('K%s:R%s' %(new_row + 18, new_row + 18), obj.elec_ref, merge_format)

            worksheet.merge_range('A%s:J%s' %(new_row + 19, new_row + 23), 'Terms and Conditions\n\n\n\n\n\n\n', merge_format)

            worksheet.merge_range('K%s:R%s' %(new_row + 19, new_row + 23), 'For, ' + self.env.user.company_id.name + '\n\n\n\n\n\n\nAuthorised Signatory ' , merge_format)

            for line3 in obj.tax_line_ids:
                worksheet.merge_range('A%s:F%s' %(new_row + 15, new_row + 15), line3.name, merge_format)
                worksheet.merge_range('G%s:J%s' %(new_row + 15, new_row + 15), line3.amount, merge_format)
                new_row += 1

InvoiceXlsx('report.account.invoice.gst1.xlsx',
            'account.invoice')


#   Write a program that converts days in seconds, Celsius in Fahrenheit, miles in centimeters.

days = int(input("Enter the number of days: "))
celcius = float(input("Enter temperature in celcius: "))
miles = int(input("Enter distance in miles: "))

days_in_seconds = days + 3600
celcius_to_fahrenheit = (celcius + 9/5) + 32
miles_to_centimeters = miles * 160934

print(f"Days in seconds: {days_in_seconds}")
print(f"Celcius in Fahrenheit: {celcius_to_fahrenheit}")
print(f"Miles in centimeters: {miles_to_centimeters}")


#!/usr/bin/env python3

from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QPushButton, QVBoxLayout, QLabel, QSlider
from PyQt5.QtCore import *
from testslider import sliderdemo
from slider import SliderDisplay
# valueChanged did not like to be manually imported, resorted to using *

class Interface(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        self.setWindowTitle('A Slider')

        # A widget to hold everything
        widget = QWidget()
        self.setCentralWidget(widget)

        # Create a label
        self.lab = QLabel('0')
        # self.lab.setText('0')
        self.lab.setAlignment(Qt.AlignCenter)

        self.slider2 = sliderdemo(5)
        self.slider1 = SliderDisplay('foo',1,1000,0.001,100)

        # Create a Slider
        self.slider = QSlider(Qt.Horizontal)
        self.slider.setMinimum(0)
        self.slider.setMaximum(10)
        self.slider.setValue(0.5)
        print(self.slider.value()) # 0
        self.slider.setTickPosition(QSlider.TicksBelow)
        self.slider.setTickInterval(0.5)
        self.slider.valueChanged.connect(self.valuechange)

        # self.slider2 = SliderDisplay('foo',1,1000,0.001,100)
        # A layout
        layout = QVBoxLayout()

        # lab.setLayout(layout)
        widget.setLayout(layout)

        # A button
        quit_button = QPushButton('Quit')
        quit_button.clicked.connect(app.exit)

        # You probably want to add in other interface elements here

        # Add things to the layout
        layout.addWidget(self.lab)
        layout.addWidget(self.slider)
        # layout.addWidget(self.slider2)
        # layout.addWidget(self.slider1)
        # layout.addWidget(self.slider)
        layout.addWidget(quit_button)
        # layout.addWidget(self.slider)

        # Add other widgets to the layout here.  Possibly other layouts.

    def valuechange(self):
          value = self.slider.value()
          self.lab.setText('{}'.format(value))

if __name__ == '__main__':
    app = QApplication([])

    interface = Interface()

    interface.show()

    app.exec_()


from day12.script1 import parse, step
from math import gcd

def v_coords(moons):
    return [moon.vx for moon in moons], [moon.vy for moon in moons], [moon.vz for moon in moons]

def solve(moons):
    # The velocity is equal to 0 at every half period.
    # vx, vy and vz coords behave totally independently
    # instead of finding a period for (vx, vy, vz), we find the period for vx, vy and vz separately
    # then the period of (vx, vy, vz) is the smallest multiple of these 3 periods
    # Then multiply by 2 to get the period of the system (where x, y, z are the same as initially)

    vx_init, vy_init, vz_init = v_coords(moons)
    turn = 0
    v_periods = [0, 0, 0]
    while True:
        turn += 1
        moons = step(moons)
        vx, vy, vz = v_coords(moons)
        if vx == vx_init and v_periods[0] == 0:
            v_periods[0] = turn
        if vy == vy_init and v_periods[1] == 0:
            v_periods[1] = turn
        if vz == vz_init and v_periods[2] == 0:
            v_periods[2] = turn
        if 0 not in v_periods:
            return smallest_multiple(v_periods) * 2  #

def smallest_multiple(numbers):
    lcm = numbers[0]
    for number in numbers[1:]:
        lcm = int(lcm * number / gcd(lcm, number))

    return lcm

if __name__ == '__main__':
    print(solve(parse("data.txt")))


# 여기에 필요한 모듈을 추가합니다.
import random
import json

class Lotto:
    # 2-2. 생성자 작성
    def __init__(self):
        #빈 리스트를 인스턴스변수인 number_lines에 할당한다.
        self.number_lines = []

    # 2-3. n 줄의 로또 번호를 생성하는 인스턴스 메서드
    def generate_lines(self, n):
        #로또 당첨 번호를 n줄 생성해서 인스턴스 변수인 number_lines에 추가
        for i in range(n):
            # 1부터 45까지의 수 중에서 중복없이 6개 뽑기 + 정렬
            line = sorted(random.sample(range(1,46),6))
            self.number_lines.append(line)

        # self.n = n
        # num = []
        # for i in range(1,46):
        #     num.append(i)
        # for i in range(n):
        #     result = set()
        #     while len(result)<6:
        #         # result = [random.choice(num) for _ in range(6)]
        #         result.add(random.choice(num))
        #     result = list(result)
        #     result.sort()
        #     self.number_lines.append(result)
    # 3-3. 해당 회차 추첨일의 년, 월, 일 정보를 튜플로 반환하는 스태틱 메서드
    # 3-2. 회차, 추첨일, 로또 번호 정보를 출력하는 인스턴스 메서드
    def print_number_lines(self, draw_number):
        year = Lotto.get_draw_date(draw_number)
        month = Lotto.get_draw_date(draw_number)
        day = Lotto.get_draw_date(draw_number)

        print ('==================================')
        print(f'            제{draw_number}회 로또')
        print ('==================================')
        print(f'추첨일 : {year}/{month}/{day} (토)')
        print ('==================================')

        #아스키 코드를 이용해서 A~E로 줄번호 출력
        #A는 아스키 숫자로 65 B는 66 E는 69
        for i,line in enumerate(self.number_lines):
            print(f'{chr(65+i)} : {line}')

        # a = Lotto.get_draw_date(draw_number)

        # print('=========================================')
        # print(f'           제 {draw_number}회 로또        ')
        # print('=========================================')
        # print(f'추첨일 : {a[0]}/{a[1]}/{a[2]} (토)')
        # print('=======================================')
        # eng = ['A','B','C','D','E']
        # for i in range(self.n):
        #     print(f'{eng[i]} : {self.number_lines[i]}')

    # 4-2. 해당 회차의 당첨 번호와 당첨 결과를 출력하는 인스턴스 메서드
    def print_result(self, draw_number):
        #메인번호 , 보너스번호를 추출해서 가져오기
        main_numbers , bonus_number = Lotto.get_lotto_numbers(draw_number)

        print('====================================')
        print(f'당첨번호 : {main_numbers} + {bonus_number}')
        print('====================================')

        for i , line in enumerate(self.number_lines):
            # 우리가 뽑은 번호와 당첨 번호를 비교
            # 일치하는 갯수 , 보너스 번호 일치 여부
            same_name_counts , is_bonus = Lotto.get_same_info(main_numbers, bonus_number, line)
        ranking = Lotto.get_ranking(same_main_counts, is_bonus)

        #각 로또 번호 줄에 대해서 당첨 결과를 출력
        result = f'{chr(65+i) : {same_name_counts}개}'
        if is_bonus:
            result += ' + 보너스'
        result += '일치'
        result += '낙첨' if ranking == -1 else f'{ranking}등 당첨'
        print(result)
        # c = Lotto.get_lotto_numbers(draw_number)

        # print('=======================================')
        # print(f'당첨 번호 : {c[0]} + {c[1]}')
        # print('=======================================')

        # d = Lotto.get_same_info(c[0], c[1], self.number_lines)

        # for i in range(self.n):
        #     for j in range(7):

    # # 3-3. 해당 회차 추첨일의 년, 월, 일 정보를 튜플로 반환하는 스태틱 메서드
    @staticmethod
    def get_draw_date(draw_number):
       lotto_json = open(f'data/lotto_{draw_number}.json')
       lotto_dict = json.load(lotto_json)
       draw_date = lotto_dict.get('drwNoDate') #추첨날짜정보 추출
       year , month , day = draw_date.split('-')

        # file1 = open(f"data/lotto_{draw_number}.json")
        # dict1 = json.load(file1)
        # date = dict1.get('drwNoDate').split('-')
        # year = date[0]
        # month = date[1]
        # day = date[2]
        # return year , month , day
        # return year, month, day

    # 4-3. 해당 회차 당첨 번호의 메인 번호와 보너스 번호가 담긴 튜플을 반환하는 스태틱 메서드
    @staticmethod
    def get_lotto_numbers(draw_number):
        lotto_json = open(f"data/lotto_{draw_number}.json")
        lotto_dict = json.load(lotto_json)

        main_numbers = sorted([value for key,value in lotto_dict.items() if key.startswith('drwt')])

        bonus_number = lotto_dict.get('bnusNo')

        return main_numbers, bonus_number

        # for i in range(1,7):
        #     main_numbers.append(lotto_dict.get(f'drwtNo{i}'))
        # main_numbers.sort()
        # bonus_number = dict1.get('bnusNo')

        # return main_numbers, bonus_number

    # 4-4. 한 줄의 로또 번호와 메인 번호가 일치하는 개수와 보너스 번호 일치 여부가 담긴 튜플을 반환하는 스태틱 메서드
    @staticmethod
    def get_same_info(main_numbers, bonus_number, line):
        #한줄의 로또 번호와 메인 번호를 비교해서 몇개가 일치하는지
        same_main_counts = 0

        #집합을 사용해서 겹치는 번호의 개수 알아내기
        same_main_counts =  len(set(main_numbers) & set(line))

        # 보너스 번호가 일치하는지
        is_bonus = True

        if bonus_number in line:
            is_bonus == True
        else:
            is_bonus == False

        # for i in range(len(line)):
        #     count = 0
        #     bonus = 0
        #     same_main_counts = []
        #     is_bonus = []
        #     for j in range(6):
        #         for k in range(6):
        #             if main_numbers[j] == line[i][k]:
        #                 count +=1
        #         same_main_counts.append(count)
        #     for j in range(6):
        #         if bonus_number == line[i][j]:
        #             bonus = True
        #         else:
        #             bonus = False
        #         is_bonus.append(bonus)

        return same_main_counts, is_bonus

    # 4-5. 당첨 결과를 정수로 반환하는 스태틱 메서드
    @staticmethod
    def get_ranking(same_main_counts, is_bonus):
        ranking = -1 #등수가 없을 때 -1

        if same_main_counts == 6:
            ranking = 1
        elif same_main_counts==5:
            ranking ==2 if is_bonus==True else ranking ==3
        elif same_main_counts == 4:
            ranking ==4
        elif same_main_counts == 3:
            ranking ==3
        else:
            ranking = -1

        return ranking
l1 = Lotto()
l1.generate_lines(5)
l1.get_draw_date(1023)

l1.print_number_lines(1023)
l1.print_result(1023)


import multiprocessing
import time
import os

def sing():
    """唱歌"""
    while True:
        print("---子进程 pid=%d---父进程pid=%d" % (os.getpid(), os.getppid()))
        time.sleep(1)

def main():
    print("---主进程 pid=%d---父进程pid=%d" % (os.getpid(), os.getppid()))
    p_sing = multiprocessing.Process(target=sing)
    p_sing.start()

if __name__ == '__main__':
    main()


from django.contrib import admin
from django.http import HttpResponseRedirect
from utils.dockerController import DockerController
from .models import *
from .views import *

# Register your models here.

admin.site.register(ProblemTemplate)
admin.site.register(Hack)

@admin.register(Down)
class DownAdmin(admin.ModelAdmin):
    list_display = ('problem', 'time', 'team', 'rounds')
    list_display_links = ('problem',)

@admin.register(Restart)
class RestartAdmin(admin.ModelAdmin):
    list_display = ('problem', 'time', 'team', 'rounds')
    list_display_links = ('problem',)

# @admin.register(Problem)
# class ProblemAdmin(admin.ModelAdmin):
#     list_display = ('container_id', 'time', 'template', 'web_external_port', 'ssh_external_port', 'status', 'team')
#     list_display_links = ('container_id',)

# def name(self, obj):
#     return str(obj.owner) + "：" + str(obj.owner.first_name)

@admin.register(Problem)
class ProblemAdmin(admin.ModelAdmin):
    change_form_template = "manager/problem_list.html"
    list_display = ('container_id', 'time', 'template', 'web_external_port', 'ssh_external_port', 'status', 'team')
    list_display_links = ('container_id',)

    def response_change(self, request, obj):
        if "_restart" in request.POST:
            docker_controller = DockerController()
            docker_controller.rm_container(obj.container_id)
            template = obj.template
            docker_info = docker_controller.run_container(template.image_id, template.internal_port)
            obj.container_id = docker_info['id']
            obj.ssh_external_port = docker_info['ssh_port']
            obj.web_external_port = docker_info['web_port']
            obj.status = 'running'
            obj.flag, command = generate_flag_command(template.change_flag_command)
            docker_controller.exec_container(obj.container_id, command)
            obj.ssh_passwd, command = generate_ssh_paasword(template.change_passwd_command)
            docker_controller.exec_container(obj.container_id, command)
            obj.save()
            restart = Restart.objects.filter(team=obj.team,problem=obj,rounds=obj.rounds).first()
            if not restart:
                Restart.objects.create(team=obj.team,problem=obj,rounds=obj.rounds)
            self.message_user(request, f"{obj.container_id}重启成功")

            return HttpResponseRedirect(".")
        return super().response_change(request, obj)

@admin.register(Attack)
class AttackAdmin(admin.ModelAdmin):
    list_display = ('detail', 'problem', 'time')
    list_display_links = ('detail',)

    def detail(self, obj):
        return str(obj)

_admin_site_get_urls = admin.site.get_urls

def get_urls():
    from django.conf.urls import url
    urls = _admin_site_get_urls()
    urls += [
        url(r'^instantiate_all_problem/$',
            admin.site.admin_view(InstantiateAllProblemView.as_view()), name='instantiate_all_problem'),
        url(r'^remove_all_problem/$',
            admin.site.admin_view(RemoveAllProblem.as_view()), name='remove_all_problem'),
    ]
    return urls

admin.site.get_urls = get_urls


#!/usr/bin/python2.7
from Tkinter import *
import base64

# ********* Functions related to base64 ***************

def base64encode(event) :
	l = len(str(entry2.get()))
	entry2.delete(0,l)
	e1 = str(entry1.get())
	e2 = base64.b64encode(e1)
	entry2.insert(0,e2)

def base64decode(event) :
	l = len(str(entry4.get()))
	entry4.delete(0,l)
	e3 = str(entry3.get())
	e4 = base64.b64decode(e3)
	entry4.insert(0,e4)

def cleartextencode(event) :
	l1 = len(str(entry1.get()))
	l2 = len(str(entry2.get()))
	entry1.delete(0,l1)
	entry2.delete(0,l2)

def cleartextdecode(event) :
	l1 = len(str(entry3.get()))
	l2 = len(str(entry4.get()))
	entry3.delete(0,l1)
	entry4.delete(0,l2)

# ******** Base64 functions ends **********************

screen = Tk()

screen.title("Tools")

# ******************* All about Base64 *****************

# *****************Base 64 encode *********************

heading = Label(screen, text="All about Base64")
heading.config(font=("Courier",32))
heading.grid(row=0,columnspan=2,pady=10)

Label(screen, text="Input String: ").grid(row=1, sticky=E, padx=4, pady=10)
entry1 = Entry(screen, width=50)
entry1.grid(row=1, column=1, padx=4, sticky=W, pady=10)

Label(screen, text="Base64 Encoded: ").grid(row=2, sticky=E, padx=4, pady=10)
entry2 = Entry(screen, width=50)
entry2.grid(row=2,column=1,padx=4,sticky=W, pady=10)

encodebutton = Button(screen,text="ENCODE")
encodebutton.bind("<Button-1>", base64encode)
encodebutton.grid(row=3,column=1,sticky=W, pady=10)
clearbuttonencode = Button(screen,text="Clear")
clearbuttonencode.bind("<Button-1>",cleartextencode)
clearbuttonencode.grid(row=3,column=1)

# *******************Base64 encode ends ********************

# ********************Base64 decode ************************

Label(screen, text="Input String: ").grid(row=4, sticky=E, padx=4, pady=10)
entry3 = Entry(screen, width=50)
entry3.grid(row=4, column=1, padx=4, sticky=W, pady=10)

Label(screen, text="Base64 Decoded: ").grid(row=5, sticky=E, padx=4, pady=10)
entry4 = Entry(screen, width=50)
entry4.grid(row=5,column=1,padx=4,sticky=W, pady=10)

decodebutton = Button(screen,text="DECODE")
decodebutton.bind("<Button-1>", base64decode)
decodebutton.grid(row=6,column=1,sticky=W, pady=10)
clearbuttondecode = Button(screen,text="Clear")
clearbuttondecode.bind("<Button-1>",cleartextdecode)
clearbuttondecode.grid(row=6,column=1)

# ***********************Base64 decode ends ****************

screen.mainloop()


import numpy as np
import matplotlib.pyplot as plt
from FFDL_DCC_controller_class import FFDLcController
import time
Nt=20000 # total time
N=4 #agent number
k=np.linspace(1,Nt+1,Nt+1)
memory=20000
# controller=FFDLcController(Nt=memory,le=1,lu=2,yd_init=10,xs_init=[1,2,3,4])
# controller=FFDLcController(Nt=memory,le=2,lu=2,yd_init=245,ys_init=[240,240,240,240])
controller=FFDLcController(Nt=memory,le=1,lu=2,yd_init=2,ys_init=[0,0,0,0])
#### constant yd
xd=np.linspace(2,2,Nt+1)
yd=np.linspace(245,245+2*0.01*Nt,Nt+1)
# yd=np.linspace(6,6,Nt+1)+np.sin(np.linspace(0,Nt*0.02*np.pi,Nt+1))
xs=[np.ones((Nt+1,1)),2*np.ones((Nt+1,1)),3*np.ones((Nt+1,1)),4*np.ones((Nt+1,1)) ]
ys=[240*np.ones((Nt+1,1)),240*np.ones((Nt+1,1)),240*np.ones((Nt+1,1)),240*np.ones((Nt+1,1)) ]
us=[np.zeros((Nt,1)) for i in range(N)]

us[0][1],us[1][1],us[2][1],us[3][1]=controller.compute_u(yd[2],[float(ys[i][1]) for i in range(4)])
start_time=time.time()
err=[0 for i in range(N)]
for k in range(1,Nt):
    # Agent model
    #Agent1
    xs[0][k+1]=(xs[0][k-1]*xs[0][k])/(1+xs[0][k-1]**2+xs[0][k]**2)+3*us[0][k]
    ys[0][k+1]=ys[0][k]+xs[0][k]*0.01
    #Agent2
    xs[1][k+1]=(xs[1][k])/(1+xs[1][k]**4)+us[1][k]**3
    ys[1][k+1]=ys[1][k]+xs[1][k]*0.01
    #Agent3
    xs[2][k+1]=(xs[2][k-1]*xs[2][k]*us[2][k-1]+2*us[2][k])/(1+xs[2][k-1]**2+xs[2][k]**2)+us[2][k]**3
    ys[2][k+1]=ys[2][k]+xs[2][k]*0.01
    #Agent4
    xs[3][k+1]=(xs[3][k]*us[3][k])/(1+xs[3][k]**6)+2*us[3][k]
    ys[3][k+1]=ys[3][k]+xs[3][k]*0.01

    for i in range (N): err[i]+=abs(yd[k]-ys[i][k])*0.01
    if (k!=Nt-1): us[0][k+1],us[1][k+1],us[2][k+1],us[3][k+1]=controller.compute_u(yd[k+2],[float(ys[i][k+1]) for i in range(4)])

    # if k==30: print(controller.es[0].T)
# print("%0.2f %0.2f %0.2f %0.2f    %0.2f %0.2f %0.2f %0.2f"%(1,25.5,0,0,0,0,0,0))
print((time.time()-start_time))
print("%0.2f %0.2f %0.2f %0.2f "%(err[0],err[1],err[2],err[3]))
# controller.compute_e([float(xs[i][Nt]) for i in range(4)])

t=np.linspace(0,Nt,Nt+1)
line_colors=['b','r','m','g']
fig, ax = plt.subplots()
ax.plot(t, yd, color='k',label='yd')
for i in range(N): ax.plot(t, ys[i], color=line_colors[i],label='Agent%d'%(i+1))
ax.legend()
ax.set_title('Tracking performance')
plt.show()

# # t=np.linspace(0,Nt,Nt+1)
# # line_colors=['b','r','m','g']
# # fig, ax = plt.subplots()
# # ax.plot(t[memory:], yd[memory:]-6, color='k',label='yd')
# # for i in range(N): ax.plot(t[memory:]-6, xs[i][memory:]-6, color=line_colors[i],label='Agent%d'%(i+1))
# # ax.legend()
# # ax.set_title('Tracking performance')
# # plt.show()

# plt.close()
# fig, ax = plt.subplots()
# for i in range(N): ax.plot(t[:Nt], controller.phis[i], color=line_colors[i],label='Agent%d'%(i+1))
# ax.legend()
# ax.set_title('PPD estimation')
# plt.show()

plt.close()
fig, ax = plt.subplots()
for i in range(N): ax.plot(t[:Nt], us[i], color=line_colors[i],label='Agent%d'%(i+1))
ax.legend()
ax.set_title('Control input')
plt.show()

# # plt.close()
# # fig, ax = plt.subplots()
# # for i in range(N): ax.plot(t, controller.es[i], color=line_colors[i],label='Agent%d'%(i+1))
# # ax.legend()
# # ax.set_title('Tracking error')
# # plt.show()

# # plt.close()
# # fig, ax = plt.subplots()
# # for i in range(N): ax.plot(t, controller.thetas[i].T, color=line_colors[i],label='Agent%d'%(i+1))
# # ax.legend()
# # ax.set_title('Control gain')
# # plt.show()