from time import time
from urlparse import urlparse, urlunparse, urldefrag

from twisted.python import failure
from twisted.web.client import HTTPClientFactory
from twisted.web.http import HTTPClient
from twisted.internet import defer

from scrapy.http import Headers
from scrapy.utils.httpobj import urlparse_cached
from scrapy.responsetypes import responsetypes
from scrapy import optional_features
from scrapy import log
from copy import deepcopy

def _parsed_url_args(parsed):
    path = urlunparse(('', '', parsed.path or '/', parsed.params, parsed.query, ''))
    host = parsed.hostname
    port = parsed.port
    scheme = parsed.scheme
    netloc = parsed.netloc
    if port is None:
        port = 443 if scheme == 'https' else 80
    return scheme, netloc, host, port, path

def _parse(url):
    url = url.strip()
    parsed = urlparse(url)
    return _parsed_url_args(parsed)

class _IdentityTransferDecoder(object):
    """
    Protocol for accumulating bytes up to a specified length.  This handles the
    case where no I{Transfer-Encoding} is specified.

    @ivar contentLength: Counter keeping track of how many more bytes there are
        to receive.

    @ivar dataCallback: A one-argument callable which will be invoked each
        time application data is received.

    @ivar finishCallback: A one-argument callable which will be invoked when
        the terminal chunk is received.  It will be invoked with all bytes
        which were delivered to this protocol which came after the terminal
        chunk.
    """
    def __init__(self, contentLength, dataCallback, finishCallback):
        self.contentLength = contentLength
        self.dataCallback = dataCallback
        self.finishCallback = finishCallback

    def dataReceived(self, data):
        """
        Interpret the next chunk of bytes received.  Either deliver them to the
        data callback or invoke the finish callback if enough bytes have been
        received.

        @raise RuntimeError: If the finish callback has already been invoked
            during a previous call to this methood.
        """
        if self.dataCallback is None:
            raise RuntimeError(
                "_IdentityTransferDecoder cannot decode data after finishing")

        if self.contentLength is None:
            self.dataCallback(data)
        elif len(data) < self.contentLength:
            self.contentLength -= len(data)
            self.dataCallback(data)
        else:
            # Make the state consistent before invoking any code belonging to
            # anyone else in case noMoreData ends up being called beneath this
            # stack frame.
            contentLength = self.contentLength
            dataCallback = self.dataCallback
            finishCallback = self.finishCallback
            self.dataCallback = self.finishCallback = None
            self.contentLength = 0

            dataCallback(data[:contentLength])
            finishCallback(data[contentLength:])

    def noMoreData(self):
        """
        All data which will be delivered to this decoder has been.  Check to
        make sure as much data as was expected has been received.

        @raise PotentialDataLoss: If the content length is unknown.
        @raise _DataLoss: If the content length is known and fewer than that
            many bytes have been delivered.

        @return: C{None}
        """
        finishCallback = self.finishCallback
        self.dataCallback = self.finishCallback = None
        if self.contentLength is None:
            finishCallback('')
            raise PotentialDataLoss()
        elif self.contentLength != 0:
            raise _DataLoss()

class _ChunkedTransferDecoder(object):
    """
    Protocol for decoding I{chunked} Transfer-Encoding, as defined by RFC 2616,
    section 3.6.1.  This protocol can interpret the contents of a request or
    response body which uses the I{chunked} Transfer-Encoding.  It cannot
    interpret any of the rest of the HTTP protocol.

    It may make sense for _ChunkedTransferDecoder to be an actual IProtocol
    implementation.  Currently, the only user of this class will only ever
    call dataReceived on it.  However, it might be an improvement if the
    user could connect this to a transport and deliver connection lost
    notification.  This way, `dataCallback` becomes `self.transport.write`
    and perhaps `finishCallback` becomes `self.transport.loseConnection()`
    (although I'm not sure where the extra data goes in that case).  This
    could also allow this object to indicate to the receiver of data that
    the stream was not completely received, an error case which should be
    noticed. -exarkun

    @ivar dataCallback: A one-argument callable which will be invoked each
        time application data is received.

    @ivar finishCallback: A one-argument callable which will be invoked when
        the terminal chunk is received.  It will be invoked with all bytes
        which were delivered to this protocol which came after the terminal
        chunk.

    @ivar length: Counter keeping track of how many more bytes in a chunk there
        are to receive.

    @ivar state: One of C{'chunk-length'}, C{'trailer'}, C{'body'}, or
        C{'finished'}.  For C{'chunk-length'}, data for the chunk length line
        is currently being read.  For C{'trailer'}, the CR LF pair which
        follows each chunk is being read.  For C{'body'}, the contents of a
        chunk are being read.  For C{'finished'}, the last chunk has been
        completely read and no more input is valid.

    @ivar finish: A flag indicating that the last chunk has been started.  When
        it finishes, the state will change to C{'finished'} and no more data
        will be accepted.
    """
    state = 'chunk-length'
    finish = False

    def __init__(self, dataCallback, finishCallback):
        self.dataCallback = dataCallback
        self.finishCallback = finishCallback
        self._buffer = ''

    def dataReceived(self, data):
        """
        Interpret data from a request or response body which uses the
        I{chunked} Transfer-Encoding.
        """
        data = self._buffer + data
        self._buffer = ''
        while data:
            if self.state == 'chunk-length':
                if '\r\n' in data:
                    line, rest = data.split('\r\n', 1)
                    parts = line.split(';')
                    self.length = int(parts[0], 16)
                    if self.length == 0:
                        self.state = 'trailer'
                        self.finish = True
                    else:
                        self.state = 'body'
                    data = rest
                else:
                    self._buffer = data
                    data = ''
            elif self.state == 'trailer':
                if data.startswith('\r\n'):
                    data = data[2:]
                    if self.finish:
                        self.state = 'finished'
                        self.finishCallback(data)
                        data = ''
                    else:
                        self.state = 'chunk-length'
                else:
                    self._buffer = data
                    data = ''
            elif self.state == 'body':
                if len(data) >= self.length:
                    chunk, data = data[:self.length], data[self.length:]
                    self.dataCallback(chunk)
                    self.state = 'trailer'
                elif len(data) < self.length:
                    self.length -= len(data)
                    self.dataCallback(data)
                    data = ''
            elif self.state == 'finished':
                raise RuntimeError(
                    "_ChunkedTransferDecoder.dataReceived called after last "
                    "chunk was processed")

    def noMoreData(self):
        """
        Verify that all data has been received.  If it has not been, raise
        L{_DataLoss}.
        """
        if self.state != 'finished':
            raise _DataLoss(
                "Chunked decoder in %r state, still expecting more data to "
                "get to finished state." % (self.state,))

class myHTTPClient(HTTPClient):

    def sendCommand(self, command, path):
        self.transport.write('%s %s HTTP/1.1\r\n' % (command, path))

    def extractHeader(self, header):
        """
        Given a complete HTTP header, extract the field name and value and
        process the header.

        @param header: a complete HTTP request header of the form
            'field-name: value'.
        @type header: C{str}
        """
        key, val = header.split(':', 1)
        val = val.lstrip()
        self.handleHeader(key, val)
        if key.lower() == 'content-length':
            self.length = int(val)
            log.msg("extractHeader  UnChunk.")
            self._transferDecoder = _IdentityTransferDecoder(
                self.length, self.handleContentChunk, self._finishRequestBody)
        elif key.lower() == 'transfer-encoding' and val.lower() == 'chunked':
            self.length = None
            log.msg("extractHeader  Chunk.")
            self._transferDecoder = _ChunkedTransferDecoder(
                self.handleContentChunk, self._finishRequestBody)

    def lineReceived(self, line):
        if self.firstLine:
            self.firstLine = False
            l = line.split(None, 2)
            version = l[0]
            status = l[1]
            try:
                message = l[2]
            except IndexError:
                # sometimes there is no message
                message = ""
            self.handleStatus(version, status, message)
            return
        if not line:
            # reach the end of head
            if self._header != "":
                self.extractHeader(self._header)
            self._header = ""
            self.handleEndHeaders()
            self.__buffer = ""
            # self.__buffer = StringIO()
            if self.length == 0:
                #Something special
                self.allContentReceived()
            else:
                # use the raw-receiver
                self.setRawMode()
            return
        # Still read header lines
        if line.startswith('\t') or line.startswith(' '):
            self._header = self._header + line
        elif self._header:
            self.extractHeader(self._header)
            self._header = line
        else: # First header
            self._header = line

    def allContentReceived(self):
        self.length = 0
        self._first_line = 1
        self._transferDecoder = None

    def connectionLost(self, reason):
        log.msg("connectionLost")
        self.handleResponseEnd()

    def handleResponseEnd(self):
        """
        The response has been completely received.

        This callback may be invoked more than once per request.
        """
        if self.__buffer is not None:
            b = deepcopy(self.__buffer)
            #.getvalue()
            self.__buffer = None
            log.msg("Response length %d" % len(b))
            self.handleResponse(b)

    def rawDataReceived(self, data):
        self._transferDecoder.dataReceived(data)

    def handleContentChunk(self, data):
        log.msg("handleContentChunk. %d" % len(data))
        self.__buffer = self.__buffer + data
        self.transport.write("HTTP/1.1 100 Continue\r\n\r\n")

    def _finishRequestBody(self, data):
        log.msg("_finishRequestBody. %d" % len(data))

        self.allContentReceived()
        self.__buffer = self.__buffer + data
        self.handleResponseEnd()

class ScrapyHTTPPageGetter(myHTTPClient):

    delimiter = '\n'

    def connectionMade(self):
        self.headers = Headers() # bucket for response headers

        # Method command
        self.sendCommand(self.factory.method, self.factory.path)
        # Headers
        for key, values in self.factory.headers.items():
            for value in values:
                self.sendHeader(key, value)
        self.endHeaders()
        # Body
        if self.factory.body is not None:
            self.transport.write(self.factory.body)

    def lineReceived(self, line):
        return myHTTPClient.lineReceived(self, line.rstrip())

    def handleHeader(self, key, value):
        self.headers.appendlist(key, value)

    def handleStatus(self, version, status, message):
        self.factory.gotStatus(version, status, message)

    def handleEndHeaders(self):
        self.factory.gotHeaders(self.headers)

    def connectionLost(self, reason):
        myHTTPClient.connectionLost(self, reason)
        self.factory.noPage(reason)

    def handleResponse(self, response):
        if self.factory.method.upper() == 'HEAD':
            self.factory.page('')
        else:
            self.factory.page(response)
        self.transport.loseConnection()

    def timeout(self):
        self.transport.loseConnection()
        self.factory.noPage(\
                defer.TimeoutError("Getting %s took longer than %s seconds." % \
                (self.factory.url, self.factory.timeout)))

class ScrapyHTTPClientFactory(HTTPClientFactory):
    """Scrapy implementation of the HTTPClientFactory overwriting the
    serUrl method to make use of our Url object that cache the parse
    result.
    """

    protocol = ScrapyHTTPPageGetter
    waiting = 1
    noisy = False
    followRedirect = False
    afterFoundGet = False

    def __init__(self, request, timeout=180):
        self.url = urldefrag(request.url)[0]
        self.method = request.method
        self.body = request.body or None
        self.headers = Headers(request.headers)
        self.response_headers = None
        self.timeout = request.meta.get('download_timeout') or timeout
        self.start_time = time()
        self.deferred = defer.Deferred().addCallback(self._build_response, request)

        # Fixes Twisted 11.1.0+ support as HTTPClientFactory is expected
        # to have _disconnectedDeferred. See Twisted r32329.
        # As Scrapy implements it's own logic to handle redirects is not
        # needed to add the callback _waitForDisconnect.
        # Specifically this avoids the AttributeError exception when
        # clientConnectionFailed method is called.
        self._disconnectedDeferred = defer.Deferred()

        self._set_connection_attributes(request)

        # set Host header based on url
        self.headers.setdefault('Host', self.netloc)

        # set Content-Length based len of body
        if self.body is not None:
            self.headers['Content-Length'] = len(self.body)
            # just in case a broken http/1.1 decides to keep connection alive
            self.headers.setdefault("Connection", "close")

    def _build_response(self, body, request):
        request.meta['download_latency'] = self.headers_time-self.start_time
        status = int(self.status)
        headers = Headers(self.response_headers)
        respcls = responsetypes.from_args(headers=headers, url=self.url)
        return respcls(url=self.url, status=status, headers=headers, body=body)

    def _set_connection_attributes(self, request):
        parsed = urlparse_cached(request)
        self.scheme, self.netloc, self.host, self.port, self.path = _parsed_url_args(parsed)
        proxy = request.meta.get('proxy')
        if proxy:
            self.scheme, _, self.host, self.port, _ = _parse(proxy)
            self.path = self.url

    def gotHeaders(self, headers):
        self.headers_time = time()
        self.response_headers = headers

if 'ssl' in optional_features:
    from twisted.internet.ssl import ClientContextFactory
    from OpenSSL import SSL
else:
    ClientContextFactory = object

class ScrapyClientContextFactory(ClientContextFactory):
    "A SSL context factory which is more permissive against SSL bugs."
    # see https://github.com/scrapy/scrapy/issues/82
    # and https://github.com/scrapy/scrapy/issues/26

    def getContext(self):
        ctx = ClientContextFactory.getContext(self)
        # Enable all workarounds to SSL bugs as documented by
        # http://www.openssl.org/docs/ssl/SSL_CTX_set_options.html
        ctx.set_options(SSL.OP_ALL)
        return ctx


import pywt
import numpy as np
import matplotlib.pyplot as plt
t = np.linspace(-1, 1, 200, endpoint=False)
sig  = np.cos(2 * np.pi * 7 * t)
widths = np.arange(1, 31)
cwtmatr, freqs = pywt.cwt(sig, widths, 'mexh')
plt.imshow(cwtmatr, extent=[-1, 1, 1, 31], cmap='PRGn', aspect='auto',
            vmax=abs(cwtmatr).max(), vmin=-abs(cwtmatr).max())  # doctest: +SKIP
plt.show() # doctest: +SKIP

print(freqs)


from rest_framework.viewsets import ViewSet
from rest_framework.response import Response

from common.request_data_validator import RequestDataValidationHelper
from common.response_helper import ResponseHelper

from app.services.user_service import UserService

class UserController(ViewSet):
    """CRUD APIs for the 'User' model"""

    request_data_validation_schema = {
        'create': {
            'email': ['required', 'str'],
            'product_alert_email_frequency': ['required', 'int'],
            'search_phrases': ['required', 'list_of_str']
        }
    }

    def list(self, request) -> Response:
        """
        Get user object using user_id or email query param

        :param request: client Request containing user_id or email query param
        :return: Response
        """

        user_id = request.query_params.get('user_id')
        email = request.query_params.get('email')

        user_service = UserService()
        if user_id:
            return ResponseHelper.create_response(
                user_service.get_user_by_user_id(user_id=user_id, load_search_phrases=True, serialize=True))
        elif email:
            return ResponseHelper.create_response(
                user_service.get_user_by_email(email=email, load_search_phrases=True, serialize=True))
        else:
            return ResponseHelper.create_response(data={'errors': ['user_id or email query param required']},
                                                  status_code=400)

    def create(self, request) -> Response:
        """
        Create user object along with search phrases (just to keep it simple)

        :param request: client Request with payload data

        format:
        {
            'email': 'test@abc.com',
            'product_alert_email_frequency': 2 | 10 | 20,
            'search_phrases': ['gym', 'fitness', 'music' ...]
        }

        :return: Response
        """

        # Validate request payload
        errors = RequestDataValidationHelper().validate(data=request.data,
                                                        validation_schema=self.request_data_validation_schema['create'])
        if errors:
            return ResponseHelper.create_response(data={'errors': errors}, status_code=400)

        user_service = UserService()
        user_service.create_user(email=request.data['email'],
                                 product_alert_email_frequency=request.data['product_alert_email_frequency'],
                                 search_phrases=request.data['search_phrases'])

        return ResponseHelper.create_response(data="User created successfully")


class DynamicArray(object):
    def __init__(self) -> None:
        self.index = 0
        self.capacity = 1

        self.array = self._create_array(self.capacity)

    def __len__(self):
        '''
            len(array) return the length of the array it will be the last known index
        '''
        return self.index

    def __getitem__(self, index):
        if not 0 <= index < self.index:
            return IndexError(f'Index {index} out of bound')
        else:
            return self.array[index]

    def __str__(self):
        return str(self.array[:self.index])

    def _create_array(self, new_capacity):
        return [None] * new_capacity

    def _resize(self, new_capacity):
        new_arr = self._create_array(new_capacity)

        for i in range(self.index):
            new_arr[i] = self.array[i]

        self.array = new_arr
        self.capacity = new_capacity

    def max_cap(self):
        '''
            max_cap() return the capacity of the array
        '''
        return self.capacity

    def is_empty(self):
        '''
            is_empty() returns True if empty else false
        '''
        if self.index == 0:
            return True
        else:
            return False

    def append(self, item):
        '''
            append(item) adds a new item at the end.
        '''

        if self.index == self.capacity:
            self._resize(2*self.capacity)

        self.array[self.index] = item
        self.index = self.index + 1

    def pop(self):
        '''
            pop() removes and returns the last item
        '''
        if self.index == 0:
            return IndexError('No items available to remove')
        else:
            el = self.array[self.index]
            self.array[self.index] = None
            self.index = self.index -1
            return el

    def insert_at(self, index, item):

        if index < 0 or index > self.index:
            return IndexError(f'Index {index} out of bound')

        if self.index == self.capacity:
            self._resize(2*self.capacity)
        print(self.index)
        for i in range(self.index - 1, index-1, -1):
            print(self.array, i)
            self.array[i+1] = self.array[i]

        self.array[index] = item
        self.index = self.index + 1

    def delete(self, index):

        if index < 0 or index > self.index:
            return IndexError(f'Index {index} out of bound')

        for i in range(index+1, self.index):
            self.array[i-1] = self.array[i]

        self.array[self.index] = None
        self.index = self.index - 1

arr = DynamicArray()
print(arr[3])
print(arr.pop())
arr.append(1)
arr.append(2)
arr.append(3)
arr.append(5)
print(arr.max_cap())
print(len(arr))
print(arr[2])
arr.insert_at(3,4)
arr.insert_at(0,0)
arr.delete(3)

print(arr)


"""
Based on PureJaxRL Implementation of PPO
"""

import jax
import jax.numpy as jnp
import flax.linen as nn
import numpy as np
import optax
from flax.linen.initializers import constant, orthogonal
from typing import Sequence, NamedTuple, Any, Dict
from flax.training.train_state import TrainState
import distrax
from jaxmarl.wrappers.baselines import LogWrapper
import jaxmarl
import wandb
import functools
import matplotlib.pyplot as plt
import hydra
from omegaconf import OmegaConf

class ActorCritic(nn.Module):
    action_dim: Sequence[int]
    config: Dict

    @nn.compact
    def __call__(self, x):
        obs, dones, avail_actions = x
        embedding = nn.Dense(
            512, kernel_init=orthogonal(np.sqrt(2)), bias_init=constant(0.0)
        )(obs)
        embedding = nn.relu(embedding)

        actor_mean = nn.Dense(512, kernel_init=orthogonal(2), bias_init=constant(0.0))(
            embedding
        )
        actor_mean = nn.relu(actor_mean)
        actor_mean = nn.Dense(
            self.action_dim, kernel_init=orthogonal(0.01), bias_init=constant(0.0)
        )(actor_mean)
        unavail_actions = 1 - avail_actions
        action_logits = actor_mean - (unavail_actions * 1e10)
        pi = distrax.Categorical(logits=action_logits)

        critic = nn.Dense(512, kernel_init=orthogonal(2), bias_init=constant(0.0))(
            embedding
        )
        critic = nn.relu(critic)
        critic = nn.Dense(1, kernel_init=orthogonal(1.0), bias_init=constant(0.0))(
            critic
        )

        return pi, jnp.squeeze(critic, axis=-1)

class Transition(NamedTuple):
    done: jnp.ndarray
    action: jnp.ndarray
    value: jnp.ndarray
    reward: jnp.ndarray
    log_prob: jnp.ndarray
    obs: jnp.ndarray
    info: jnp.ndarray
    avail_actions: jnp.ndarray

def batchify(x: dict, agent_list, num_actors):
    x = jnp.stack([x[a] for a in agent_list])
    return x.reshape((num_actors, -1))

def unbatchify(x: jnp.ndarray, agent_list, num_envs, num_actors):
    x = x.reshape((num_actors, num_envs, -1))
    return {a: x[i] for i, a in enumerate(agent_list)}

def make_train(config):
    env = jaxmarl.make(config["ENV_NAME"], **config["ENV_KWARGS"])
    config["NUM_ACTORS"] = env.num_agents * config["NUM_ENVS"]
    config["NUM_UPDATES"] = (
            config["TOTAL_TIMESTEPS"] // config["NUM_STEPS"] // config["NUM_ENVS"]
    )
    config["MINIBATCH_SIZE"] = (
            config["NUM_ACTORS"] * config["NUM_STEPS"] // config["NUM_MINIBATCHES"]
    )

    env = LogWrapper(env)

    def linear_schedule(count):
        frac = 1.0 - (count // (config["NUM_MINIBATCHES"] * config["UPDATE_EPOCHS"])) / config["NUM_UPDATES"]
        return config["LR"] * frac

    def train(rng):

        # INIT NETWORK
        network = ActorCritic(env.action_space(env.agents[0]).n, config=config)
        rng, _rng = jax.random.split(rng)
        init_x = (
            jnp.zeros(
                (1, config["NUM_ENVS"], env.observation_space(env.agents[0]).n)
            ),
            jnp.zeros((1, config["NUM_ENVS"])),
            jnp.zeros((1, config["NUM_ENVS"], env.action_space(env.agents[0]).n))
        )
        network_params = network.init(_rng, init_x)
        if config["ANNEAL_LR"]:
            tx = optax.chain(
                optax.clip_by_global_norm(config["MAX_GRAD_NORM"]),
                optax.adam(learning_rate=linear_schedule, eps=1e-5),
            )
        else:
            tx = optax.chain(optax.clip_by_global_norm(config["MAX_GRAD_NORM"]), optax.adam(config["LR"], eps=1e-5))
        train_state = TrainState.create(
            apply_fn=network.apply,
            params=network_params,
            tx=tx,
        )

        # INIT ENV
        rng, _rng = jax.random.split(rng)
        reset_rng = jax.random.split(_rng, config["NUM_ENVS"])
        obsv, env_state = jax.vmap(env.reset, in_axes=(0))(reset_rng)

        # TRAIN LOOP
        def _update_step(update_runner_state, unused):
            # COLLECT TRAJECTORIES
            runner_state, update_steps = update_runner_state
            def _env_step(runner_state, unused):
                train_state, env_state, last_obs, last_done, rng = runner_state

                # SELECT ACTION
                rng, _rng = jax.random.split(rng)
                avail_actions = jax.vmap(env.get_legal_moves)(env_state.env_state)
                avail_actions = jax.lax.stop_gradient(
                    batchify(avail_actions, env.agents, config["NUM_ACTORS"])
                )
                obs_batch = batchify(last_obs, env.agents, config["NUM_ACTORS"])
                ac_in = (obs_batch[np.newaxis, :], last_done[np.newaxis, :], avail_actions[np.newaxis, :])
                pi, value = network.apply(train_state.params, ac_in)
                action = pi.sample(seed=_rng)
                log_prob = pi.log_prob(action)
                env_act = unbatchify(action, env.agents, config["NUM_ENVS"], env.num_agents)

                # STEP ENV
                rng, _rng = jax.random.split(rng)
                rng_step = jax.random.split(_rng, config["NUM_ENVS"])
                obsv, env_state, reward, done, info = jax.vmap(env.step, in_axes=(0, 0, 0))(
                    rng_step, env_state, env_act
                )
                info = jax.tree_map(lambda x: x.reshape((config["NUM_ACTORS"])), info)
                done_batch = batchify(done, env.agents, config["NUM_ACTORS"]).squeeze()
                transition = Transition(
                    done_batch,
                    action.squeeze(),
                    value.squeeze(),
                    batchify(reward, env.agents, config["NUM_ACTORS"]).squeeze(),
                    log_prob.squeeze(),
                    obs_batch,
                    info,
                    avail_actions
                )
                runner_state = (train_state, env_state, obsv, done_batch, rng)
                return runner_state, transition

            runner_state, traj_batch = jax.lax.scan(
                _env_step, runner_state, None, config["NUM_STEPS"]
            )

            # CALCULATE ADVANTAGE
            train_state, env_state, last_obs, last_done, rng = runner_state
            last_obs_batch = batchify(last_obs, env.agents, config["NUM_ACTORS"])
            avail_actions = jnp.ones(
                (config["NUM_ACTORS"], env.action_space(env.agents[0]).n)
            )
            ac_in = (last_obs_batch[np.newaxis, :], last_done[np.newaxis, :], avail_actions)
            _, last_val = network.apply(train_state.params, ac_in)
            last_val = last_val.squeeze()

            def _calculate_gae(traj_batch, last_val):
                def _get_advantages(gae_and_next_value, transition):
                    gae, next_value = gae_and_next_value
                    done, value, reward = (
                        transition.done,
                        transition.value,
                        transition.reward,
                    )
                    delta = reward + config["GAMMA"] * next_value * (1 - done) - value
                    gae = (
                            delta
                            + config["GAMMA"] * config["GAE_LAMBDA"] * (1 - done) * gae
                    )
                    return (gae, value), gae

                _, advantages = jax.lax.scan(
                    _get_advantages,
                    (jnp.zeros_like(last_val), last_val),
                    traj_batch,
                    reverse=True,
                    unroll=16,
                )
                return advantages, advantages + traj_batch.value

            advantages, targets = _calculate_gae(traj_batch, last_val)

            # UPDATE NETWORK
            def _update_epoch(update_state, unused):
                def _update_minbatch(train_state, batch_info):
                    traj_batch, advantages, targets = batch_info

                    def _loss_fn(params, traj_batch, gae, targets):
                        # RERUN NETWORK
                        pi, value = network.apply(params,
                                                  (traj_batch.obs, traj_batch.done, traj_batch.avail_actions))
                        log_prob = pi.log_prob(traj_batch.action)

                        # CALCULATE VALUE LOSS
                        value_pred_clipped = traj_batch.value + (
                                value - traj_batch.value
                        ).clip(-config["CLIP_EPS"], config["CLIP_EPS"])
                        value_losses = jnp.square(value - targets)
                        value_losses_clipped = jnp.square(value_pred_clipped - targets)
                        value_loss = (
                                0.5 * jnp.maximum(value_losses, value_losses_clipped).mean()
                        )

                        # CALCULATE ACTOR LOSS
                        ratio = jnp.exp(log_prob - traj_batch.log_prob)
                        gae = (gae - gae.mean()) / (gae.std() + 1e-8)
                        loss_actor1 = ratio * gae
                        loss_actor2 = (
                                jnp.clip(
                                    ratio,
                                    1.0 - config["CLIP_EPS"],
                                    1.0 + config["CLIP_EPS"],
                                )
                                * gae
                        )
                        loss_actor = -jnp.minimum(loss_actor1, loss_actor2)
                        loss_actor = loss_actor.mean()
                        entropy = pi.entropy().mean()

                        total_loss = (
                                loss_actor
                                + config["VF_COEF"] * value_loss
                                - config["ENT_COEF"] * entropy
                        )
                        return total_loss, (value_loss, loss_actor, entropy)

                    grad_fn = jax.value_and_grad(_loss_fn, has_aux=True)
                    total_loss, grads = grad_fn(
                        train_state.params, traj_batch, advantages, targets
                    )
                    train_state = train_state.apply_gradients(grads=grads)
                    return train_state, total_loss

                train_state, traj_batch, advantages, targets, rng = update_state
                rng, _rng = jax.random.split(rng)

                batch = (traj_batch, advantages.squeeze(), targets.squeeze())
                permutation = jax.random.permutation(_rng, config["NUM_ACTORS"])

                shuffled_batch = jax.tree_util.tree_map(
                    lambda x: jnp.take(x, permutation, axis=1), batch
                )

                minibatches = jax.tree_util.tree_map(
                    lambda x: jnp.swapaxes(
                        jnp.reshape(
                            x,
                            [x.shape[0], config["NUM_MINIBATCHES"], -1]
                            + list(x.shape[2:]),
                        ),
                        1,
                        0,
                    ),
                    shuffled_batch,
                )

                train_state, total_loss = jax.lax.scan(
                    _update_minbatch, train_state, minibatches
                )
                update_state = (train_state, traj_batch, advantages, targets, rng)
                return update_state, total_loss

            update_state = (train_state, traj_batch, advantages, targets, rng)
            update_state, loss_info = jax.lax.scan(
                _update_epoch, update_state, None, config["UPDATE_EPOCHS"]
            )
            train_state = update_state[0]
            metric = traj_batch.info
            rng = update_state[-1]

            def callback(metric):
                wandb.log(
                    {
                        "returns": metric["returned_episode_returns"][-1, :].mean(),
                        "env_step": metric["update_steps"]
                        * config["NUM_ENVS"]
                        * config["NUM_STEPS"],
                    }
                )
            metric["update_steps"] = update_steps
            jax.experimental.io_callback(callback, None, metric)
            update_steps = update_steps + 1
            runner_state = (train_state, env_state, last_obs, last_done, rng)
            return (runner_state, update_steps), None

        rng, _rng = jax.random.split(rng)
        runner_state = (train_state, env_state, obsv, jnp.zeros((config["NUM_ACTORS"]), dtype=bool), _rng)
        runner_state, _ = jax.lax.scan(
            _update_step, (runner_state, 0), None, config["NUM_UPDATES"]
        )
        return {"runner_state": runner_state}

    return train

@hydra.main(version_base=None, config_path="config", config_name="ippo_ff_hanabi")
def main(config):
    config = OmegaConf.to_container(config)

    wandb.init(
        entity=config["ENTITY"],
        project=config["PROJECT"],
        tags=["IPPO", "FF", config["ENV_NAME"]],
        config=config,
        mode=config["WANDB_MODE"],
    )

    rng = jax.random.PRNGKey(50)
    train_jit = jax.jit(make_train(config), device=jax.devices()[0])
    out = train_jit(rng)

if __name__ == "__main__":
    main()
    '''results = out["metrics"]["returned_episode_returns"].mean(-1).reshape(-1)
    jnp.save('hanabi_results', results)
    plt.plot(results)
    plt.xlabel("Update Step")
    plt.ylabel("Return")
    plt.savefig(f'IPPO_{config["ENV_NAME"]}.png')'''


from agents.DQNAgent import DQNAgent
from utils.Environments import DiscEnv
import matplotlib.pyplot as plt
import numpy as np

num_act = 3
input_dim  = 1
batch_size = 100
sender = DQNAgent(input_dim=input_dim, num_actions=num_act, batch_size=batch_size, lr=0.01, eps_start=0.99, intermed_nodes=num_act,
eps_min=0.01, eps_dec=5e-5, capacity=7000) #5
receiver = DQNAgent(input_dim=input_dim, num_actions=num_act, batch_size=batch_size, lr=0.01, eps_start=0.99, intermed_nodes=num_act,
eps_min=0.01, eps_dec=5e-5, capacity=7000)
env = DiscEnv(num_obs=num_act, num_actions=num_act)
returns = []

print("sender action probabilities")
for s in range(num_act):
    print(sender.get_action_probabilities(s))

print("receiver action probabilities")
for s in range(num_act):
    print(receiver.get_action_probabilities(s))

num_episodes = 20000

for ep in range(num_episodes):
    if (ep%1000 == 0):
        print("episode=", ep)
        print("epsilon", sender.epsilon)

    obs = env.get_observation()
    mex = sender.take_action(obs)
    act = receiver.take_action(mex)
    rew = env.get_reward(obs, act)

    sender.store_transition(obs, mex, rew)
    receiver.store_transition(mex, act, rew)
    returns.append(rew)

    sender.train(ep)
    receiver.train(ep)

print("epsilon", sender.epsilon)
print("sender action probabilities")
for s in range(num_act):
    print(sender.get_action_probabilities(s))

print("receiver action probabilities")
for s in range(num_act):
    print(receiver.get_action_probabilities(s))

for s in range(num_act):
    plt.plot(np.linspace(0, num_act, num_act), sender.get_action_probabilities(s), label=str(s))
plt.legend()
plt.savefig("sender_policy"+str(num_act)+"_dqn_discrete.png")
plt.show()

for s in range(num_act):
    plt.plot(np.linspace(0, num_act, num_act), receiver.get_action_probabilities(s), label=str(s))
plt.legend()
plt.savefig("receiver_policy"+str(num_act)+"_dqn_discrete.png")
plt.show()

#test phase:
test_episodes = 3000
sender.epsilon = 0
receiver.epsilon = 0
for ep in range(test_episodes):

    obs = env.get_observation()
    mex = sender.take_action(obs)
    act = receiver.take_action(mex)
    rew = env.get_reward(obs, act)
    returns.append(rew)

plt.plot(np.linspace(0, len(returns), len(returns)), returns, label="returns")
plt.legend()
plt.savefig("returns_dqn_discrete.png")
plt.show()


from django.shortcuts import render,redirect
from .forms import UserRegisterForm, UserUpdatedForm, ProfileUpdateForm
from django.contrib import messages
# Create your views here.

def register(request):

    if request.method == 'POST':
        form = UserRegisterForm(request.POST)

        if form.is_valid():
            username = form.cleaned_data.get('username')
            form.save()
            messages.success(request,'User created')
            return redirect('products:product_list')
    else:
        form = UserRegisterForm()
    return render(request, 'users/register.html', {'form':form})

def profile(request):

    if request.method == 'POST':
        u_form = UserUpdatedForm(request.POST, instance=request.user)
        p_form = ProfileUpdateForm(request.POST, request.FILES, instance=request.user.profile)
        if u_form.is_valid() and p_form.is_valid():
            u_form.save()
            p_form.save()
            messages.success(request, 'User updated')
            return redirect('profile')
    else:
        u_form = UserUpdatedForm(instance=request.user)
        p_form = ProfileUpdateForm(instance=request.user.profile)

        context = {

            'u_form' : u_form,
            'p_form' : p_form
        }
    return render(request, 'users/profile.html', context)


#!/usr/bin/env python3
import os
from pathlib import Path
from markdown import markdown
# This entire script is done somewhat verbosely to hopefully be platform-independent
# Render all markdowns to html, and place them in the 'html' directory.

# List of extensions. Codehilite is, as the name suggests, for code highlighting.
extensions = ['codehilite']

# Create the html output directory if it was deleted for some reason
if not os.path.exists('html'):
    os.makedirs('html')

for fname in os.listdir('markdown'):
    # Convert 'markdown/filename.md' to 'filename'
    basename = os.path.splitext(os.path.basename(fname))[0]
    # Output path like 'html/filename.html'
    output_fname = Path('html', basename).with_suffix('.html')
    # First read and render the markdown to html
    with open(Path('markdown', fname), 'r') as f:
        body = markdown(f.read(), extensions=extensions)
    # Then read the template
    with open('scripts/template.html', 'r') as f:
        template = f.read()
    # Then write the result to the output
    with open(output_fname, 'w+') as f:
        f.write(template.format(markdown=body))


import sys

sys.stdin = open('3143_가장빠른문자열타이핑_input.txt')

T = int(input())

for tc in range(1, T + 1):
    A, B = list(map(str, input().split()))

    # banananananana
    # bana

    # 타이핑 + 단축키 사용횟수
    cnt = 0

    # A의 앞부분을 슬라이싱으로 잘라줄 예정
    # A가 B보다 짧아지면 종료
    while len(A) >= len(B):
        tmp = ''
        for i in range(len(B)):
            if A[i] == B[i]:
                tmp += A[i]
        if tmp == B:  # B랑 일치했기 때문에
            A = A[len(B):]  # B의 길이만큼 슬라이싱
            cnt += 1  # 단축키 사용횟수 추가
        else:  # B랑 달랐기 때문에
            A = A[1:]  # 한글자 슬라이싱
            cnt += 1  # 타이핑 횟수 추가
    result = cnt + len(A)  # (단축키+타이핑) + 나머지 글자

    print(f'#{tc} {result}')

3143_가장빠른문자열타이핑.py


# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Initial Owner of the Original Code is European Environment
# Agency (EEA).  Portions created by Finsiel Romania are
# Copyright (C) European Environment Agency.  All
# Rights Reserved.
#
# Authors:
#
# Cornel Nitu, Finsiel Romania
# Dragos Chirila, Finsiel Romania
#
#
#$Id: constants.py 2542 2004-11-11 13:50:03Z finrocvs $

#Python imports
from os.path import join, dirname

#Zope imports

#Product imports
EWAPPLICATIONS_PRODUCT_NAME = 'EnviroWindowsApplications'
EWAPPLICATIONS_PRODUCT_PATH = dirname(__file__)
EWAPPLICATIONS_VAR_PATH = join(CLIENT_HOME, EWAPPLICATIONS_PRODUCT_NAME)

#Meta types
METATYPE_EWAPPLICATIONS = 'EWApplications'
METATYPE_EWAPPLICATION = 'EWApplication'

#Prefixes
PREFIX_EWAPPLICATIONS = 'apps'
PREFIX_EWAPPLICATION = 'app'

#Session key
APPLICATION_DATA = 'application_data'

#Others
APPLICATION_STATUS_PENDING = 'Pending'
APPLICATION_STATUS_APPROVED = 'Approved'
APPLICATION_STATUS_REJECTED = 'Rejected'
APPLICATION_STATUS_MESSAGE_REJECTED = 'portal not created'
APPLICATION_STATUS_MESSAGE_WORKING = 'portal not created, work in progress'
APPLICATION_STATUS_MESSAGE_FINISHED = 'portal successfully created'
APPLICATION_STATUS_MESSAGE_ABORTED = 'portal not created, installation aborted by end user'
APPLICATION_STATUS_MESSAGE_PENDING = 'the application has not been processed yet'


def recursion(s, l, r):
   recursion.counter += 1
   if l >= r:
        return 1
   elif s[l] != s[r]:
        return 0
   else:
        return recursion(s, l+1, r-1)

def isPalindrome(s):
    return recursion(s, 0, len(s)-1)

# print('ABBA:', isPalindrome('ABBA'))
# print('ABC:', isPalindrome('ABC'))

for i in range(int(input())):
    word=input()
    recursion.counter = 0
    print(isPalindrome(word),recursion.counter)


from flask import Flask, render_template, request, redirect, jsonify, url_for
from flask import flash
from functools import wraps

from sqlalchemy import create_engine, asc, desc
from sqlalchemy.orm import sessionmaker
from database_setup import Base, Category, Item, User

# New imports for google security test
from flask import session as login_session
import random
import string
from oauth2client.client import flow_from_clientsecrets
from oauth2client.client import FlowExchangeError
import httplib2
import json
from flask import make_response
import requests
import os
dir_path = os.path.dirname(os.path.realpath(__file__))
app = Flask(__name__)
CLIENT_ID = json.loads(open(dir_path+'/client_secrets.json', 'r').read())[
    'web']['client_id']
APPLICATION_NAME = "Items by Category App"

# Connect to Database and create database session
engine = create_engine('postgresql:///items')
Base.metadata.bind = engine

DBSession = sessionmaker(bind=engine)
session = DBSession()

class Env:
    """
    Env class: used to send parameters to templates in a unifor way
    Args:
        title (data type: str): used in templates for title
    """
    def __init__(self, title=None):
        if 'email' in login_session:
            self.__currentUser = login_session
        else:
            self.__currentUser = []
        if title:
            self.__title = title
        else:
            self.__title = "Categories App"

    @property
    def title(self):
        return self.__title

    @title.setter
    def title(self, title):
        self.__title = title

    @property
    def creator(self):
        return self.__creator

    @creator.setter
    def creator(self, creator):
        self.__creator = creator

    @property
    def currentUser(self):
        return self.__currentUser

    @currentUser.setter
    def currentUser(self, currentUser):
        self.__currentUser = currentUser

@app.route('/login')
def showLogin():
    """
    showLogin: displays login page
    Returns:
        return templates having a state and environment object
    """
    state = ''.join(random.choice(string.ascii_uppercase + string.digits)
                    for x in xrange(32))
    login_session['state'] = state
    env = Env("Login Page")
    # return render_template(dir_path+'/templates/login.html', STATE=state, env=env)
    return render_template('login.html', STATE=state, env=env)

def login_required(f):
    """
    login_required decorator: used to validate if a particular method requires
    start a session
    Args:
        f (data type: function): function to be validated for login
    Returns:
        returns the function to decorate or redirects to login page if access
        is not granted
    """
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'email' in login_session:
            return f(*args, **kwargs)
        else:
            flash('Please log-in first...')
            return redirect('/login')
    return decorated_function

@login_required
def check_creator(category_id=None):
    """
    check_creator method: validates the creator to allow operations such as
    edit and delete
    Args:
        category_id (data type: int): used to get user_id for category
    Returns:
        returns True or False to allow or deny UD operations
    """
    if category_id:
        editCat = session.query(Category).filter_by(id=category_id).one()
        user = editCat.user_id
    if user == login_session['email']:
        return True
    else:
        flash('You are not allowed to change this record')
        return False

# Create a state token to prevent request forgery.
# Store it in the session for latter validation.
@app.route('/fbconnect', methods=['POST'])
def fbconnect():
    """
    fbconnect: provides means to authenticate and authorize via fb apis
    Returns:
        returns the html with the results of the operation
    """
    if request.args.get('state') != login_session['state']:
        response = make_response(json.dumps('Invalid state parameter'), 401)
        response.headers['Content-Type'] = 'application/json'
        return response
    access_token = request.data
    app_id = json.loads(open(dir_path+'/fb_client_secrets.json', 'r').read())[
        'web']['app_id']
    app_secret = json.loads(open(dir_path+'/fb_client_secrets.json', 'r').read())[
        'web']['app_secret']
    url = 'https://graph.facebook.com'
    url += '/oauth/access_token?grant_type=fb_exchange_token'
    url += '&client_id=%s&client_secret=%s&fb_exchange_token=%s' % (
        app_id, app_secret, access_token)
    h = httplib2.Http()
    result = h.request(url, 'GET')[1]
    userinfo_url = 'https://graph.facebook.com/v2.2/me'
    token = result.split('&')[0]
    url = 'https://graph.facebook.com/v2.2/me?%s&fields=name,id,email' % token
    h = httplib2.Http()
    result = h.request(url, 'GET')[1]
    data = json.loads(result)
    login_session['provider'] = 'facebook'
    login_session['username'] = data['name']
    login_session['email'] = data['email']
    login_session['facebook_id'] = data['id']
    # Get picture
    url = 'https://graph.facebook.com/v2.2/me/'
    url += 'picture?%s&redirect=0&height=200&width=200' % token
    h = httplib2.Http()
    result = h.request(url, 'GET')[1]
    data = json.loads(result)
    login_session['picture'] = data['data']['url']

    user_id = getUserID(login_session['email'])
    if not user_id:
        user_id = createUser(login_session)
    login_session['email'] = user_id
    output = ''
    output += 'Welcome, '
    output += login_session['username']
    output += '!'
    output += ''
    flash("You are now logged in as %s" % login_session['username'])
    return output

@app.route('/gconnect', methods=['POST'])
def gconnect():
    """
    gconnect: provides means to authenticate and authorize via google apis
    Returns:
        returns the html with the results of the operation
    """
    # return request.args.get('state')
    # return login_session['state']
    if request.args.get('state') != login_session['state']:
        response = make_response(json.dumps('Invalid state parameter'), 401)
        response.headers['Content-Type'] = 'application/json'
        return response
    # request.get_data()
    code = request.data  # .decode('utf-8')
    try:
        oauth_flow = flow_from_clientsecrets(dir_path+'/client_secrets.json', scope='')
        oauth_flow.redirect_uri = 'postmessage'
        credentals = oauth_flow.step2_exchange(code)
    except FlowExchangeError:
        response = make_response(json.dumps(
            'Failed to upgrade the authorization code', 401))
        response.headers['Content-Type'] = 'application/json'
        return response
    access_token = credentals.access_token
    # 'https://accounts.google.com/o/oauth2/token'
    url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=%s' %
           access_token)
    h = httplib2.Http()
    response = h.request(url, 'GET')[1]
    str_response = response.decode('utf-8')
    result = json.loads(str_response)

    if result.get('error') is not None:
        response = make_response(json.dumps(result('error')), 500)
        response.headers['Content-Type'] = 'application/json'
        return response

    gplus_id = credentals.id_token['sub']
    if result['user_id'] != gplus_id:
        response = make_response(json.dumps(
            'Token\'s user_id does not match given user ID'), 401)
        response.headers['Content-Type'] = 'application/json'
        return response
    if result['issued_to'] != CLIENT_ID:
        response = make_response(json.dumps(
            'Token\'s CLIENT Information does not match app\'s'), 401)
        response.headers['Content-Type'] = 'application/json'
        return response
    stored_access_token = login_session.get('access_token')
    stored_gplus_id = login_session.get('gplus_id')
    if stored_access_token is not None and gplus_id == stored_gplus_id:
        response = make_response(json.dumps(
            'Current user ID already connected'), 200)
        response.headers['Content-Type'] = 'application/json'
        return response

    # Store the access token for later use
    login_session['access_token'] = access_token
    login_session['gplus_id'] = gplus_id

    # Get user info
    userinfo_url = "https://www.googleapis.com/oauth2/v1/userinfo"
    params = {'access_token': access_token, 'alt': 'json'}
    answer = requests.get(userinfo_url, params=params)
    data = answer.json()

    login_session['provider'] = 'google'
    login_session['username'] = data['name']
    login_session['picture'] = data['picture']
    login_session['email'] = data['email']
    user_id = getUserID(login_session['email'])

    if not user_id:
        user_id = createUser(login_session)
    login_session['user_id'] = user_id

    output = ''
    output += 'Welcome, '
    output += login_session['username']
    output += '!'
    output += ''
    flash("You are now logged in as %s" % login_session['username'])
    return output

@app.route('/fbdisconnect')
def fbdisconnect():
    """
    fbconnect: provides means to disconnect from fb
    Returns:
        returns message after disconnected
    """
    # Only disconnect a connected user
    facebook_id = login_session.get('facebook_id')
    url = 'https://graph.facebook.com/%s/permissions' % facebook_id
    h = httplib2.Http()
    result = h.request(url, 'DELETE')[1]

    return 'You have been logged out.'

@app.route('/gdisconnect')
def gdisconnect():
    """
    gdisconnect: provides means to disconnect from google
    Returns:
        returns json message after disconnected
    """
    # Only disconnect a connected user
    credentials = login_session.get('credentials')
    if credentials is None:
        response = make_response(json.dumps(
            'Current user not connected.', 401))
        response.headers['Content-Type'] = 'application/json'
        return response
        access_token = credentials.access_token
        url = 'https://accounts.google.com'
        url += '/o/oauth2/revoke?token=%s' % access_token
        h = httplib2.Http()
        result = h.request(url, 'GET')[0]
        if result['status'] == '200':
            # Reset user session
            response = make_response(json.dumps(
                'Successfully disconnected', 200))
            response.headers['Content-Type'] = 'application/json'
            return response

@app.route('/disconnect')
def disconnect():
    """
    disconnect: generic method used for both fb and google disconnect
    Returns:
        return redirection to categories page
    """
    if 'provider' in login_session:
        if login_session['provider'] == 'google':
            gdisconnect()
            del login_session['gplus_id']
            # del login_session['credentials']
        if login_session['provider'] == 'facebook':
            fbdisconnect()
            del login_session['facebook_id']

        # del login_session['user_id']
        del login_session['username']
        del login_session['email']
        del login_session['picture']
        del login_session['provider']
        flash("You have successfully been logged out.")
        return redirect(url_for('showCategories'))
    else:
        # login_session['provider'] = 'google'
        flash("You were not logged in to begin with!")
        return redirect(url_for('showCategories'))

# PROJECT RELATED
# JSON APIs to view Category Information

@app.route('/category/<int:category_id>/item/JSON')
@login_required
def categoryItemJSON(category_id):
    """
    categoryItemJSON method: displays items related to a category in JSON
    format
    Args:
        category_id (data type: int): category to be used to query items
    Returns:
        returns serialized JSON with a list of Items
    """
    category = session.query(Category).filter_by(id=category_id).one()
    items = session.query(Item).filter_by(category_id=category_id).all()
    return jsonify(Items=[i.serialize for i in items])

@app.route('/item/<int:item_id>/JSON')
@login_required
def itemJSON(item_id):
    """
    itemJSON method: displays a single item based on ID in JSON format
    Args:
        item_id (data type: int): item to be used to query items
    Returns:
        returns serialized JSON with a list of Items
    """
    item = session.query(Item).filter_by(id=item_id).one()
    return jsonify(item=item.serialize)

@app.route('/category/JSON')
@login_required
def categoriesJSON():
    """
    categoriesJSON method: displays all categories in JSON format
    Returns:
        returns serialized JSON with a list of Categories
    """
    categories = session.query(Category).order_by(Category.name).all()
    return jsonify(categories=[c.serialize for c in categories])

@app.route('/user/JSON')
@login_required
def userJSON():
    """
    userJSON method: displays all users in JSON format
    Returns:
        returns serialized JSON with a list of Users
    """
    users = session.query(User).order_by(User.name).all()
    return jsonify(users=[u.serialize for u in users])

@app.route('/allitems/JSON')
@login_required
def allItemsJSON():
    """
    allItemsJSON method: displays all items in JSON format
    Returns:
        returns serialized JSON with a list of Items
    """
    items = session.query(Item).order_by(Item.created_date).all()
    return jsonify(items=[i.serialize for i in items])

# Show all categories
@app.route('/')
@app.route('/category/')
def showCategories():
    """
    showCategories method: generates categories page based on session status
    Returns:
        return categories page
    """
    categories = session.query(Category).order_by(asc(Category.name)).all()
    items = session.query(Item).order_by(desc(Item.created_date)).all()
    env = Env()
    env.title = "Categories"
    if 'username' not in login_session:
        # return render_template('publiccategories.html',
        # categories=categories,items=items,env=env)
        return render_template('categories.html', categories=categories,
                               items=items, env=env)
    else:
        return render_template('categories.html', categories=categories,
                               items=items, env=env)

# Create a new category
@app.route('/category/new/', methods=['GET', 'POST'])
@login_required
def newCategory():
    """
    newCategory method: generates  new category page based on session status
    Returns:
        return new category page
    """
    if 'username' not in login_session:
        return redirect('/login')
    if request.method == 'POST':
        newCategory = Category(
            name=request.form['name'], user_id=login_session['email'])
        session.add(newCategory)
        flash('New Category %s Successfully Created' % newCategory.name)
        session.commit()
        return redirect(url_for('showCategories'))
    else:
        env = Env()
        env.title = "New Categories"
        return render_template('newCategory.html', env=env)

# Edit a category
@app.route('/category/<int:category_id>/edit/', methods=['GET', 'POST'])
@login_required
def editCategory(category_id):
    """
    editCategory method: generates edit category page based on session status
    Args:
    category_id (data type: int): category to be edited
    Returns:
        return edit category page
    """
    if check_creator(category_id):
        editedCategory = session.query(
            Category).filter_by(id=category_id).one()
        if request.method == 'POST':
            if request.form['name']:
                editedCategory.name = request.form['name']
                flash('Category Successfully Edited %s' % editedCategory.name)
                return redirect(url_for('showCategories'))
        else:
            env = Env("Edit Category")
            return render_template('editCategory.html',
                                   category=editedCategory, env=env)
    else:
        return redirect('/category/%s/' % str(category_id))

# Delete a category
@app.route('/category/<int:category_id>/delete/', methods=['GET', 'POST'])
@login_required
def deleteCategory(category_id):
    """
    deleteCategory method: generates  new category page based on session status
    Args:
    category_id (data type: int): category to be deleted
    Returns:
        return new category page
    """
    if check_creator(category_id):
        categoryToDelete = session.query(
            Category).filter_by(id=category_id).one()
        if request.method == 'POST':
            session.delete(categoryToDelete)
            flash('%s Successfully Deleted' % categoryToDelete.name)
            session.commit()
            return redirect(url_for('showCategories', category_id=category_id))
        else:
            env = Env("Delete Category")
            return render_template('deleteCategory.html',
                                   category=categoryToDelete, env=env)
    else:
        return redirect('/category/%s/' % str(category_id))

# Show a category item
@app.route('/category/<int:category_id>/')
@app.route('/category/<int:category_id>/item/')
def showItem(category_id):
    """
    method/class name: short description
    Args:
        arg1 (data type: int/str/ etc): argument description
        etc ...
    Returns:
        return value description
    """
    category = session.query(Category).filter_by(id=category_id).one()
    items = session.query(Item).filter_by(
        category_id=category_id).order_by(asc(Item.created_date)).distinct()
    creator = getUserInfo(category.user_id)
    env = Env()
    env.title = "Items By Category"
    env.creator = creator
    # if 'username' not in login_session or creator.email !=
    # login_session['email']:
    if 'email' not in login_session:
        return render_template('item.html', items=items, category=category,
                               env=env)
    else:
        return render_template('item.html',
                               items=items, category=category, env=env)

# Create a new item
@app.route('/category/<int:category_id>/item/new/', methods=['GET', 'POST'])
@login_required
def newItem(category_id):
    """
    newItem method: generates new item page based on session status
    Args:
    category_id (data type: int): category associated to the item to be
    created.
    Returns:
        return new item page depending on session status
    """
    if check_creator(category_id):
        category = session.query(Category).filter_by(id=category_id).one()
        env = Env()
        env.title = "New Item"
        if request.method == 'POST':
            newItem = Item(name=request.form['name'], category_id=category_id,
                           user_id=category.user_id)
            session.add(newItem)
            session.commit()
            flash('New %s Item Successfully Created' % (newItem.name))
            return redirect(url_for('showItem', category_id=category_id))
        else:
            return render_template('newitem.html',
                                   category_id=category_id, env=env)
    return redirect('/category/%s/item/' % str(category_id))

# Edit a item
@app.route('/category/<int:category_id>/item/<int:item_id>/edit',
           methods=['GET', 'POST'])
@app.route('/category/<int:category_id>/items/<int:item_id>/edit',
           methods=['GET', 'POST'])
@login_required
def editItem(category_id, item_id):
    """
    editItem method: generates edit item page based on session status
    Args:
    category_id (data type: int): category associated to the item to be
    edited.
    Returns:
        return edit item page depending on session status
    """
    if check_creator(category_id):
        editedItem = session.query(Item).filter_by(id=item_id).one()
        category = session.query(Category).filter_by(id=category_id).one()
        if request.method == 'POST':
            if request.form['name']:
                editedItem.name = request.form['name']
            session.add(editedItem)
            session.commit()
            flash('Item Successfully Edited')
            return redirect(url_for('showItem', category_id=category_id))
        else:
            env = Env("Edit Item")
            return render_template('editItem.html',
                                   category_id=category_id,
                                   item=editedItem, env=env)
    else:
        return redirect('/category/%s/item/' % str(category_id))

# Delete a item
@app.route('/category/<int:category_id>/items/<int:item_id>/delete',
           methods=['GET', 'POST'])
@login_required
def deleteItem(category_id, item_id):
    """
    deleteItem method: generates edit item page based on session status
    Args:
    category_id (data type: int): category associated to the item to be deleted
    item_id (data type: int): item to be deleted
    Returns:
        return delete item page depending on session status and redirect to the
        category page when item was deleted
    """
    if check_creator(category_id):
        category = session.query(Category).filter_by(id=category_id).one()
        itemToDelete = session.query(Item).filter_by(id=item_id).one()
        if request.method == 'POST':
            session.delete(itemToDelete)
            session.commit()
            flash('Item Successfully Deleted')
            return redirect(url_for('showItem', category_id=category_id))
        else:
            env = Env("Delete Item")
            return \
                render_template('deleteItem.html', item=itemToDelete, env=env)
    else:
        return redirect('/category/%s/item/' % str(category_id))

# GENERAL STUFF
def getUserID(email):
    """
    getUserID method: get user id based on email
    Args:
    email (data type: str): email to be used to retreive ID
    Returns:
        returns user id
    """
    try:
        user = session.query(User).filter_by(email=email).one()
        return user.email
    except:
        return None

def getUserInfo(user_id):
    """
    getUserInfo method: gets a user object based on user_id
    Args:
    user_id (data type: str): email to be used to retreive user
    Returns:
        returns user object
    """
    try:
        user = session.query(User).filter_by(email=user_id).one()
    except:
        user = None
    return user

def createUser(login_session):
    """
    createUser method: creates user based on login session
    Args:
    login_session (data type: object): login_session to be used to create user
    Returns:
        returns email of newly created user
    """
    newUser = User(name=login_session['username'], email=login_session[
                   'email'], picture=login_session['picture'])
    session.add(newUser)
    session.commit()
    user = session.query(User).filter_by(email=login_session['email']).one()
    return user.email

if __name__ == '__main__':
    app.secret_key = 'super_secret_key'
    app.debug = True
    app.run(host='0.0.0.0', port=80)


# lagrange interpolation example

from __future__ import print_function

import math
import numpy as np
import matplotlib.pyplot as plt

# globals to control some behavior
func_type = "tanh"   # can be sine or tanh
points = "variable"  # can be variable or fixed

npts = 15

def fun_exact(x):
    """ the exact function that we sample to get the points to
    interpolate through """

    if func_type == "sine":
        return np.sin(x)
    elif func_type == "tanh":
        return 0.5*(1.0+np.tanh((x-1.0)/0.1))

def get_interp_points(N, xmin, xmax):
    """ get the x points that we interpolate at """
    if points == "fixed":
        x = np.linspace(xmin, xmax, N)

    elif points == "variable":
        # the Chebyshev nodes
        x = 0.5*(xmin + xmax) + \
            0.5*(xmax - xmin)*np.cos(2.0*np.arange(N)*math.pi/(2*N))

    return x

def lagrange_poly(x, xp, fp):
    """ given points (xp, fp), fit a lagrange polynomial and return
        the value at point x """

    f = 0.0

    # sum over points
    for m in range(len(xp)):

        # create the Lagrange basis polynomial for point m
        l = None

        for n in range(len(xp)):
            if n == m:
                continue

            if l == None:
                l = (x - xp[n])/(xp[m] - xp[n])
            else:
                l *= (x - xp[n])/(xp[m] - xp[n])

        f += fp[m]*l

    return f

if func_type == "sine":
    xmin = 0.0
    xmax = 2.0*math.pi
elif func_type == "tanh":
    xmin = 0.0
    xmax = 2.0

# xp, fp are the points that we build the interpolant from
xp = get_interp_points(npts, xmin, xmax)
fp = fun_exact(xp)

# xx are the finely grided data that we will interpolate at to get
# the interpolated function values ff
xx = np.linspace(xmin, xmax, 200)
ff = np.zeros(len(xx))

for n in range(len(xx)):
    ff[n] = lagrange_poly(xx[n], xp, fp)

# exact function values at the interpolated points
fexact = fun_exact(xx)

# error
e = fexact-ff

plt.subplot(211)

plt.scatter(xp, fp, marker="x", color="r", s=30)
plt.plot(xx, ff, color="k")

plt.plot(xx, fexact, color="0.5")

plt.xlim(xmin, xmax)

plt.subplot(212)

plt.plot(xx, e)

plt.xlim(xmin, xmax)

plt.savefig("lagrange.png")


'''Write a function count_letter (word, letter) which takes a word & letter as
arguments and returns the number of occurrences of that letter in a word.'''
from itertools import count

def countLetter(word, letter):
    count = 0
    if letter in word:
        for i in word:
            if i in letter:
                count += 1
    else:
        print("Not Found")
    count=word.count(letter) #   Second Way of doing the same thing
    return count

word = input("Enter the word :")
letter = input("Enter the letter : ")
print("Occurence of {} in {} is :".format(letter, word), countLetter(word, letter))


""""
We will use python predefined data structure heapq to implement Priority Queue
we will use array [task, priority, is_deleted] to represent each item
"""

from heapq import *

class PriorityQueue(object):

    def __init__(self, is_min_heap):
        self.task_dict = {}
        self.priority_queue = []
        if is_min_heap:
            self.mul = 1
        else:
            self.mul = -1

    def is_contains_task(self, task):
        if task in self.task_dict:
            return True
        return False

    def add_task(self, priority, task):
        if self.is_contains_task(task):
            raise KeyError("Task already present")
        entry = [self.mul * priority, task, False]
        self.task_dict[task] = entry
        heappush(self.priority_queue, entry)

    def remove_task(self, task):
        if not self.is_contains_task(task):
            raise KeyError("Task not present to remove")
        entry = self.task_dict.pop(task)
        entry[2] = True

    def pop_task(self):
        while self.priority_queue:
            priority, task, is_removed = heappop(self.priority_queue)
            if not is_removed:
                del self.task_dict[task]
                return task
        raise KeyError("Priority Queue is empty to be pop")

    def peek_task(self):
        while self.priority_queue:
            priority, task, is_removed = heappop(self.priority_queue)
            if not is_removed:
                heappush(self.priority_queue, [priority, task, is_removed])
                return task
        raise KeyError("Priority Queue is empty to be peek")

    def remove_task(self, task):
        entry = self.task_dict.pop(task)
        entry[2] = True

    def change_priority(self, priority, task):
        if not self.is_contains_task(task):
            raise KeyError("Task not present to change the priority")
        self.remove_task(task)
        entry = [self.mul * priority, task, False]
        self.task_dict[task] = entry
        heappush(self.priority_queue, entry)

    def is_empty(self):
        try:
            self.peek_task()
            return False
        except KeyError:
            return True

if __name__ == "__main__":
    min_pq = PriorityQueue(True)
    task1 = "play vlc"
    task2 = "search another planet and identity its speed using ML"
    task3 = "cleanup the room"
    task4 = "bring a cup of coffee"
    task5 = "scan for any spacecraft issue"

    min_pq.add_task(3, task1)
    min_pq.add_task(1, task2)
    min_pq.add_task(2, task4)

    min_pq.change_priority(0, task1)

    while min_pq.is_empty() is False:
        print(min_pq.pop_task())

    # min_pq.change_priority(0, task1)

    # while min_pq.is_empty() is False:
    #     print(min_pq.pop_task())


from django.contrib.auth import get_user_model

User = get_user_model()

def resolve_user(parent, info, **kwargs):
    pk = kwargs.get('id')
    email = kwargs.get('email')

    if pk is not None:
        return User.objects.get(pk=pk)

    if email is not None:
        return User.objects.get(email=email)


from abc import ABC

from Data.Entities import ChatHistory
from Repositories.IRepositoryBase import IRepositoryBase
from Repositories.RepositoryBase import RepositoryBase
from Service.Abstraction.IMessageService import IMessageService

class MessageService(IMessageService, ABC):
    def __init__(self):
        self.message_repo: IRepositoryBase = RepositoryBase(ChatHistory)

    def save_message(self, message: dict):
        try:
            msg: ChatHistory = ChatHistory()
            max_pk = self.message_repo.max(ChatHistory.id)
            if max_pk:
                msg.id = max_pk + 1
            else:
                msg.id = 1
            msg.sender = message['sender']
            msg.receiver = message['receiver']
            msg.message = message['message']
            self.message_repo.add(msg)
        except Exception as e:
            print(str(e))


from .serverConn import serverConn
from .buryTelligence import buryTelligence
import mysql.connector
import time
import random

class buryBotManager:
    """
    This class manages the connection between the chatbot and the server. It takes in the following constructor parameters.

    Args:
        folder_path(string): The location of the json_files
        json_files(list of string): The list of json_files
        debug(bool): Debug on or not.
        option(int): Option. If debug is true, option does nothing
    """
    def __init__(self,folder_path,json_files,debug=False,option=0,clearMemory=True):
        print("Initializing connection...")
        self.myConn = serverConn()
        print("Initializing intelligence...")
        self.bot = buryTelligence(folder_path=folder_path,json_files=json_files,debug=debug)

        self.clearMemory = clearMemory

        self.printCat()
        if(debug == True):
            print("You are simulating. No changes will be made to the server")
            self.bot.debugSimulate()
        elif(option == 1):
            print("Burybot is going to respond to the latest post")
            self.respondToLatestPost()
        elif(option == 2):
            print("Burybot is going to work very hard to post to everyone")
            self.runForevea()
        else:
            print("Burybot is lazy and going to go to sleep")
            self.doNothing()

    def respondToLatestPost(self):
        serverResponse = self.myConn.myQuery(que="SELECT threadReference,messageId,messageContent FROM messageList \
                                            WHERE messageOwner != -1 and ((buryBotCode>>1)&1) != 1 ORDER BY messageId DESC LIMIT 1");
        if(serverResponse == None):
            return -1
        if(len(serverResponse) == 0):
            return 1

        res = serverResponse[0]

        print("[-] replying to post "+str(res[1])+"...")

        newMsg = self.bot.get_most_similar_response(res[2])
        newMsg = ("#"+str(res[1])+"\n"+newMsg)
        self.myConn.myQuery(que="INSERT INTO messageList(threadReference,messageContent,messageOwner,userReference,hashed_ip,buryBotCode) \
                                VALUES(%s,%s,-1,-1,-1,1)",values=(res[0],newMsg))
        #extra comma for tuple definition
        self.myConn.myQuery(que="UPDATE threadList \
                                SET threadSize = threadSize+1 \
                                WHERE threadId=%s",values=(res[0],))
        self.myConn.myQuery(que="UPDATE messageList \
                                SET buryBotCode=(buryBotCode | (1<<1)) \
                                WHERE messageId={}".format(str(res[1])))
        self.myConn.updateSQL()
        print("[+] finish replying to post "+str(res[1]))
        return 0

    def runForevea(self):
        timer = input("Minutes inbetween each post \n>>> ")
        timer = 60*float(timer)

        shutdown = input("Shutdown? Type 0 to shutdown. Else, type minutes till next check\n>>> ")
        shutdown = 60*float(shutdown)

        while(1):
            print("\nCURRENT TIME: ",time.ctime())
            tmp = self.respondToLatestPost();
            nextTime = timer
            if(tmp == -1): break
            elif(tmp == 0):
                print("Bot will talk again on",time.ctime(time.time()+timer))
            elif(tmp == 1):
                if(shutdown == 0): break
                else:
                    print("Bot FELL ASLEEP...")
                    print("Bot will talk again on",time.ctime(time.time()+shutdown))
                    nextTime = shutdown
                    print("Bot WOKE UP 0<:O")
            if(self.clearMemory):
                print("clearing memory")
                self.bot.clearMem()
            time.sleep(nextTime)
            self.restartConnection()

    def restartConnection(self):
        self.myConn = serverConn()

    def doNothing(self):
        print("Initialization complete...\nEverything should work. Please use:")
        print_highlighted("  docker compose run --rm burybot [opt]")
        print("to run your burybot")
        return

    def printCat(self):
        eyes = ["o","-",".","V","/",]
        randomEye = random.randint(0,len(eyes)-1)
        cheeks = ["//","==","..","  "]
        randomCheek = random.randint(0,len(cheeks)-1)

        print(34*"_")
        print("This is your not so random cat...")
        print("    /\_____/\\")
        print("   /  {}   {}  \\".format(eyes[randomEye],eyes[randomEye]))
        print("  ( {}  ^  {} )".format(cheeks[randomCheek],cheeks[randomCheek]))
        print(34*"_")

def print_highlighted(text, color="white", background="light_grey"):
    colors = {
        "black": "30",
        "red": "31",
        "green": "32",
        "yellow": "33",
        "blue": "34",
        "magenta": "35",
        "cyan": "36",
        "white": "37",
        "grey": "90",
        "light_grey": "37"
    }
    backgrounds = {
        "black": "40",
        "red": "41",
        "green": "42",
        "yellow": "43",
        "blue": "44",
        "magenta": "45",
        "cyan": "46",
        "white": "47"
    }

    color_code = colors.get(color, "33")  # Default to yellow if color is not recognized
    background_code = backgrounds.get(background, "40")  # Default to black if background is not recognized

    try:
        highlighted_text = f"\033[{color_code};{background_code}m{text}\033[0m"
        print(highlighted_text)
    except:
        # If formatting fails (no ANSI support), print the text without formatting
        print(text)