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

"""
test_autolysis
----------------------------------

Tests for `autolysis` module.
"""
from __future__ import absolute_import, division, print_function

import os
import re
import logging
import traceback
import pandas as pd
import autolysis as al
import sqlalchemy as sa
from odo import odo
from blaze import Data
from nose.tools import eq_, ok_
from numpy.testing import assert_array_almost_equal as aaq_

from . import DATA_DIR, config, server_exists

def setUpModule():
    "Download test data files into data/ target folder"

    # Set root logger logging level to INFO
    logging.basicConfig(level=logging.INFO)

    # Download datasets
    for dataset in config['datasets']:
        dataset['path'] = os.path.join(DATA_DIR, dataset['table'] + '.csv')
        dataset['uris'] = [dataset['path']]
        if not os.path.exists(dataset['path']):
            logging.info('Downloading %s', dataset['table'])
            pd.read_csv(dataset['url'], encoding='cp1252').to_csv(
                dataset['path'], index=False, encoding='cp1252')

    # Create autolysis databases (sqlite3 data directory is DATA_DIR)
    os.chdir(DATA_DIR)
    dburl = {}
    for db, url in config['databases'].items():
        if not server_exists(url):
            continue
        dburl[db] = url

    # Load datasets into databases
    for dataset in config['datasets']:
        for db in dataset.get('databases', []):
            if db not in dburl:
                logging.warning('%s cannot use unconfigured DB %s', dataset['table'], db)
                continue
            url = dburl[db]

            # Don't load data if a non-empty table already exists
            target = dburl[db] + '::' + dataset['table']
            dataset['uris'].append(target)
            engine = sa.create_engine(url, encoding='utf-8')
            if engine.dialect.has_table(engine.connect(), dataset['table']):
                if Data(target).count() > 0:
                    continue
            logging.info('Creating table %s on %s', dataset['table'], db)
            try:
                odo(dataset['path'], target)
            except sa.exc.InternalError:
                logging.warning('Loading %s into %s failed: %s',
                                dataset['table'], db, traceback.format_exc(0))

def getengine(uris):
    'Return sql engine or csv for given uri'
    engines = []
    for uri in uris:
        engines.append(
            re.sub(
                r'[^a-zA-Z+]',
                r'',
                re.search(r'([\w|+]+:\/\/)|(.csv)', uri).group())
            )
    return engines

class TestImport(object):
    "Test autolysis import basics"
    def test_version(self):
        "autolysis has a version"
        ok_(hasattr(al, '__version__'))

    def test_release(self):
        "autolysis has a release information dict"
        ok_(hasattr(al, 'release'))
        ok_(isinstance(al.release, dict))

class TestGetNumericCols(object):
    "Test autolysis.get_numeric_cols"
    def test_numeric_cols(self):
        for dataset in config['datasets']:
            for uri in dataset['uris']:
                data = Data(uri)
                result = al.get_numeric_cols(data.dshape)
                eq_(set(result), set(dataset['types']['numbers']))
            print('for %s on %s' % (dataset['table'], getengine(dataset['uris'])))

class TestTypes(object):
    "Test autolysis.types"
    def check_type(self, result, expected, msg):
        "result = expected, but order does not matter. Both are dict of lists"
        eq_(set(result.keys()),
            set(expected.keys()), 'Mismatch: %s keys' % msg)
        for key in expected:
            eq_(set(result[key]),
                set(expected[key]), 'Mismatch: %s - %s' % (msg, key))

    def test_detect_types(self):
        for dataset in config['datasets']:
            for uri in dataset['uris']:
                data = Data(uri)
                result = al.types(data)
                self.check_type(result, dataset['types'], dataset['table'])
            print('for %s on %s' % (dataset['table'], getengine(dataset['uris'])))

class TestGroupMeans(object):
    "Test autolysis.groupmeans"
    def check_gain(self, result, expected, msg):
        gains = []
        while True:
            try:
                item = next(result)
                gains.append(item['gain'])
            except StopIteration:
                break
        if len(gains):
            # Ignoring type check, SQL might return Decimal
            # TODO: Sorting & comparing might not be a good idea.
            # Look for alternative comparison. create a series instead
            aaq_([float(i) for i in sorted(gains)],
                 sorted(expected),
                 4,
                 'Mismatch with URI: %s ' % msg)
        else:
            eq_([], expected, 'Mismatch with URI: %s ' % msg)

    def test_gains(self):
        for dataset in config['datasets']:
            for uri in dataset['uris']:
                data = Data(uri)
                types = al.types(data)
                result = al.groupmeans(data, types['groups'], types['numbers'])
                self.check_gain(result, dataset['groupmeans']['gain'], uri)
            print('for %s on %s' % (dataset['table'], getengine(dataset['uris'])))

    def test_gains_changed_types(self):
        # Issue #24
        for dataset in config['datasets']:
            if 'changedtypes' not in dataset:
                continue
            for uri in dataset['uris']:
                data = Data(uri)
                types = dataset['changedtypes']
                result = al.groupmeans(data, types['groups'], types['numbers'])
                self.check_gain(result, types['groupmeans']['gain'], uri)
            print('for %s on %s' % (dataset['table'], getengine(dataset['uris'])))

class TestCrossTabs(object):
    "Test autolysis.crosstabs"
    def check_stats(self, result, exp, msg):
        if result:
            while True:
                try:
                    obs = next(result)
                    exp_stats = exp[(exp['index'] == obs['index']) &
                                    (exp['column'] == obs['column'])]['stats'].values[0]
                    eq_(obs['stats'], exp_stats, 'Test Mismatch with URI: %s ' % msg)
                except StopIteration:
                    break
        else:
            eq_([], exp, 'Mismatch with URI: %s ' % msg)

    def test_stats(self):
        for dataset in config['datasets']:
            for uri in dataset['uris']:
                data = Data(uri)
                groups = dataset['types']['groups']
                result = al.crosstabs(data, groups, details=False)
                expected = pd.DataFrame(dataset['crosstabs'])
                self.check_stats(result, expected, uri)
            print('for %s on %s' % (dataset['table'], getengine(dataset['uris'])))


# policy_iteration.py
"""Volume 2: Policy Function Iteration.
<Name> Dallin Seyfried
<Class> 001
<Date> 04/13/2023
"""

import numpy as np
import gym
from gym import wrappers

# Intialize P for test example
#Left =0
#Down = 1
#Right = 2
#Up= 3

P = {s : {a: [] for a in range(4)} for s in range(4)}
P[0][0] = [(0, 0, 0, False)]
P[0][1] = [(1, 2, -1, False)]
P[0][2] = [(1, 1, 0, False)]
P[0][3] = [(0, 0, 0, False)]
P[1][0] = [(1, 0, -1, False)]
P[1][1] = [(1, 3, 1, True)]
P[1][2] = [(0, 0, 0, False)]
P[1][3] = [(0, 0, 0, False)]
P[2][0] = [(0, 0, 0, False)]
P[2][1] = [(0, 0, 0, False)]
P[2][2] = [(1, 3, 1, True)]
P[2][3] = [(1, 0, 0, False)]
P[3][0] = [(0, 0, 0, True)]
P[3][1] = [(0, 0, 0, True)]
P[3][2] = [(0, 0, 0, True)]
P[3][3] = [(0, 0, 0, True)]

# Problem 1
def value_iteration(P, nS ,nA, beta = 1, tol=1e-8, maxiter=3000):
    """Perform Value Iteration according to the Bellman optimality principle.

    Parameters:
        P (dict): The Markov relationship
                (P[state][action] = [(prob, nextstate, reward, is_terminal)...]).
        nS (int): The number of states.
        nA (int): The number of actions.
        beta (float): The discount rate (between 0 and 1).
        tol (float): The stopping criteria for the value iteration.
        maxiter (int): The maximum number of iterations.

    Returns:
       v (ndarray): The discrete values for the true value function.
       n (int): number of iterations
    """
    # Initialize value function vectors
    V_olds = np.zeros(nS)
    V_new = np.zeros(nS)
    k = 0

    for k in range(maxiter):
        for s in range(nS):
            sa_vector = np.zeros(nA)
            for a in range(nA):
                for tuple_info in P[s][a]:
                    # tuple_info is a tuple of (probability, next state, reward, done)
                    p, s_, u, _ = tuple_info
                    # sums up the possible end states and rewards with given action
                    sa_vector[a] += (p * (u + beta * V_olds[s_]))
                # Add the max value to the value function
                V_new[s] = np.max(sa_vector)
        # Check to see if vectors match, update and repeat if not
        if np.linalg.norm(V_new - V_olds) < tol:
            break
        V_olds = V_new.copy()

    return V_new, k + 1

# Test Problem 1
def test_value_iteration():
    print('\n')
    print(value_iteration(P=P, nS=4, nA=4, beta=1))

# Problem 2
def extract_policy(P, nS, nA, v, beta = 1.0):
    """Returns the optimal policy vector for value function v

    Parameters:
        P (dict): The Markov relationship
                (P[state][action] = [(prob, nextstate, reward, is_terminal)...]).
        nS (int): The number of states.
        nA (int): The number of actions.
        v (ndarray): The value function values - optimal V* from value iteration.
        beta (float): The discount rate (between 0 and 1).

    Returns:
        policy (ndarray): which direction to move in from each square.
    """
    # Initialize policy vector
    policy = np.zeros(nS, dtype=int)
    for s in range(nS):
        sa_vector = np.zeros(nA)
        for a in range(nA):
            for tuple_info in P[s][a]:
                # tuple_info is a tuple of (probability, next state, reward, done)
                p, s_, u, _ = tuple_info
                # sums up the possible end states and rewards with given action
                sa_vector[a] += (p * (u + beta * v[s_]))
            # Add the max value to the value function
            policy[s] = np.argmax(sa_vector)

    return policy

# Test Problem 2
def test_extract_policy():
    print('\n')
    v = value_iteration(P=P, nS=4, nA=4)[0]
    print(extract_policy(P=P, nS=4, nA=4, v=v))

# Problem 3
def compute_policy_v(P, nS, nA, policy, beta=1.0, tol=1e-8):
    """Computes the value function for a policy using policy evaluation.

    Parameters:
        P (dict): The Markov relationship
                (P[state][action] = [(prob, nextstate, reward, is_terminal)...]).
        nS (int): The number of states.
        nA (int): The number of actions.
        policy (ndarray): The policy to estimate the value function.
        beta (float): The discount rate (between 0 and 1).
        tol (float): The stopping criteria for the value iteration.

    Returns:
        v (ndarray): The discrete values for the true value function.
    """
    # Define values
    V_olds = np.zeros(nS)
    V_new = np.ones(nS)

    # Iterate until Values found
    while np.linalg.norm(V_new - V_olds) > tol:
        V_olds = V_new.copy()

        for s in range(nS):
            # Optimal action already known so
            # maximizing for loop not needed
            a = policy[s]
            sa_value = 0

            # Cycle through each action's potential outcome
            for tuple_info in P[s][a]:
                p, s_, u, _ = tuple_info
                sa_value += (p * (u + beta * V_olds[s_]))

            # Record value in function
            V_new[s] = sa_value

    return V_new

# Test Problem 3
def test_compute_policy():
    print('\n')
    v = value_iteration(P=P, nS=4, nA=4)[0]
    print(f'Original Value: {v}\n')
    policy = extract_policy(P=P, nS=4, nA=4, v=v)
    print(f'Recomputed Value: {compute_policy_v(P=P, nS=4, nA=4, policy=policy, beta=1.0, tol=1e-8)}')

# Problem 4
def policy_iteration(P, nS, nA, beta=1, tol=1e-8, maxiter=200):
    """Perform Policy Iteration according to the Bellman optimality principle.

    Parameters:
        P (dict): The Markov relationship
                (P[state][action] = [(prob, nextstate, reward, is_terminal)...]).
        nS (int): The number of states.
        nA (int): The number of actions.
        beta (float): The discount rate (between 0 and 1).
        tol (float): The stopping criteria for the value iteration.
        maxiter (int): The maximum number of iterations.

    Returns:
    	v (ndarray): The discrete values for the true value function
        policy (ndarray): which direction to move in each square.
        n (int): number of iterations
    """
    # Initialize policy with common choice
    p_new = np.random.choice(nA, size=nS)
    v_new = p_new.copy()
    k = 0

    # Iterate up to maxiter times to find optimal policy
    for k in range(maxiter):
        # Evaluate policy and improve upon it
        v_k = compute_policy_v(P, nS, nA, p_new, beta, tol)
        p_k = extract_policy(P, nS, nA, v_k, beta)

        # If policy is close enough return
        if np.linalg.norm(p_k - p_new) < tol:
            p_new = p_k
            v_new = v_k
            break

        p_new = p_k.copy()

    return v_new, p_new, k

# Test Problem 4
def test_policy_iteration():
    print('\n')
    print(f'Value Iteration: {value_iteration(P=P, nS=4, nA=4, beta=1)}')
    print('\n')
    v = value_iteration(P=P, nS=4, nA=4)[0]
    print(f'Extract Policy: {extract_policy(P=P, nS=4, nA=4, v=v)}')
    v_new, p_new = policy_iteration(P=P, nS=4, nA=4)
    print('\n')
    print(f'v_new {v_new}\n')
    print(f'p_new {p_new}\n')

# Problem 5 and 6
def frozen_lake(basic_case=True, M=1000, render=False):
    """ Finds the optimal policy to solve the FrozenLake problem

    Parameters:
    basic_case (boolean): True for 4x4 and False for 8x8 environemtns.
    M (int): The number of times to run the simulation using problem 6.
    render (boolean): Whether to draw the environment.

    Returns:
    vi_policy (ndarray): The optimal policy for value iteration.
    vi_total_rewards (float): The mean expected value for following the value iteration optimal policy.
    pi_value_func (ndarray): The maximum value function for the optimal policy from policy iteration.
    pi_policy (ndarray): The optimal policy for policy iteration.
    pi_total_rewards (float): The mean expected value for following the policy iteration optimal policy.
    """
    # Make environment for 4x4 scenario or 8x8 as appropriate
    env_name = 'FrozenLake8x8-v1'
    if basic_case:
        env_name = 'FrozenLake-v1'
    env = gym.make(env_name).env

    # Put environment in starting state
    obs = env.reset()

    # Find number of states and actions
    nS = env.observation_space.n
    nA = env.action_space.n

    # Get the dictionary with all the states and actions
    P = env.P

    # Compute policy via value iteration
    vi_policy = extract_policy(P, nS, nA, value_iteration(P, nS, nA)[0])

    # Compute value, policy, and rewards via policy iteration
    pi_value_func, pi_policy, k = policy_iteration(P, nS, nA)

    # Get rewards for policies
    value_reward = []
    policy_reward = []
    for i in range(M):
        value_reward.append(run_simulation(env, vi_policy, render))
        policy_reward.append(run_simulation(env, pi_policy, render))

    value_mean = np.mean(value_reward)
    policy_mean = np.mean(policy_reward)

    env.close()

    return vi_policy, value_mean, pi_value_func, pi_policy, policy_mean

# Problem 6
def run_simulation(env, policy, render=True, beta = 1.0):
    """ Evaluates policy by using it to run a simulation and calculate the reward.

    Parameters:
    env (gym environment): The gym environment.
    policy (ndarray): The policy used to simulate.
    beta float: The discount factor.
    render (boolean): Whether to draw the environment.

    Returns:
    total reward (float): Value of the total reward received under policy.
    """
    done = False
    total_reward = 0
    obs = env.reset()[0]
    k = 1

    # Cycle to find total reward then return it
    while not done:
        obs, reward, done, _, _ = env.step(int(policy[obs]))
        total_reward += reward * beta**k
        k += 1

        if render:
            env.render()

    return total_reward

# Test Problem 5 & 6
def test_frozen_lake():
    print('\n')
    print(frozen_lake(M=1, render=True))


from __future__ import absolute_import

import io
import ssl
import errno
import socket
import struct
import threading

from .. import logs
from .. import utils
from .. import errors
from .. import param

from . import Connection, Transport

BACKLOG = socket.SOMAXCONN
CHUNK_SIZE = io.DEFAULT_BUFFER_SIZE
ACCEPT_TIMEOUT = 0.1

log = logs.get(__name__)

class TcpConnection(Connection):
    log = log

    def __init__(self, interface, sock, url, chunk_size=None):
        super(TcpConnection, self).__init__(interface, url)
        self._sock = sock
        self._chunk_size = chunk_size
        self.log.debug('connected: %s', self.url)

    def recv(self):
        return recv(self._sock, self._chunk_size)

    def send(self, data):
        try:
            return send(self._sock, data)
        except socket.error as e:
            raise errors.TransportError(e)

    def close(self):
        close(self._sock)
        self.log.debug('disconnected: %s', self.url)

class TcpTransport(Transport):
    _name_ = 'tcp'
    log = log
    Connection = TcpConnection

    @param('backlog', int, default=BACKLOG)
    @param('chunk_size', int, default=CHUNK_SIZE)
    @param('ssl_key', doc='server-side only')
    def __init__(self, url, timeout=None, backlog=None, chunk_size=None,
            ssl_cert=None, ssl_key=None):
        super(TcpTransport, self).__init__(url)
        url = self._url

        self._addr = (url.host, url.port)
        self._sock = None

        self.timeout = timeout
        self.backlog = backlog or BACKLOG
        self.chunk_size = chunk_size

        self._ssl_cert = ssl_cert
        self._ssl_key = ssl_key

        self._stop = threading.Event()
        self._stopped = threading.Event()

    def connect(self, client):
        s = socket.create_connection(self._addr, self.timeout)

        if self._ssl_cert:
            ctx = ssl.create_default_context()
            ctx.load_verify_locations(self._ssl_cert)
            hostname = socket.gethostbyaddr(self._addr[0])[0]
            s = ctx.wrap_socket(s, server_hostname=hostname)

        return self.Connection(client, s, self._url.netloc, self.chunk_size)

    def bind(self):
        self._sock = s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(ACCEPT_TIMEOUT)
        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s.bind(self._addr)
        s.listen(self.backlog)

    def serve(self, server):
        self.bind()
        self.log.info('listening: %s', self.url)

        s = self._sock

        ctx = None
        if self._ssl_cert:
            ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
            ctx.load_cert_chain(certfile=self._ssl_cert, keyfile=self._ssl_key)

        stop = self._stop
        stopped = self._stopped
        stop.clear()
        stopped.clear()

        try:
            while not stop.is_set():
                try:
                    sock, addr = s.accept()
                except socket.timeout:
                    continue
                sock.settimeout(self.timeout)

                try:
                    if ctx:
                        sock = ctx.wrap_socket(sock, server_side=True)
                except ssl.SSLError:
                    log.exception('ssl error')
                    continue

                utils.start_thread(self.handle, server, sock, addr)
        finally:
            stopped.set()

    def handle(self, server, sock, addr):
        addr = utils.url.format_addr(addr)
        with self.Connection(server, sock, addr, self.chunk_size) as con:
            server.handle(con)

    def stop(self):
        self._stop.set()

    def join(self, timeout=None):
        self._stopped.wait(timeout)

##
## sockio
##

def close(sock):
    try:
        sock.shutdown(socket.SHUT_RDWR)
    except (OSError, socket.error) as e:
        # ignore if not connected
        if e.errno not in (errno.ENOTCONN,):# errno.EBADF):
            raise
    sock.close()

def recv(sock, chunk_size=None):
    """Receives a block of data from the socket."""
    try:
        return b''.join(recviter(sock, chunk_size))
    except errors.ReceiveInterrupted:
        return b''

def recviter(sock, chunk_size=None):
    """Returns an iterator over the data received from the socket.

    Each chunk of data yielded will be equal to or less than
    *chunk_size*.

    Can raise a `ReceiveInterrupted` exception.
    """
    buf = b''.join(recvsize(sock, 4, chunk_size))
    data_len = struct.unpack('>I', buf)[0]
    for chunk in recvsize(sock, data_len, chunk_size):
        yield chunk

def recvsize(sock, size, chunk_size=None):
    """Returns an iterator over the chunks received from the socket.

    If the socket stops receiving data before *size* bytes have been read, a
    `ReceiveInterrupted` exception will be raised.
    """
    pos = 0
    chunk_size = min(size, chunk_size or CHUNK_SIZE)
    while pos < size:
        chunk = sock.recv(min(size-pos, chunk_size))
        if not chunk:
            raise errors.ReceiveInterrupted()
        pos += len(chunk)
        yield chunk

def send(sock, data):
    """Sends *data* over the socket."""
    data_len = len(data)
    size = struct.pack('>I', data_len)
    sock.sendall(size)
    sock.sendall(data)


"""
https://tools.ietf.org/html/rfc4566
"""
from collections import namedtuple
import re
import io

TIMESRE = re.compile('(-?\d+)([dhms]{0,1})')

class FieldSpec(object):

    def __init__(self, long_name, description='', required=True, multiple=False, short_name=''):
        if not short_name:
            short_name = long_name[0]
        self.long_name = long_name
        self.short_name = short_name
        self.description = description
        self.required = required
        self.multiple = multiple

class SectionSpec(object):

    def __init__(self, name, dsecription='', required=True, multiple=False):
        self.name = name
        self.description = description
        self.required = required
        self.multiple = muliple
        self.fields = fields

time_spec = SectionSpec(
    'Time description',
    'one or more time descriptions',
    required=True,
    multiple=True,
)

time_spec.fields = [
   FieldSpec('time', 't', 'time the session is acive', True, False),
   FieldSpec('repeat', 'r', 'zero or more repeat times', False, False),
]

media_spec = SectionSpec(
    'Media description',
    'zero or more media descriptions',
    required=False,
    multiple=True
)

media_spec.fields = [
    FieldSpec('media', 'm', 'media name and transport address', True, False),
    FieldSpec('title', 'i', 'media title', False, False),
    FieldSpec('connection_info', 'c', 'connection information -- optional if included at session level', False, False),
    FieldSpec('bandwidth', 'b', 'zero or more bandwidth information lines', False, False),
    FieldSpec('key', 'k', 'encryption key', False, False),
    FieldSpec('attribute', 'a', 'zero or more media attribute lines', False, False),
]

sdp_spec = SectionSpec(
    'Session description',
    'SDP Message',
    required=True,
    multiple=False
)

sdp_spec.fields = [
    FieldSpec('version', 'v', 'protocol version', True, False),
    FieldSpec('origin', 'o', 'originator and session identifier', True, False),
    FieldSpec('session_name', 's', 'session name', True, False),
    FieldSpec('session_info', 'i', 'session information', False, False),
    FieldSpec('url', 'u', 'URI of description', False, False),
    FieldSpec('email', 'e', 'email address', False, False),
    FieldSpec('phone', 'p', 'phone number', False, False),
    FieldSpec('connection_info', 'c', 'connection information -- not required if included in all media', False, False),
    FieldSpec('bandwidth', 'b', 'zero or more bandwidth information lines', False, False),
    time_spec,
    FieldSpec('tz_adjustments', 'z', 'time zone adjustments', False, False),
    FieldSpec('key', 'k', 'encryption key', False, False),
    FieldSpec('attribute', 'a', 'zero or more session attribute lines', False, True),
    media_spec,
]

_name_to_cls = {}

def parse_sdp(val, desc=sdp_desc):
    fp = io.StringIO(val)
    n = 0
    field = desc.fields[n]
    for line in fp:
        if field.required:
            x = (field.name, line)
        print(repr(line))

class SectionValidator(object):

    def __init__(self, desc):
        self.desc = desc
        self._diter = self.iter_desc(desc)

    def next_desc(self):
        return next(self._diter)

    def __call__(self, rawsdp):
        lines = io.StringIO(s)
        desc = self.next_desc()
        for line in lines:
            name, val = line.strip('\n').split('=', 1)

    @staticmethod
    def iter_desc(*fields):
        for a in fields:
            if type(a) == _S:
                for b in iter_desc(*a.fields):
                    yield b
            else:
                yield a

class FieldValidator(object):

    def __init__(self, desc):
        self.desc = desc

    def __call__(self, rawsdp):
        lines = io.StringIO(s)
        for line in lines:
            name, val = line.strip('\n').split('=', 1)

class Loader(object):

    def __init__(self, specs, classes):
        self.specs = specs
        self.classes = classes
        self._current_spec = None

    def __call__(self, value):
        spec = self.next_spec()

    def next_spec(self):
        if self._current_spec:
            return self._current_spec

class SdpValue(object):
    pass

    @classmethod
    def consume(cls, value):
        line, leftover = value.split('\n', 1)
        name, val = line.split('=', 1)
        if name == cls.spec.name:
            return cls.load_val(val), leftover
        return None, value

    @staticmethod
    def _load_str(val):
        return val

    @staticmethod
    def _load_int(val):
        return int(val)

    @staticmethod
    def _load_enum(val, enums):
        if val in enums:
            return val
        raise Exception("Unable to laod value")

class ProtocolVersion(SdpValue):

    def __init__(self, version=0):
        self.version = version

    def dumps(self):
        return '{}'.format(self.spec.name, self.version)

    @classmethod
    def load_val(cls, val):
        return cls(int(val))

_name_to_cls['v'] = ProtocolVersion

class Origin(SdpValue):

    def __init__(self, username, sess_id, sess_version, nettype, addrtype, unicast_address):
        self.username = username
        self.sess_id = sess_id
        self.sess_version = sess_version
        self.nettype = nettype
        self.addrtype = addrtype
        self.unicast_address = unicast_address

    def dumps(self):
        return ' '.join([
            self.username,
            str(self.sess_id),
            str(self.session_version),
            self.nettype,
            self.addrtype,
            self.unicast_address,
        ])

    @classmethod
    def loads(cls, val):
        fields = [x.strip() for x in val.split(' ') if x.strip()]
        return cls(
            cls._load_str(fields[0]),
            cls._load_int(fields[1]),
            cls._load_int(fields[2]),
            cls._load_enum(fields[3], ('IN',)),
            cls._load_enum(fields[4], ('IPV4', 'IPV6')),
            cls._load_str(fields[5]),
        )

class SessionName(SdpValue):

    def __init__(self, value, encoding='utf-8'):
        self.value = value
        self.encoding = encoding

    def dumps(self):
        return '{}'.format(self.value)

    @classmethod
    def loads(cls, val):
        return cls(val)

class Uri(SdpValue):

    def __init__(self, value):
        self.value = value

    def dumps(self):
        return '{}'.format(self.value)

    @classmethod
    def loads(cls, val):
        return cls(val)

class Email(SdpValue):

    def __init__(self, value):
        self.value = value

    def dumps(self):
        return '{}'.format(self.value)

    @classmethod
    def loads(cls, val):
        return cls(val)

class PhoneNumber(SdpValue):

    def __init__(self, value):
        self.value = value

    def dumps(self):
        return '{}'.format(self.value)

    @classmethod
    def loads(cls, val):
        return cls(val)

class ConnectionData(SdpValue):

    def __init__(self, nettype, addrtype, connection_address):
        self.nettype = nettype
        self.addrtype = addrtype
        self.connection_address = connection_address

    def dumps(self):
        return '{} {} {}'.format(self.nettype, self.addrtype, self.connection_address)

    @classmethod
    def loads(cls, val):
        fields = [x.strip() for x in val.split(' ') if x.strip()]
        return cls(
            cls._load_enum(fields[0], ('IN',)),
            cls._load_enum(fields[1], ('IP4', 'IP6',)),
            cls._load_str(fields[2]), ## TODO: _load_addr
        )

class Bandwidth(SdpValue):
    def __init__(self, bwtype, bandwidth):
        self.bwtype = bwtype
        self.bandwidth = bandwidth

    def dumps(self):
        return '{}:{}'.format(self.bwtype, self.bandwith)

    @classmethod
    def loads(cls, val):
        fields = [x.strip() for x in val.split(':') if x.strip()]
        return cls(
            cls._load_enum(fields[0], ('CT', 'AS',)),
            cls._load_str(fields[1]),
        )

class Timing(SdpValue):

    def __init__(self, start_time, stop_time):
        self.start_time = start_time
        self.stop_time = stop_time

    def dumps(self):
        return '{} {}'.format(self.start_time, self.stop_time)

    @classmethod
    def loads(cls, val):
        fields = [x.strip() for x in val.split(' ') if x.strip()]
        return cls(
            cls._load_int(fields[0]),
            cls._load_int(fields[1]),
        )

class RepeatTimes(SdpValue):
    _times_re = TIMESRE
    _mod = {
        'd': 86400,
        'h': 3600,
        'm': 60,
        's': 1,
    }

    def __init__(self, interval, duration, times):
        self.interval = interval
        self.duration = duration
        self.times = times

    @classmethod
    def loads(cls, val):
        fields = [x.strip() for x in val.split(' ') if x.strip()]

    @classmethod
    def _load_time(cls, val):
        match = self._times_re.match(val)
        if not match:
            raise Exception("Failed to parse: {}".format(val))
        if match[1]:
            return int(match[0]) * cls._mod[match[1]]
        else:
            return int(match[0])

class TimeZones(SdpValue):

    def __init__(self, zones):
        self.zones = zones

class EncryptionKeys(SdpValue):

    def __init__(self, method, key=None):
        self.method = method
        self.key = key

class Attributes(SdpValue):

    def __init__(self, attr, val=None):
        self.attr = attr
        self.val = val

class MediaDescrption(SdpValue):

    def __init__(self, port, proto, fmts):
        self.port = port
        self.proto = proto
        self.fmts = fmts


import sys, re
from math import ceil, floor, sqrt, pi, factorial, gcd,sin,cos,tan,asin,acos,atan2,exp,log,log10
from collections import deque, Counter, defaultdict
from itertools import product, accumulate
from functools import reduce,lru_cache
from bisect import bisect
from heapq import heapify, heappop, heappush
sys.setrecursionlimit(5 * 10 ** 5)
try:
    from pypyjit import set_param
    set_param('max_unroll_recursion=-1')
except ModuleNotFoundError:
    pass
input = lambda: sys.stdin.readline().rstrip()
ii = lambda: int(input())
mi = lambda: map(int, input().split())
li = lambda: list(mi())
inf = 2 ** 63 - 1
tokens = (i for line in iter(input, "") for i in line.split())

def solve(N: int, Q: int, S: str, L: "List[int]", R: "List[int]"):
    def get_ans(l,r):
        return t[r] - t[l]

    t = [0]*N
    c = 0
    for i in range(1,N):
        if S[i-1:i+1] == "AC":
            c += 1
        t[i] += c

    for l,r in zip(L, R):
        print(get_ans(l-1,r-1))

def main():
    N = int(next(tokens))  # type: int
    Q = int(next(tokens))  # type: int
    S = next(tokens)  # type: str
    l = [int()] * (Q)  # type: "List[int]"
    r = [int()] * (Q)  # type: "List[int]"
    for i in range(Q):
        l[i] = int(next(tokens))
        r[i] = int(next(tokens))
    solve(N, Q, S, l, r)
    return

main()


def getPermutations(array):
    permutations = []
    helper(0, array, permutations)
    return permutations

def helper(i, array, permutations):
    if i == len(array) - 1:
        permutations.append(array[:])
        return
    for j in range(i, len(array)):
        swap(array, i, j)
        helper(i + 1, array, permutations)
        swap(array, i, j)

def swap(array, i, j):
    array[i], array[j] = array[j], array[i]

a = [1, 2, 3]
print(getPermutations(a))


"""
This script tests what accuracy can be gained on the credit approval dataset
"""

from sklearn.pipeline import make_pipeline, make_union, Pipeline
from sklearn.preprocessing import StandardScaler, Imputer, RobustScaler
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.svm import LinearSVC
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.dummy import DummyClassifier

from noxer.preprocessing import ColumnSelector, OneHotEncoder

from searchgrid import set_grid, build_param_grid

import pandas as ps
import numpy as np

from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.preprocessing import LabelBinarizer

class OneHotEncoder(BaseEstimator, TransformerMixin):
    """Wrapper around LabelBinarizer. Assumes that input X to fit and transform is a single
    column matrix of categorical values."""
    def fit(self, X, y=None):
        # create label encoder
        M = X[:, 0].astype('str')
        self.encoder = LabelBinarizer()
        self.encoder.fit(M)
        return self

    def transform(self, X, y=None):
        M = X[:, 0].astype('str')
        return self.encoder.transform(M)

data = ps.read_csv('titanic.csv')
data = data.replace('?', 'NaN')
data = data.as_matrix()

X = data[:, :-1]
y = data[:, -1].astype('int')

X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)

category = lambda idx: make_pipeline(ColumnSelector(idx), OneHotEncoder())
number = lambda idx: make_pipeline(ColumnSelector(idx), Imputer())

features = make_union(
    number(1), # Pclass
    category(3), # Gender
    number(4), # Age
    number(5), # SibSp
    category(6), # Parch
    number(8), # Fare
    category(10), # Embarked
)

estimator = Pipeline([
    ('features', features),
    ('scaler', StandardScaler()),
    ('model', LinearSVC())
])

models = [
        set_grid(
            DecisionTreeClassifier(),
            min_samples_split=[2 ** -i for i in range(1, 8)],
            max_depth=list(range(1, 21))
        ),

        set_grid(
            LinearSVC(),
            C=np.logspace(-6, 6, num=20)
        ),
]

estimator = set_grid(
    estimator,
    model=[

        set_grid(
            GradientBoostingClassifier(),
            n_estimators=[2 ** i for i in range(1, 11)],
            learning_rate=np.logspace(-4, 0, num=10)
        ),

    ],
    scaler=[
        StandardScaler()
    ]
)

param_grid = build_param_grid(estimator)

gsearch = GridSearchCV(
    estimator=estimator,
    param_grid=param_grid,
    verbose=1,
    cv=3,
    n_jobs=-1
)

model = gsearch
dummy = DummyClassifier(strategy='most_frequent')

model.fit(X_train, y_train)
dummy.fit(np.zeros((len(y_train), 1)), y_train)
print(model.best_params_)
print(model.score(X_test, y_test))
print(dummy.score(np.zeros((len(y_test), 1)), y_test))


def length_of_longest_substring(arr, k):
  '''
  sliding window start -> end
  Same approach as longest substring of same letters with repalcemet
  Time O(n) | Space O(1) due to dic being max of size 2
  '''
  start = 0; longest = 0; maxLength = 0;
  dic = {}

  for end in range(len(arr)):
    #save frequency
    char = arr[end]
    if char not in dic:
      dic[char] = 0
    dic[char] += 1

    #save longest 1
    if arr[end] == 1:
      longest = max(longest, dic[1])

    #slide the window
    size = end-start + 1
    if size - longest > k:
      char = arr[start]
      dic[char] -= 1
      start += 1

    #save the max length
    maxLength = max(maxLength, end-start+1)

  return maxLength


# -*- coding: utf-8 -*-
# @Author: cyang
# @Date:   2020-09-22 19:49:00
# @Last Modified by:   cyang
# @Last Modified time: 2020-09-22 20:47:44

def isValid(s):
	c1 = s.count('(')
	c2 = s.count(')')
	c3 = s.count('[')
	c4 = s.count(']')
	c5 = s.count('{')
	c6 = s.count('}')

	print(c1, c2, c3, c4, c5, c6)

	if c1 != c2 or c3 != c4 or c5 != c6:
		return False

	stack = []
	size_1 = size_2 = size_3 = 0

	for idx in range(len(s)):
		temp = s[idx]
		if temp == '(' or temp == '[' or temp == '{':
			if temp == '(':
				size_1 = size_1 + 1
				stack.append('(')
			elif temp == '[':
				size_2 = size_2 + 1
				stack.append('[')
			elif temp == '{':
				size_3 = size_3 + 1
				stack.append('{')
		elif len(stack):
			if temp == ')' and '(' == stack[len(stack) - 1]:
				size_1 = size_1 - 1
				del stack[len(stack) - 1]
			elif temp == ']' and '[' == stack[len(stack) - 1]:
				size_2 = size_2 - 1
				del stack[len(stack) - 1]
			elif temp == '}' and '{' == stack[len(stack) - 1]:
				size_3 = size_3 - 1
				del stack[len(stack) - 1]
		else:
			return False

	# if len(stack) == 0:
	# 	return True
	# else:
	# 	return False

	# print(stack)
	if size_1 == 0 and size_2 == 0 and size_3 == 0:
		return True
	else:
		return False

if __name__ == '__main__':
	testStr = "()"
	result = isValid(testStr)
	print(result)


import numpy
import pygame
import random
import myutils
import transition

class Trappedknight:
    def __init__(self, screen, size):
        self.size = size
        self.screen = screen
        self.squares = self.makespiral(size)
        self.xfactor = screen.get_width() / len(self.squares)
        self.yfactor = screen.get_height() / len(self.squares[0])
        self.moves = self.generatemoves()
        self.colorangle = myutils.randomangle()
        self.canvas = pygame.Surface((10000, 10000))
        self.trans = transition.Transition(self.canvas)

        for i in range(0, len(self.squares)):
            for j in range(0, len(self.squares[i])):
                if self.squares[i][j][0] == 1:
                    self.position = (i, j)
                    self.squares[self.position[0]][self.position[1]] = (1, True)
                    break

    def step(self):
        lowestnumber = 0
        nextposition = None

        for i in range(0, len(self.moves)):

            if self.position[0] + self.moves[i][0] < 0 or self.position[1] + self.moves[i][1] < 0 or self.position[0] + \
                    self.moves[i][0] >= len(self.squares) or self.position[1] + self.moves[i][1] >= len(
                self.squares[0]):
                self.reinit()
                return

            if not self.squares[self.position[0] + self.moves[i][0]][self.position[1] + self.moves[i][1]][1]:
                if self.squares[self.position[0] + self.moves[i][0]][self.position[1] + self.moves[i][1]][
                    0] < lowestnumber or lowestnumber == 0:
                    nextposition = (self.position[0] + self.moves[i][0],
                                    self.position[1] + self.moves[i][1])
                    lowestnumber = \
                        self.squares[self.position[0] + self.moves[i][0]][self.position[1] + self.moves[i][1]][0]

        if isinstance(nextposition, type(None)):
            self.reinit()
            return

        pygame.draw.line(self.canvas,
                         (int(127 * (numpy.cos(0.2 * self.colorangle) + 1)),
                          int(127 * (numpy.sin(0.5 * self.colorangle) + 1)),
                          numpy.abs(int(127 * (numpy.cos(0.9 * self.colorangle) + 1)))),
                         (int(self.position[0] * self.xfactor), int(self.position[1] * self.yfactor)),
                         (int(nextposition[0] * self.xfactor), int(nextposition[1] * self.yfactor)))

        self.position = nextposition
        self.squares[self.position[0], self.position[1]] = (self.squares[self.position[0], self.position[1]], True)
        self.colorangle += myutils.degrees(1) / 15
        self.trans.step()
        self.screen.blit(self.canvas, (0, 0))

    def reinit(self):
        self.squares = self.makespiral(self.size)
        self.moves = self.generatemoves()
        self.colorangle = myutils.randomangle()
        # self.trans.starttransition(self.canvas, 1)
        self.canvas.fill((0, 0, 0))
        # pygame.display.update()
        for i in range(0, len(self.squares)):
            for j in range(0, len(self.squares[i])):
                if self.squares[i][j][0] == 1:
                    self.position = (i, j)
                    self.squares[self.position[0]][self.position[1]] = (1, True)
                    break

    def generatemoves(self):
        nummoves = random.randint(1, 8)
        output = []
        for i in range(0, nummoves):
            output.append((random.randint(-2, 2), random.randint(-2, 2)))
        return output

    def makespiral(self, size):
        numbers = numpy.arange(1, size * size + 1)
        array = numbers.reshape(size, size)
        nrows, ncols = array.shape
        idx = numpy.arange(nrows * ncols).reshape(nrows, ncols)[::-1]
        spiral_idx = []
        while idx.size:
            spiral_idx.append(idx[0])
            idx = idx[1:]
            idx = idx.T[::-1]
        spiral_idx = numpy.hstack(spiral_idx)
        spiral = numpy.empty_like(array)
        spiral.flat[spiral_idx] = array.flat[::-1]
        squares = numpy.empty(spiral.shape, dtype=object)
        for i in range(0, len(squares)):
            for j in range(0, len(squares[i])):
                squares[i][j] = (spiral[i][j], False)
        return squares


from fastaReader import readFastaMul
from timer import Timer
import character as ch
import os, shutil
from pathlib import Path

def trimDecision(path_fasta_file, exclusion_fraction, trim_fraction_tolerated, list_AA):
    """
    Management of gaps and ambiguous amino-acids:
    - exclusion_fraction: if the fraction of residus at a position in the sequence is > exclusion_fraction
                            --> remove this position from each sequence of the seed
    - trim_fraction_tolarated: if the fraction of positions removed is > trim_fraction_tolerated
                            --> remove the seed from de dataset
    """
    list_seq = readFastaMul(path_fasta_file)
    nbre_seq = len(list_seq)
    len_seq = len(list_seq[0][1])
    count_excluded = [0]*len_seq

    for _, seq in list_seq:
        for index_aa in range(0, len_seq):
            if seq[index_aa] not in list_AA:
                count_excluded[index_aa] += 1
    fraction_excluded = [count/nbre_seq for count in count_excluded]

    to_exclude = []
    for fraction in fraction_excluded:
        if fraction > exclusion_fraction:
            to_exclude.append(1)
        else:
            to_exclude.append(0)

    seed_trimed_kept = sum(to_exclude)/len_seq < trim_fraction_tolerated

    return seed_trimed_kept, to_exclude

def trimApplication(path_fasta_file, path_fasta_file_trimmed, seed_trimed_kept, to_exclude, count_seed_excluded):

    if seed_trimed_kept == False:
        count_seed_excluded += 1

    else:
        input_handle = open(path_fasta_file)
        output_handle = open(path_fasta_file_trimmed, "w")
        list_seq = readFastaMul(path_fasta_file)
        len_seq = len(list_seq[0][1])

        for l in input_handle:
            if l[0] == ">":
                output_handle.write(l)
                aa_counter = 0
            else:
                for elem in l[0:-1]:
                    if to_exclude[aa_counter] == 0:
                        output_handle.write(elem)
                    aa_counter += 1
                if aa_counter == len_seq:      # if the sequence is finished, go back to the line
                    output_handle.write(l[-1])
        output_handle.close()
        input_handle.close()
    return count_seed_excluded

def trimSave(path_folder_fasta, path_folder_fasta_trimmed, list_AA, exclusion_fraction, trim_fraction_tolerated):
    t = Timer()
    t.start()

    if os.path.isdir(path_folder_fasta_trimmed):
        shutil.rmtree(path_folder_fasta_trimmed)
    os.mkdir(path_folder_fasta_trimmed)

    path_folder_fasta = Path(path_folder_fasta)
    path_fasta_files = path_folder_fasta.iterdir()

    count_seed_excluded = 0

    for path_fasta_file in path_fasta_files:
        seed_trimed_kept, to_exclude = trimDecision(path_fasta_file, exclusion_fraction, trim_fraction_tolerated, list_AA)
        num_accession = os.path.basename(path_fasta_file).split(".")[0] + '.' + os.path.basename(path_fasta_file).split(".")[1]
        name_fasta_file_trimmed = num_accession + '.trim'
        path_fasta_file_trimmed = path_folder_fasta_trimmed + "/"+ name_fasta_file_trimmed
        count_seed_excluded = trimApplication(path_fasta_file, path_fasta_file_trimmed, seed_trimed_kept, to_exclude, count_seed_excluded)
    print("count_seed_excluded:", count_seed_excluded)

    t.stop("Compute and save the trimmed files")

if __name__ == '__main__':
    #path_folder_fasta = "/Users/pauline/Desktop/data/Pfam_fasta"
    #path_folder_fasta_trimmed = "/Users/pauline/Desktop/data/Pfam_fasta_trimmed"
    #list_AA = ch.characterList()
    #exclusion_fraction = 0.5
    #trim_fraction_tolerated = 0.5
    #trimSave(path_folder_fasta, path_folder_fasta_trimmed, list_AA, exclusion_fraction, trim_fraction_tolerated) # 207.76572 s

    # mini test
    path_folder_fasta = "Pfam_test"
    path_folder_fasta_trimmed = "Pfam_test_trimmed"
    list_AA = ch.characterList()
    exclusion_fraction = 0.5
    trim_fraction_tolerated = 0.5
    trimSave(path_folder_fasta, path_folder_fasta_trimmed, list_AA, exclusion_fraction, trim_fraction_tolerated) # 207.76572 s


"""
Read file into texts and calls.
It's ok if you don't understand how to read files.
"""
import csv
with open('texts.csv', 'r') as f:
    reader = csv.reader(f)
    texts = list(reader)
    index = texts[0][2].index(" ")
    timestamp = texts[0][2][index+1:]
    print("First record of texts, " + texts[0][0] + " texts " + texts[0][1] + " at time " + timestamp)

with open('calls.csv', 'r') as f:
    reader = csv.reader(f)
    calls = list(reader)
    lengthlist = len(calls)-1
    index = calls[lengthlist][2].index(" ")
    timestamp = calls[lengthlist][2][index+1:]
    seconds = calls[lengthlist][3]

    print("Last record of calls, " + calls[lengthlist][0] + " calls " + calls[lengthlist][1] + " at time "+ timestamp + " lasting " + seconds + " seconds")

"""
TASK 0:
What is the first record of texts and what is the last record of calls?
Print messages:
"First record of texts, <incoming number> texts <answering number> at time <time>"
"Last record of calls, <incoming number> calls <answering number> at time <time>, lasting <during> seconds"
"""

#Time Complexity
""" the index function takes O(n) where n is the length of the string
getting an item at an index takes O(1)
the complexity should be O(n) """


import pandas as pd
import plotly.express as px
import plotly
import plotly.graph_objects as go
from plotly.subplots import make_subplots

def annotate_line(fig, title, date):
    fig.update_layout(
        title='%s for %s' % (title, date),
        xaxis_title="longitude",
        xaxis_showgrid=False,
        xaxis_range=[100.533, 100.594],
        yaxis_title="speed km/h",
        yaxis_range=[0, 90],
        yaxis_showgrid=False,
        xaxis_nticks=35,
        yaxis_nticks=35,
        autosize=True,
        # height = 600,
        # width = 1400,
        shapes=[
            dict(
                type='line',
                yref='paper', y0=0, y1=1,
                xref='x', x0=100.536, x1=100.536,
            ),
            dict(
                type='line',
                yref='paper', y0=0, y1=1,
                xref='x', x0=100.545, x1=100.545,
            ),
            dict(
                type='line',
                yref='paper', y0=0, y1=1,
                xref='x', x0=100.552, x1=100.552,
            ),
            dict(
                type='line',
                yref='paper', y0=0, y1=1,
                xref='x', x0=100.558, x1=100.558,
            ),
            dict(
                type='line',
                yref='paper', y0=0, y1=1,
                xref='x', x0=100.567, x1=100.567,
            ),
            dict(
                type='line',
                yref='paper', y0=0, y1=1,
                xref='x', x0=100.584, x1=100.584,
            ),
            dict(
                type='line',
                yref='paper', y0=0, y1=1,
                xref='x', x0=100.592, x1=100.592,
            ),
            # speed
            dict(
                layer="below",
                x0=100.533,
                x1=100.594,
                y0=0,
                y1=21,
                type='rect',
                fillcolor='#EF9A9A',
                line_width=0
            ),
            dict(
                layer="below",
                x0=100.533,
                x1=100.594,
                y0=21,
                y1=26,
                type='rect',
                fillcolor='#FFC300',
                line_width=0
            ),
            dict(
                layer="below",
                x0=100.533,
                x1=100.594,
                y0=26,
                y1=46,
                type='rect',
                fillcolor='#FFFF8D',
                line_width=0
            ),
            dict(
                layer="below",
                x0=100.533,
                x1=100.594,
                y0=46,
                y1=90,
                type='rect',
                fillcolor='#CCFF90',
                line_width=0
            ),

        ])

    fig.add_annotation(
        x=100.536,
        y=70,
        text="แยกศาลาแดง",
        showarrow=False,
        font=dict(
            family='Courier New, monospace',
            size=18,
        )
    )

    fig.add_annotation(
        x=100.545,
        y=70,
        text="แยกวิทยุ",
        showarrow=False,
        font=dict(
            family='Courier New, monospace',
            size=18,
        )
    )

    fig.add_annotation(
        x=100.551,
        y=70,
        text="แยกใต้ทางด่วน",
        showarrow=False,
        font=dict(
            family='Courier New, monospace',
            size=18,
        )
    )

    fig.add_annotation(
        x=100.558,
        y=70,
        text="แยกพระราม4",
        showarrow=False,
        font=dict(
            family='Courier New, monospace',
            size=18,
        )
    )

    fig.add_annotation(
        x=100.567,
        y=70,
        text="แยกเกษมราษฎร์",
        showarrow=False,
        font=dict(
            family='Courier New, monospace',
            size=18,
        )
    )

    fig.add_annotation(
        x=100.584,
        y=70,
        text="แยกกล้วยน้ำไท",
        showarrow=False,
        font=dict(
            family='Courier New, monospace',
            size=18,
        )
    )

    fig.add_annotation(
        x=100.592,
        y=70,
        text="แยกพระขโนง",
        showarrow=False,
        font=dict(
            family='Courier New, monospace',
            size=18,
        )
    )

def plot_line_csv(df, date):
    fig = px.line(df, x="lon3", y="speed_km/h", title='ค.เร็วเฉลี่ย(km/h) ทุกๆ100เมตร ',color = "direction",animation_frame="round_min")
    annotate_line(fig, 'Speed line', date)
    fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 1500
    return fig

def plot_line(df, date):
    fig = px.line(df, x="lon3", y="speed_kph", title='ค.เร็วเฉลี่ย(km/h) ทุกๆ100เมตร ',color = "direction")
    annotate_line(fig, 'Speed line', date)
    return fig

def plot_line_pred(df, date):
    fig = px.line(df, x="lon3", y="speed_kph", title='ค.เร็วเฉลี่ย(km/h) ทุกๆ100เมตร ',color = "direction",animation_frame="time")
    annotate_line(fig, 'Speed line prediction', date)
    fig.layout.updatemenus[0].buttons[0].args[1]["frame"]["duration"] = 1500
    return fig