from common import *
import numpy as np
from collections import deque

class Player:
    def __init__(self, is_bot, x_start=20, y_start=20, start_direction=Direction.RIGHT, color=(255,255,255)):
        self._X_START = x_start
        self._Y_START = y_start
        self._START_DIRECTION = start_direction

        self.alive = True
        self.direction = start_direction
        self.x = x_start
        self.y = y_start
        self.xs = []
        self.ys = []
        self.color = color
        self.is_bot = is_bot

    def moveRight(self):
        self.direction = Direction.RIGHT

    def moveLeft(self):
        self.direction = Direction.LEFT

    def moveUp(self):
        self.direction = Direction.UP

    def moveDown(self):
        self.direction = Direction.DOWN

    def addBlock(self):
        self.xs.append(self.x)
        self.ys.append(self.y)
        if self.direction == Direction.RIGHT:
            self.x = self.x + step_size
        elif self.direction == Direction.LEFT:
            self.x = self.x - step_size
        elif self.direction == Direction.UP:
            self.y = self.y - step_size
        else: # self.direction == Direction.DOWN:
            self.y = self.y + step_size

    ## Start BOT methods #####################################################################################################
    def _get_next(self, player, windowWidth, windowHeight):
        # assume player (except self) continues a step in each direction, to prevent head on collisions and ties
        ns = []
        if player.x != self.x:
            #  if player.direction == Direction.RIGHT:
            ns.append([min(player.x + step_size, windowWidth - step_size), player.y])
            #  yNext.append(player.y)
            #  elif player.direction == Direction.LEFT:
            ns.append([max(player.x - step_size, 0), player.y])
            #  yNext.append(player.y)
            #  elif player.direction == Direction.UP:
            ns.append([player.x, max(player.y - step_size, 0)])
            #  yNext.append(max(player.y - step_size, 0))
            #  else: # player.direction == Direction.DOWN
            #  xNext.append(player.x)
            ns.append([player.x, min(player.y + step_size, windowHeight - step_size)])
        return ns

    # return each direction bot can go in without killing himself immediately
    def _get_valid_directions(self, board, windowWidth, windowHeight):
        valid_directions = []
        if self.x != 0 and not board[ind(self.x-step_size, self.y)]:
            valid_directions.append(Direction.LEFT)
        if self.x+step_size != windowWidth and not board[ind(self.x+step_size, self.y)]:
            valid_directions.append(Direction.RIGHT)
        if self.y != 0 and not board[ind(self.x, self.y-step_size)]:
            valid_directions.append(Direction.UP)
        if self.y+step_size != windowHeight and not board[ind(self.x, self.y+step_size)]:
            valid_directions.append(Direction.DOWN)
        return valid_directions

    def _bfs_score(self, start, board, direc, numSteps, nextFive=None):
        visited = np.full(board.shape, False)
        q = deque()

        score = 0
        # Append nodes of the form (distance, coordinate)
        dist = 0
        q.append((dist, start))
        while q and dist < numSteps:
            node = q.popleft()
            dist = node[0]
            block = node[1]
            #  f.write(",".join(map(str, block)) + "\n")
            #  f.write(str(board.shape) + "\n")

            left = (block[0]-1, block[1])
            right = (block[0]+1, block[1])
            up = (block[0], block[1]-1)
            down = (block[0], block[1]+1)

            #  f.write(",".join(map(str, left)) + " ")
            #  f.write(",".join(map(str, right)) + " ")
            #  f.write(",".join(map(str, up)) + " ")
            #  f.write(",".join(map(str, down)) + "\n")
            numChoices = 0
            if block[0] != 0 and not board[left]:
                numChoices += 1
                if not visited[left]:
                    visited[left] = True
                    score += 1
                    q.append((dist+1, left))

            if block[0] != board.shape[0] - 1 and not board[right]:
                numChoices += 1
                if not visited[right]:
                    visited[right] = True
                    score += 1
                    q.append((dist+1, right))

            if block[1] != 0 and not board[up]:
                numChoices += 1
                if not visited[up]:
                    visited[up] = True
                    score += 1
                    q.append((dist+1, up))

            if block[1] != board.shape[1] - 1 and not board[down]:
                numChoices += 1
                if not visited[down]:
                    visited[down] = True
                    score += 1
                    q.append((dist+1, down))
            # discourage going down one lane paths (no escape)
            if numChoices < 2:
                score -= 5

        # encourage cutting players off
        if nextFive:
            snf = []
            if direc == Direction.LEFT:
                for i in range(0, 5):
                    if start[0] - i < 0:
                        break
                    n = (start[0] - i, start[1])
                    if i == 0 or not board[n]: snf.append(n)
                    else: break
            elif direc == Direction.RIGHT:
                for i in range(0, 5):
                    if start[0] + i > board.shape[0] - 1:
                        break
                    n = (start[0] + i, start[1])
                    if i == 0 or not board[n]: snf.append(n)
                    else: break
            elif direc == Direction.UP:
                for i in range(0, 5):
                    if start[1] - i < 0:
                        break
                    n = (start[0], start[1] - i)
                    if i == 0 or not board[n]: snf.append(n)
                    else: break
            else:
                for i in range(0,5):
                    if start[1] + i > board.shape[1] - 1:
                        break
                    n = (start[0], start[1] + i)
                    if i == 0 or not board[n]: snf.append(n)
                    else: break
            #  print("dir " + str(direc) + " snf:")
            #  print(snf) # DEBUG
            #  print("nf")
            #  print(nextFive)
            for nf in nextFive:
                for i in range(len(nf)):
                    pos = nf[i]
                    if pos in snf and snf.index(pos) <= i:
                        score += 1000
                        #  print('cutoff')

        return score

    def _get_next_five(self, board, players, windowWidth, windowHeight):
        nfs = []
        for player in players:
            nf = []
            if player.x == self.x and player.y == self.y:
                continue
            if player.direction == Direction.LEFT:
                for i in range(2, 6):
                    if player.x - (step_size * i) < 0:
                        break
                    n = ind(max(player.x - (step_size * i), 0), player.y)
                    if not board[n]: nf.append(n)
                    else:
                        break
            elif player.direction == Direction.RIGHT:
                for i in range(2, 6):
                    if player.x + (step_size * i) > windowWidth - step_size:
                        break
                    n = ind(min(player.x + (step_size * i), windowWidth - step_size), player.y)
                    if not board[n]: nf.append(n)
                    else:
                        break
            elif player.direction == Direction.UP:
                for i in range(2, 6):
                    if player.y - (step_size * i) < 0:
                        break
                    n = ind(player.x, max(player.y - (step_size * i), 0))
                    if not board[n]: nf.append(n)
                    else:
                        break
            else:
                for i in range(2, 6):
                    if player.y + (step_size * i) > windowHeight - step_size:
                        break
                    n = ind(player.x, min(player.y + (step_size * i), windowHeight - step_size))
                    if not board[n]: nf.append(n)
                    else:
                        break
            nfs.append(nf)
        return nfs

    def _get_score(self, board, start, players, nextFive, direc):
        self_score = self._bfs_score(start, board, direc, 101, nextFive)
        return self_score

    def update(self, board, players, windowWidth, windowHeight):
        # do nothing if player is human
        if not self.is_bot:
            return

        # initialize preferences to -1000
        directions = {
            Direction.LEFT: float('-inf'),
            Direction.RIGHT: float('-inf'),
            Direction.UP: float('-inf'),
            Direction.DOWN: float('-inf')
        }

        # assume all players (except self) continue one step in each direction to avoid head-on collisions
        coords = []
        old = []
        for player in players:
            ns = self._get_next(player, windowWidth, windowHeight)
            for n in ns:
                i = ind(n[0], n[1])
                old.append(board[i]) # keep track of what board used to be so we can set it back
                coords.append(i)
        for coord in coords:
            board[coord] = True

        # get next five straight moves for all players (except self)
        nextFive = self._get_next_five(board, players, windowWidth, windowHeight)
        #  print(nextFive) # DEBUG

        validDirections = self._get_valid_directions(board, windowWidth, windowHeight)
        for direction in validDirections:
            start = None
            if direction == Direction.UP:
                start = ind(self.x, self.y-step_size)
            elif direction == Direction.LEFT:
                start = ind(self.x-step_size, self.y)
            elif direction == Direction.RIGHT:
                start = ind(self.x+step_size, self.y)
            elif direction == Direction.DOWN:
                start = ind(self.x, self.y+step_size)
            board[start] = True
            directions[direction] = self._get_score(board, start, players, nextFive, direction)
            board[start] = False

        # set board back
        for j in range(len(coords)):
            board[coords[j]] = old[j]

        # if the best direction is "splitting" ie it will divide the board into two halves, discourage it
        if directions[Direction.UP] > 0 and (self.y <= step_size or board[ind(self.x, self.y-step_size*2)]) \
                and Direction.LEFT in validDirections and Direction.RIGHT in validDirections:
            #  print('up split') # debug
            directions[Direction.UP] /= 2
        if directions[Direction.LEFT] > 0 and (self.x <= step_size or board[ind(self.x-step_size*2, self.y)]) \
                and Direction.UP in validDirections and Direction.DOWN in validDirections:
            #  print('left split') # debug
            directions[Direction.LEFT] /= 2
        if directions[Direction.RIGHT] > 0 and (self.x >= windowWidth - step_size*2 or \
                board[ind(self.x+step_size*2, self.y)]) and Direction.UP in validDirections \
                and Direction.DOWN in validDirections:
            #  print('right split') # debug
            directions[Direction.RIGHT] /= 2
        if directions[Direction.DOWN] > 0 and (self.y >= windowHeight - step_size*2 or \
                board[ind(self.x, self.y+step_size*2)]) and Direction.LEFT in validDirections and \
                Direction.RIGHT in validDirections:
            directions[Direction.DOWN] /= 2
            #  print('down split') # debug

        # encourage going in the same direction
        directions[self.direction] *= 1.05

        self.direction = max(directions, key=directions.get)
        #  print(str(directions)) # DEBUG


import os
import re
import sys
from os.path import join as pjoin
from glob import glob
import shutil

from setuptools import setup, Command, Extension, find_packages
from setuptools.command.sdist import sdist
from setuptools.command.build_ext import build_ext

from utils.bundle import fetch_libev, configure, bundled_version
from utils.detect import detect_ev
from utils.other import temporary_directory

#-----------------------------------------------------------------------------
# Utils
#-----------------------------------------------------------------------------

def dotc(name):
    return os.path.abspath(pjoin('pyev', name + '.c'))

def doth(name):
    return os.path.abspath(pjoin('pyev', name + '.c'))

#-----------------------------------------------------------------------------
# Flags
#-----------------------------------------------------------------------------

bundledir = "bundled"

#-----------------------------------------------------------------------------
# Compiler settings
#-----------------------------------------------------------------------------

def bundled_settings():
    settings = {
       'libraries': [],
       'include_dirs': [pjoin(bundledir, 'libev')],
       'library_dirs': [],
    }
    return settings

def system_settings():
    settings = {
       'libraries': ['ev'],
       'include_dirs': [],
       'library_dirs': [],
    }
    return settings

COMPILER_SETTINGS = {}

#-----------------------------------------------------------------------------
# Default Values
#-----------------------------------------------------------------------------

define_fmt = "\"{0}\""

cmdclass = {}

extensions = []

package_data = {}

#-----------------------------------------------------------------------------
# Commands
#-----------------------------------------------------------------------------

class FetchCommand(Command):
    """Fetch libev sources, that's it."""

    description = "Fetch libev sources into bundled"

    user_options = []

    def initialize_options(self):
        pass

    def finalize_options(self):
        pass

    def run(self):
        # fetch sources for libev extension:
        if not os.path.exists(bundledir):
            os.makedirs(bundledir)
        fetch_libev(bundledir)
        for tarball in glob(pjoin(bundledir, '*.tar.gz')):
            os.remove(tarball)

cmdclass['fetch'] = FetchCommand

class CheckSDist(sdist):
    """Custom sdist that ensures that libev is ready."""

    def run(self):
        # Clean before fetch.
        if os.path.exists(bundledir):
            shutil.rmtree(bundledir)
        self.run_command('fetch')
        sdist.run(self)

cmdclass['sdist'] = CheckSDist

class Configure(Command):
    """Configure command adapted from h5py."""

    description = "Discover libev"

    user_options = []

    def initialize_options(self):
        self.libev = 'system'

    def finalize_options(self):
        pass

    def detect_system_libev(self):
        with temporary_directory() as basedir:
            try:
                config = detect_ev(basedir, **system_settings())
            except Exception:
                etype, evalue, tb = sys.exc_info()
                # print the error as distutils would if we let it raise:
                print ("\nerror: %s\n" % evalue)
            else:
                print ("libev version detected: %s.%s" % config['vers'])
                return config

    def bundle_libev_extension(self):
        if self.distribution.ext_modules and \
           self.distribution.ext_modules[0].name == 'pyev.libev':
            # I've already been run
            return

        self.libev = 'bundled'
        settings = bundled_settings()

        print ("Using bundled libev")

        # fetch sources for libev extension
        fetch = self.distribution.get_command_obj('fetch')
        fetch.run()

        # configure libev
        configure(pjoin(bundledir, 'libev'))

        # find depended libraries
        libev_config = open(pjoin(bundledir, 'libev', 'config.h'), "r").read()
        libraries = [l.lower() for l in set(re.findall("#define HAVE_LIB(\S+) 1",
                                            libev_config))]
        settings['libraries'] = list(set(libraries + settings.get('libraries', [])))

        # register libev as extension
        ext = Extension(
            'pyev.libev',
            sources=[pjoin('utils', 'initlibev.c')],
            define_macros=[('LIBEV_EMBED', '1'),
                           ('EV_MULTIPLICITY', '1')],
            **settings
        )

        # Fix for distribute
        ext._needs_stub = False

        # insert the extension
        self.distribution.ext_modules.insert(0, ext)

        return dict(settings=settings,
                    vers=bundled_version)

    def run(self):
        # Try to build with system wide libev.
        config = self.detect_system_libev()

        if config is None:
            # No system wide found, try to build with bundled version.
            config = self.bundle_libev_extension()

        settings = config['settings']
        modules = self.distribution.ext_modules \
                  if self.libev == 'system' \
                  else self.distribution.ext_modules[1:]
        for ext in modules:
            for attr, value in settings.items():
                setattr(ext, attr, value)

cmdclass['configure'] = Configure

class CheckingBuildExt(build_ext):

    def run(self):
        configure = self.distribution.get_command_obj('configure')
        configure.run()
        return build_ext.run(self)

cmdclass['build_ext'] = CheckingBuildExt

#-----------------------------------------------------------------------------
# Description, version and other meta information.
#-----------------------------------------------------------------------------

re_meta = re.compile(r'__(\w+?)__\s*=\s*(.*)')
re_vers = re.compile(r'VERSION\s*=\s*\((.*?)\)')
re_doc = re.compile(r'^"""(.+?)"""')
rq = lambda s: s.strip("\"'")

def add_default(m):
    attr_name, attr_value = m.groups()
    return ((attr_name, rq(attr_value)),)

def add_version(m):
    v = list(map(rq, m.groups()[0].split(', ')))
    return (('VERSION', '.'.join(v[0:3]) + ''.join(v[3:])),)

def add_doc(m):
    return (('doc', m.groups()[0]),)

pats = {re_meta: add_default,
        re_vers: add_version,
        re_doc: add_doc}
here = os.path.abspath(os.path.dirname(__file__))
meta_fh = open(os.path.join(here, 'pyev/__init__.py'))
try:
    meta = {}
    for line in meta_fh:
        if line.strip() == '# -eof meta-':
            break
        for pattern, handler in pats.items():
            m = pattern.match(line.strip())
            if m:
                meta.update(handler(m))
finally:
    meta_fh.close()

with open('README.rst') as fh:
    long_description = fh.read()

#-----------------------------------------------------------------------------
# Extensions
#-----------------------------------------------------------------------------

extensions.append(
    Extension("pyev._pyev",
              ["src/pyev.c"],
              define_macros=[('PYEV_VERSION',
                              define_fmt.format(meta['VERSION']))],
              **COMPILER_SETTINGS
    )
)

#-----------------------------------------------------------------------------
# Setup
#-----------------------------------------------------------------------------

setup(
    name="pyev-static",
    packages=find_packages(),
    version=meta['VERSION'],
    description=meta['doc'],
    long_description=long_description,
    author=meta['author'],
    author_email=meta['contact'],
    url=meta['homepage'],
    ext_modules=extensions,
    package_data=package_data,
    platforms=["Microsoft Windows", "POSIX"],
    license="BSD License / GNU General Public License (GPL)",
    cmdclass=cmdclass,
    classifiers=[
        "Development Status :: 4 - Beta",
        "Intended Audience :: Developers",
        "Intended Audience :: System Administrators",
        "License :: OSI Approved :: BSD License",
        "License :: OSI Approved :: GNU General Public License (GPL)",
        "Operating System :: Microsoft :: Windows :: Windows NT/2000",
        "Operating System :: POSIX",
        "Programming Language :: Python :: 2.6",
        "Programming Language :: Python :: 2.7",
        "Programming Language :: Python :: 3.1",
        "Programming Language :: Python :: 3.2",
    ]
)


import re
from zope.interface import Interface
from twisted.python import log
from nevow import rend, tags as T, loaders

class IPastDate(Interface):
    """Remember a past date for time travel"""
    pass

class PastConnectionPool:
    """Proxy for an existing connection pool to run queries in the past.

    This proxy intercepts runQueryInPast and modifies the request to
    make it happen in the past (if necessary). It only accepts simple
    queries (query + dict) and needs a web context (to extract the
    date).
    """

    _regexp_deleted = re.compile(r"(?:(\w+)\.|)deleted='infinity'")
    _regexp_full = re.compile(r"\B_full\b")

    def __init__(self, orig):
        self._orig = orig

    def __getattr__(self, attribute):
        return getattr(self._orig, attribute)

    def runQueryInPast(self, ctx, query, dic=None):
        """Run the specified query in the past.

        Occurences of C{deleted='infinity'} are replaced by C{(created
        < %(__date)s AND deleted > %(__date)s)}
        """

        def convert(date, mo):
            if mo.group(1):
                suffix = "%s." % mo.group(1)
            else:
                suffix = ""
            return " AND ".join(['(%screated < %%(__date)s::abstime' % suffix,
                                 '%sdeleted > %%(__date)s::abstime)' % suffix])

        # Try to get the date from the context
        try:
            date = ctx.locate(IPastDate)
        except KeyError:
            # Not in the past
            query = PastConnectionPool._regexp_full.sub("", query)
            if dic:
                return self._orig.runQuery(query, dic)
            else:
                return self._orig.runQuery(query)

        # We need to run this request in the past
        if not dic:
            dic = {}
        dic["__date"] = date
        q = PastConnectionPool._regexp_deleted.sub(
            lambda x: convert(date, x), query)
        return self._orig.runQuery(q, dic)

class PastResource(rend.Page):
    """This is a special resource that needs to be instanciated with
    another resource. This resource will register the date into the
    current context and use the given resource to handle the request.
    """

    addSlash = True
    docFactory = loaders.stan(T.html [ T.body [ T.p [ "Nothing here" ] ] ])

    def __init__(self, main):
        self.main = main
        rend.Page.__init__(self)

    def dateOk(self, ctx, date):
        # The given date is correct, insert it in the context
        ctx.remember(date, IPastDate)
        return self.main

    def badDate(self, ctx, date):
        log.msg("Got bad date: %r" % date)
        return self.main

    def childFactory(self, ctx, date):
        # We must validate the date (use runOperation to avoid proxy)
        d = self.main.dbpool.runOperation("SELECT %(date)s::abstime",
                                          {'date': date})
        d.addCallbacks(lambda x: self.dateOk(ctx, date),
                       lambda x: self.badDate(ctx, date))
        return d


# import pandas as pd
import expressyeaself.encode_sequences as encode
import numpy as np
from keras import optimizers
from keras.models import Sequential
from keras.layers import Dense, Activation, Dropout, LSTM
# from keras.layers import Embedding
# from keras.utils import to_categorical
# from keras.datasets import mnist
# from sklearn.preprocessing import OneHotEncoder
import matplotlib.pyplot as plt

# encode = context.encode_sequences
# construct = context.construct_neural_net_input

# def load_raw_data(data):
#     raw_data = pd.read_csv(data, names=['raw_sequence','output'], sep='\t')
#     return raw_data

# def load_raw_sequence_data(raw_matrix):
#     """
#     """
#     sequence_matrix=[]
#     for sequence in raw_matrix['raw_sequence']:
#         one_hot_array_sequence=np.array(ast.literal_eval(sequence))
#         sequence_matrix.append(one_hot_array_sequence)
#     sequence_matrix=np.array(sequence_matrix)
#     return sequence_matrix

def read_length(encoded_sequences):
    """
    this function helps to read the sequence length and the data amount
    """
    data_length = int(encoded_sequences.shape[0])
    sequence_length = int(encoded_sequences.shape[1])
    return data_length, sequence_length

# def matrix_reshape_to_array(matrix, data_amount, sequence_length):
#     """
#     this function helps to reshape the sequences matric
#     """
#     sequence_array=matrix.reshape(data_amount, sequence_length, 5)
#     return sequence_array

# def input_sequence_to_embedding(sequence_matrix, sequence_length):
#     sequence_matrix_for_enbedding=[]
#     for matrix in sequence_matrix:
#         matrix=matrix.reshape(1,sequence_length,5)
#         sequence_matrix_for_enbedding.append(matrix)
#     sequence_matrix_for_enbedding=np.array(sequence_matrix_for_enbedding)
#     return sequence_matrix_for_enbedding

def split_data(input_data, output_data):
    """
    the split function
    """
    training_sequence = input_data[:int(input_data.shape[0] * 0.8)]
    training_output = output_data[:int(output_data.shape[0] * 0.8)]
    testing_sequence = input_data[int(input_data.shape[0] * 0.8):]
    testing_output = output_data[int(output_data.shape[0] * 0.8):]
    return training_sequence, training_output, testing_sequence, testing_output

# def load_output(raw_matrix, data_length):
#     output_matrix=np.array(raw_matrix['output']).reshape(data_length, 1, 1)
#     return output_matrix

def scale_output(raw_data, data_length):
    """
    the function to scale the output
    """
#     output_data=[]
#     max_value=raw_data['output'].max()
#     for output in raw_data['output']:
#         output=output/max_value
#         output_data.append(output)
    output_data = np.array(raw_data).reshape(data_length, 1, 1)
    return output_data

def input_of_one_hot_sequence(raw_matrix, data_amount, sequence_length):
    """
    the function to reshape the matrix to the input
    format of the lstm
    """
    sequence_matrix = raw_matrix.reshape(data_amount, -1)
    one_hot_sequence_matrix = sequence_matrix.reshape(
        data_amount, 1, sequence_length * 5)
    return one_hot_sequence_matrix

def lstm_model_on_one_hot_sequence(
        sequence_length,
        training_sequence,
        training_output,
        testing_sequence,
        testing_output,
        self_loss='mean_squared_error',
        learning_rate=0.01,
        epochs_value=20,
        batch_size_value=50):
    """
    the lstm model function
    """
    model = Sequential()
    # model.add(Embedding(5, 1, input_length=1285))
    model.add(Dropout(0.8))
    model.add(
        Dense(
            1024,
            kernel_initializer='uniform',
            input_shape=(
                1,
                5 *
                sequence_length,
            )))
    model.add(Activation('softmax'))
    model.add(Dense(512, kernel_initializer='uniform', input_shape=(1, 1024,)))
    model.add(Activation('softmax'))
    model.add(Dense(256, kernel_initializer='uniform', input_shape=(1, 512,)))
    model.add(Activation('softmax'))
    model.add(Dense(128, kernel_initializer='uniform', input_shape=(1, 256,)))
    model.add(Activation('softmax'))
    model.add(Dense(64, kernel_initializer='uniform', input_shape=(1, 128,)))
    model.add(Activation('softmax'))
    model.add(Dense(32, kernel_initializer='uniform', input_shape=(1, 64,)))
    model.add(Activation('softmax'))
    model.add(Dense(16, kernel_initializer='uniform', input_shape=(1, 32,)))
    model.add(Activation('softmax'))
    model.add(Dense(8, kernel_initializer='uniform', input_shape=(1, 16,)))
    model.add(Activation('softmax'))

    model.add(LSTM(units=1, return_sequences=True))
    sgd = optimizers.SGD(
        lr=learning_rate,
        decay=1e-6,
        momentum=0.9,
        nesterov=True)
    model.compile(loss=self_loss, optimizer=sgd, metrics=['mae', 'acc'])

    model.fit(
        training_sequence,
        training_output,
        epochs=epochs_value,
        batch_size=batch_size_value,
        verbose=1)
    score = model.evaluate(testing_sequence, testing_output)
    predicted_output_raw = model.predict(testing_sequence)
    predicted_output = predicted_output_raw.reshape(
        len(predicted_output_raw), 1)

    return score, predicted_output

# def LSTM_model_training(
#         LSTM_model,
#         training_sequence,
#         training_output,
#         epochs_value=20,
#         batch_size_value=50):

# return LSTM_model.fit(
#     training_sequence,
#     training_output,
#     epochs=epochs_value,
#     batch_size=batch_size_value,
#     verbose=1)

# def model_report(
#         LSTM_model,
#         testing_sequence,
#         testing_output,
#         batch_size_value=50):
#     #     LSTM_model.summary()
#     score = model.evaluate(testing_sequence, testing_output)
#     return score

# def predict_data(LSTM_model, testing_sequence):
#     predicted_output_raw = LSTM_model.predict(testing_sequence)
#     predicted_output = predicted_output_raw.reshape(
#         len(predicted_output_raw), 1)
#     return predicted_output

def caparison_figure(predict_data, testing_output):
    """
    the function to plot the comparison figures
    """
    testing_output_array = testing_output.reshape(len(testing_output), 1)
    index = np.arange(1, len(testing_output) + 1, 1)
    fig = plt.figure(figsize=(9, 6))
    plt.scatter(index, testing_output_array, label='real output')
    plt.scatter(index, predict_data, label='predict output')
    plt.legend()
    return fig

def lstm_training(
        data_path,
        self_loss='mean_squared_error',
        learning_rate=0.01,
        epochs_value=2,
        batch_size_value=50):
    """
    the wrapping function
    """
    sequence_list, raw_output, max_value = encode.encode_sequences_with_method(
        data_path, 'One-Hot')
    data_number, bp_number = read_length(sequence_list)
    reshaped_sequence_matrix = input_of_one_hot_sequence(
        sequence_list, data_number, bp_number)
    output = scale_output(raw_output, data_number)
    train_x, train_y, test_x, text_y = split_data(
        reshaped_sequence_matrix, output)
    score, predict_data = lstm_model_on_one_hot_sequence(
        bp_number, train_x, train_y, test_x, text_y,
        self_loss=self_loss, learning_rate=learning_rate,
        epochs_value=epochs_value, batch_size_value=batch_size_value)
#     LSTM_model_training(LSTM_model, train_x, train_y,
#         epochs_value=2, batch_size_value=50)
#     socre=model_report(LSTM_model, testing_sequence,
#         testing_output, batch_size_value=50)
#     predicted_output=predict_data(LSTM_model, test_x)
    cp_figure = caparison_figure(predict_data, text_y)
    return score, cp_figure


import operator
import os
import shutil
from multiprocessing import cpu_count
from time import time

import click
from tabulate import tabulate

from platformio import app, exception, fs, util
from platformio.device.monitor.command import device_monitor_cmd
from platformio.project.config import ProjectConfig
from platformio.project.exception import ProjectError
from platformio.project.helpers import find_project_dir_above, load_build_metadata
from platformio.run.helpers import clean_build_dir
from platformio.run.processor import EnvironmentProcessor
from platformio.test.runners.base import CTX_META_TEST_IS_RUNNING

# pylint: disable=too-many-arguments,too-many-locals,too-many-branches

try:
    DEFAULT_JOB_NUMS = cpu_count()
except NotImplementedError:
    DEFAULT_JOB_NUMS = 1

@click.command("run", short_help="Run project targets (build, upload, clean, etc.)")
@click.option("-e", "--environment", multiple=True)
@click.option("-t", "--target", multiple=True)
@click.option("--upload-port")
@click.option("--monitor-port")
@click.option(
    "-d",
    "--project-dir",
    default=os.getcwd,
    type=click.Path(exists=True, file_okay=True, dir_okay=True, writable=True),
)
@click.option(
    "-c",
    "--project-conf",
    type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True),
)
@click.option(
    "-j",
    "--jobs",
    type=int,
    default=DEFAULT_JOB_NUMS,
    help=(
        "Allow N jobs at once. "
        "Default is a number of CPUs in a system (N=%d)" % DEFAULT_JOB_NUMS
    ),
)
@click.option(
    "-a",
    "--program-arg",
    "program_args",
    multiple=True,
    help="A program argument (multiple are allowed)",
)
@click.option("--disable-auto-clean", is_flag=True)
@click.option("--list-targets", is_flag=True)
@click.option("-s", "--silent", is_flag=True)
@click.option("-v", "--verbose", is_flag=True)
@click.pass_context
def cli(
    ctx,
    environment,
    target,
    upload_port,
    monitor_port,
    project_dir,
    project_conf,
    jobs,
    program_args,
    disable_auto_clean,
    list_targets,
    silent,
    verbose,
):
    app.set_session_var("custom_project_conf", project_conf)

    # find project directory on upper level
    if os.path.isfile(project_dir):
        project_dir = find_project_dir_above(project_dir)

    targets = list(target) if target else []
    del target
    only_monitor = targets == ["monitor"]
    is_test_running = CTX_META_TEST_IS_RUNNING in ctx.meta
    command_failed = False

    with fs.cd(project_dir):
        config = ProjectConfig.get_instance(project_conf)
        config.validate(environment)

        if list_targets:
            return print_target_list(list(environment) or config.envs())

        # clean obsolete build dir
        if not only_monitor and not disable_auto_clean:
            build_dir = config.get("platformio", "build_dir")
            try:
                clean_build_dir(build_dir, config)
            except ProjectError as exc:
                raise exc
            except:  # pylint: disable=bare-except
                click.secho(
                    "Can not remove temporary directory `%s`. Please remove "
                    "it manually to avoid build issues" % build_dir,
                    fg="yellow",
                )

        default_envs = config.default_envs()
        results = []
        for env in config.envs():
            skipenv = any(
                [
                    environment and env not in environment,
                    not environment and default_envs and env not in default_envs,
                ]
            )
            if skipenv:
                results.append({"env": env})
                continue

            # print empty line between multi environment project
            if not silent and any(r.get("succeeded") is not None for r in results):
                click.echo()

            results.append(
                process_env(
                    ctx,
                    env,
                    config,
                    targets,
                    upload_port,
                    monitor_port,
                    jobs,
                    program_args,
                    is_test_running,
                    silent,
                    verbose,
                )
            )
        command_failed = any(r.get("succeeded") is False for r in results)
        if (
            not is_test_running
            and not only_monitor
            and (command_failed or not silent)
            and len(results) > 1
        ):
            print_processing_summary(results, verbose)

    # Reset custom project config
    app.set_session_var("custom_project_conf", None)

    if command_failed:
        raise exception.ReturnErrorCode(1)

    return True

def process_env(
    ctx,
    name,
    config,
    targets,
    upload_port,
    monitor_port,
    jobs,
    program_args,
    is_test_running,
    silent,
    verbose,
):
    if not is_test_running and not silent:
        print_processing_header(name, config, verbose)

    targets = targets or config.get(f"env:{name}", "targets", [])
    only_monitor = targets == ["monitor"]
    result = {"env": name, "duration": time(), "succeeded": True}

    if not only_monitor:
        result["succeeded"] = EnvironmentProcessor(
            ctx,
            name,
            config,
            [t for t in targets if t != "monitor"],
            upload_port,
            jobs,
            program_args,
            silent,
            verbose,
        ).process()

    if result["succeeded"] and "monitor" in targets and "nobuild" not in targets:
        ctx.invoke(
            device_monitor_cmd,
            port=monitor_port,
            environment=name,
        )

    result["duration"] = time() - result["duration"]

    # print footer on error or when is not unit testing
    if (
        not is_test_running
        and not only_monitor
        and (not silent or not result["succeeded"])
    ):
        print_processing_footer(result)

    return result

def print_processing_header(env, config, verbose=False):
    env_dump = []
    for k, v in config.items(env=env):
        if verbose or k in ("platform", "framework", "board"):
            env_dump.append("%s: %s" % (k, ", ".join(v) if isinstance(v, list) else v))
    click.echo(
        "Processing %s (%s)"
        % (click.style(env, fg="cyan", bold=True), "; ".join(env_dump))
    )
    terminal_width = shutil.get_terminal_size().columns
    click.secho("-" * terminal_width, bold=True)

def print_processing_footer(result):
    is_failed = not result.get("succeeded")
    util.print_labeled_bar(
        "[%s] Took %.2f seconds"
        % (
            (
                click.style("FAILED", fg="red", bold=True)
                if is_failed
                else click.style("SUCCESS", fg="green", bold=True)
            ),
            result["duration"],
        ),
        is_error=is_failed,
    )

def print_processing_summary(results, verbose=False):
    tabular_data = []
    succeeded_nums = 0
    failed_nums = 0
    duration = 0

    for result in results:
        duration += result.get("duration", 0)
        if result.get("succeeded") is False:
            failed_nums += 1
            status_str = click.style("FAILED", fg="red")
        elif result.get("succeeded") is None:
            if not verbose:
                continue
            status_str = "IGNORED"
        else:
            succeeded_nums += 1
            status_str = click.style("SUCCESS", fg="green")

        tabular_data.append(
            (
                click.style(result["env"], fg="cyan"),
                status_str,
                util.humanize_duration_time(result.get("duration")),
            )
        )

    click.echo()
    click.echo(
        tabulate(
            tabular_data,
            headers=[
                click.style(s, bold=True) for s in ("Environment", "Status", "Duration")
            ],
        ),
        err=failed_nums,
    )

    util.print_labeled_bar(
        "%s%d succeeded in %s"
        % (
            "%d failed, " % failed_nums if failed_nums else "",
            succeeded_nums,
            util.humanize_duration_time(duration),
        ),
        is_error=failed_nums,
        fg="red" if failed_nums else "green",
    )

def print_target_list(envs):
    tabular_data = []
    for env, data in load_build_metadata(os.getcwd(), envs).items():
        tabular_data.extend(
            sorted(
                [
                    (
                        click.style(env, fg="cyan"),
                        t["group"],
                        click.style(t.get("name"), fg="yellow"),
                        t["title"],
                        t.get("description"),
                    )
                    for t in data.get("targets", [])
                ],
                key=operator.itemgetter(1, 2),
            )
        )
        tabular_data.append((None, None, None, None, None))
    click.echo(
        tabulate(
            tabular_data,
            headers=[
                click.style(s, bold=True)
                for s in ("Environment", "Group", "Name", "Title", "Description")
            ],
        ),
    )


from rest_framework.response import Response

class APIResponse(Response):
    """
    自定义Response返回数据：
    return APIResponse(data={"name":'11111111'},request_id='11111111')
    return APIResponse(data={"name":'11111111'})
    return APIResponse(errcode='101', errmsg='错误',data={"name":'11111111'}, header={})
    """
    def __init__(self, errcode=0, errmsg=None, data=None, status=None, headers=None, **kwargs):
        dic = {'errcode': errcode, 'errmsg': errmsg}
        if data:
            dic = {'errcode': errcode, 'errmsg': errmsg, 'data': data}
        dic.update(kwargs)
        super().__init__(data=dic, status=status, headers=headers)


 #!/usr/bin/env python3
import urllib.request
from bs4 import BeautifulSoup
import re
import csv
#test
DOMAIN = "http://posuda-mix.ru"
CATEGORY_URL = "https://www.klenmarket.ru/shop/inventory/kitchen-equipment/pans-cauldrons-saji/aluminum-pans/"

url_from_rak = 'http://rakporcelain.com/productdetails.php?catid=51'

url_of_posuda_mix= 'http://posuda-mix.ru/shop/farfor/RAK/Bez_dekora/group_824/?on_page=100'

def parse(html):
	soup = BeautifulSoup(html)

	soup2 = BeautifulSoup(get_html(url_from_rak))

	dict = {}
	dict2 = {}
	dict3 = {}
	dict4 = {}

	for div_item in soup2.find_all('div', class_="product_sub_items"):
		name_item = " ".join(div_item.find('div', class_="product_sub_items_name").get_text().replace('/','').split())

		article = div_item.find('div', class_="product_sub_items_name").get_text().split()[0]

		article_with_dig = article
		#print(article)

		article = ''.join([i for i in article if not i.isdigit()])

		image = div_item.find('img')['src'].replace('small', 'big')
		article_from_img= image.split('_').pop().replace('.png','')

		print(len(article_from_img))

		article_from_img = ''.join([i for i in article_from_img[0:14] if not i.isdigit()])+article_from_img[14:16]

		article_from_img_without_dig = ''.join([i for i in article_from_img if not i.isdigit()])

		dict.setdefault(article,image)
		dict2.setdefault(article_with_dig,image)
		dict3.setdefault(article_from_img, image)
		dict4.setdefault( article_from_img_without_dig,image)
	print(dict3)

	#name_category = '1883 > '+ soup.find('div', class_="product-list-intro").find('h1').get_text()
	#print(name_category)

	count_of_items = 0
	change_photo= 0
	for n in range(0,1):
		items = []

		categ = ' '.join(soup.find('div',class_="main-text").find('h1').get_text().split())
		print(categ)
		for item_div in soup.find_all('td',class_="b-item-name"):

			count_of_items = count_of_items + 1
			href_item = item_div.find('a')['href']

			name_of_item = item_div.find('a').get_text().split()

			name_of_item = ' '.join(name_of_item)
			print(name_of_item)

			print(href_item)

			address_of_item = DOMAIN+ href_item

			soup2 = BeautifulSoup(get_html(address_of_item))

			image_item = ''
			if(name_of_item =="Емкость глубокая квадратная BURD25" ):
				pass

			else :
				if(name_of_item !="Сливочн" ):
					image_item = DOMAIN+soup2.find('div', class_="image_cell_inn").find('a')['href']

				image_bool = False

				print(image_item)

				description_div = soup2.find('div', class_="description_row")

				div =description_div.find_all('div', class_="description_cell")

				if(div[0].find('strong').get_text()!=None):
					article_item = div[0].find('strong').get_text()
				print(article_item)

				if(div[1].find('a').get_text() != None):
					proizvoditel = div[1].find('a').get_text()
				print(proizvoditel)
				try:

					obem = div[2].get_text()
				except IndexError:
					obem = ""
				print(obem)

				price = soup2.find('div', class_="b-item-price").find('span').get_text().replace(',','.')
				print(price)

				atricle_without_dig = ''.join([i for i in article_item if not i.isdigit()])

				if (dict2.get(article_item) == None):
					if (dict.get(atricle_without_dig)!= None):
						image_item = dict.get(atricle_without_dig)
						change_photo = change_photo +1
						image_bool = True

				if(dict3.get(article_item) != None):
					image_item = dict3.get(article_item)
					change_photo = change_photo +1
					image_bool = True

				if(dict2.get(article_item)!= None):
					image_item = dict2.get(article_item)

					change_photo = change_photo +1
					image_bool = True

			#if (image_bool is False and dict4.get(atricle_without_dig)!= None ):
			#	image_item = dict4.get(atricle_without_dig)

				#.replase('href="/shop/producers/rak-porcelain/"','href="/product-category/posuda/rak-porcelain-oae/"')
			#print(description_div)

			#image_item = item_div.find('div', class_="cat-goods__item-preview").find('a').find('img')['src']#Картинка товара

			#name_of_item = soup2.find('h1', class_="product-title").get_text()#Имя товара

			#article_item = item_div.find('span', class_="text-warning").get_text()#Артикль

			#price_item = item_div.find('span', class_="price__current-value").get_text()#Цена товара

			#table = soup2.find('div', class_="col-12 col-md-8").find('table')#Описание товара

			#href_price = soup2.find('a', class_="btn btn-dark w-100 my-3")['href']

			#soup3 = BeautifulSoup(get_html(href_price))
			#price_item = soup3.find('span', class_="price_value").get_text()
			name_category = 'Фарфоровая посуда > RAK Porcelain (ОАЭ) > Без декора >  ' + categ
			items.append({

				'name': name_of_item,
				'img_src': image_item,
				'price': price,
				'proizvoditel': proizvoditel,
				'article': article_item,
				'obem':  obem,
				#'article' : article_item,
				'category': name_category,
				'def_url': address_of_item,
			})
	csvfilename = name_category+".csv"
	with open (csvfilename, 'w') as csvfile:
		writer =  csv.writer(csvfile)
		writer.writerow(("Имя", "Изображения", 'Цена','Производитель' , 'Артикул','Короткое описание', "Категории","Исходный адрес"))
		for item in items:
			writer.writerow(
				(item['name'],item['img_src'],item['price'], item['proizvoditel'],item['article'],item['obem'],item['category'],item['def_url'])
				)

	print('COunt all items:', count_of_items)

	print('COunt of all items where changed add of IMG:', change_photo)

def get_html(url):
 	resposnse = urllib.request.urlopen(url)
 	return resposnse.read()

def main():

	parse(get_html(url_of_posuda_mix))

	#soupmain = BeautifulSoup(get_html('https://www.klenmarket.ru/shop/inventory/kitchen-equipment/pans-cauldrons-saji/'))

	'''for categ in soupmain.find_all('a', class_="shop-eq-list__cats-item"):
		print(categ['href'])
		parse(get_html(DOMAIN + categ['href']))'''

if __name__ == '__main__':
	main()


# !/usr/bin/python
# -*- coding: utf-8 -*-

#  作者：马欣宇
#  时间：2019-3-26
#  文件描述：
#  1.用于在道路文件输入时给出地图显示的中心点，以便前端人员显示地图
#  2.用于计算道路全长

import pandas as pd
import numpy as np
from refs.utils import coordinate_cal
import json

class handle_street:
    def __init__(self, file2):
        data2 = pd.read_csv(file2)
        self.data2 = np.array(data2)

    #  用道路文件中的第一条道路中心点做地图显示的中心
    def getMapCenter(self):
        firstStreet = self.data2[0]
        location1 = [firstStreet[2], firstStreet[3]]  # 起始点经纬度
        location2 = [firstStreet[4], firstStreet[5]]  # 终止点经纬度
        cul_cp = coordinate_cal.CenterPointFromListOfCoordinates()
        return cul_cp.center_geolocation([location1, location2])

    #  计算每条道路的全长和起点，格式为： 道路编号：【长度，起点】
    def getStreetAttr(self):
        streetAttr = {}
        for street in self.data2:
            if street[0] not in streetAttr.keys():
                cal = coordinate_cal.cal_distance(lat1=street[3], lon1=street[2], lat2=street[5], lon2=street[4])
                streetAttr[street[0]] = [cal.twopoint_distance(), [street[2], street[3]]]
        return streetAttr

    # 获取地图显示中心点的json
    def getMapCenterJson(self):
        location = self.getMapCenter()
        locationDict = {}
        locationDict["center_lon"] = location[0]
        locationDict["center_lat"] = location[1]
        return json.dumps(locationDict)

# file1 = 'file_toTry/time_accident.csv'
# file2 = 'file_toTry/roads.csv'
# h = handle_street(file2)
# print(h.getMapCenterJson())


'''
Export MIDI files to csv
MS 2/18/19

What pitch-classes are being played each time step? (normalize octaves)
Format:

'''

import mido
import pandas as pd
import sys

STEP = .2 # seconds

def frange(start, stop, step):
	r = []
	tmp = start
	while (tmp < stop):
		r.append(tmp)
		tmp += step
	return r

# Import track
print("Importing track")
f = sys.argv[1]
mid = mido.MidiFile(f)

# Construct intermediate dataframe, all note_on/note_off events
print("Construting intermediate dataframe")
df0 = pd.DataFrame(columns = ['t','note','vel','type'])
t = 0
i = 0
for msg in mid:
	t += msg.time
	if (msg.type == 'note_on' or msg.type == 'note_off'):
		df0.loc[i] = [t, msg.note % 12, msg.velocity, msg.type]
		i += 1
print("df0")
print(df0)

# Construct dataframe of all pitch classes each timestep
print("Constructing final dataframe")
df = pd.DataFrame(columns = ['t']+range(12))
i = 0
note_vels = [0]*12 # initialize note columns to zero
increments = frange(STEP, df0['t'].max(), STEP)
print("starting loop")
for t in increments:
	start = df0['t'] >= (t-STEP)
	end = df0['t'] < t
	on = df0['type'] == 'note_on'
	off = df0['type'] == 'note_off'
	# find note onsets
	onsets = df0[start & end & on]
	for row in range(onsets.shape[0]):
		onset = onsets.iloc[row]
		note_vels[onset['note']] = 1

	df.loc[i] = [t*1000] + note_vels

	# find note offs
	offs = df0[start & end & off]
	for row in range(offs.shape[0]):
		off = offs.iloc[row]
		note_vels[off['note']] = 0

	i += 1

# Output to csv
print("Outputing to csv")
df.to_csv(f.replace('.mid','_time.csv'),index=False)


import sys
from io import StringIO
import unittest

class TestClass(unittest.TestCase):
    maxDiff = None
    def assertIO(self, input, output):
        stdout, stdin = sys.stdout, sys.stdin
        sys.stdout, sys.stdin = StringIO(), StringIO(input)
        resolve()
        sys.stdout.seek(0)
        out = sys.stdout.read()[:-1]
        sys.stdout, sys.stdin = stdout, stdin
        self.assertEqual(out, output)

    def test_Sample_Input_1(self):
        input = """105"""
        output = """1"""
        self.assertIO(input, output)

    def test_Sample_Input_2(self):
        input = """7"""
        output = """0"""
        self.assertIO(input, output)

def resolve():
  def make_divisors(n):
    lower_divisors , upper_divisors = [], []
    i = 1
    while i*i <= n:
      if n % i == 0:
        lower_divisors.append(i)
        if i != n // i:
          upper_divisors.append(n//i)
      i += 1
    return lower_divisors + upper_divisors[::-1]

  N = int(input())

  ans = len([i for i in range(1, N+1, 2) if len(make_divisors(i)) == 8])
  print(ans)

import sys
if sys.argv[-1] == './Main.py':
  resolve()

if __name__ == "__main__":
  unittest.main()


from fastapi import FastAPI
from pydantic import BaseModel
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, text
import  uvicorn
from  DbOperations import DbOperations

db = DbOperations("sqlite:///job_details.db")
meta = MetaData()
table_object = Table(
    'jobdetails', meta,
    Column('id', Integer, primary_key=True),
    Column('name', String),
    Column('salary', String),
)
meta.create_all(db.engine)

app = FastAPI()
class Jobs(BaseModel):
    job_id: int
    job_name: str
    salary: int

# GET all jobs details in array of dictionary
@app.get("/jobs/get")
def get_complete_jobdetails():
    jobsget=db.selectall(table_object)
    return jobsget

# Listing the detail of one particular job
@app.get("/jobs/{jobid}")
def get_job_details(jobid: int ):
    job_detail = db.fetchjobdetails(table_object,jobid)
    return job_detail

#Adding new job
@app.put("/jobs/post")
def job_put(newjobs: Jobs):
    db.dbinsert(table_object,newjobs.job_id ,newjobs.job_name,newjobs.salary )
    return {"jobs": newjobs.job_id, "JobName": newjobs.job_name}

#Delete one job detail from database
@app.delete("/jobs/delete/{jobid}")
def delete_job( jobid: int):
    db.deletejob(table_object,jobid)
    return {"JobidDeleted": jobid}

# Calling uvicorn run
if __name__ == "__main__":
    uvicorn.run(app,host="127.0.0.1")


'''
basic functions

'''
import sys
import numpy as np
import torch
import cupy as cp

class Timer():
    def __init__(self,proc):
        self._begin_time = None
        self.proc = proc

    def tic(self):
        print(f'{self.proc} start!')
        self._begin_time = time.perf_counter()

    def toc(self):
        print(f'{self.proc} finish!,consuming {time.perf_counter() - self._begin_time}')
        return time.perf_counter() - self._begin_time

def binary_conversion(var):
    """
    二进制单位转换
    :param var: 需要计算的变量，bytes值
    :return: 单位转换后的变量，kb 或 mb
    """
    var_size = sys.getsizeof(var)
    assert isinstance(var_size, int)
    if var_size <= 1024:
        return f'占用 {round(var_size / 1024, 2)} KB内存'
    else:
        return f'占用 {round(var_size / (1024 ** 2), 2)} MB内存'

def show_info(show_OI = False,show_values = False,**kws):
    length = 'no length'
    keys = 'not a dict'
    values = 'not a dict'
    shape = 'not a ndarray'
    size = 'not a ndarray'
    for key,x in kws.items():
        if hasattr(x,'__len__'):
            length = len(x)
        if isinstance(x,dict):
            keys = x.keys()
            values = x.values()
        if type(x) is np.ndarray or type(x) is cp.ndarray or type(x) is torch.Tensor:
            shape = x.shape
            size = x.size
        print(key)
        print(f'allocated memory:{binary_conversion(x)}')
        print(f'\ntype:{type(x)} \nlen:{length}\nshape:{shape}\nsize:{size}\nkeys:{keys}\n')
        if show_OI:
            print(f'original info:{x}\n')
        if show_values:
            print(f'\nvalues:{values}\n')


import logging

# django dependencies
from rest_framework.views import APIView
from rest_framework.response import Response

# project dependencies
from . import helpers
from .models import Video, get_video_by_id

from video_manager.constants import DEFAULT_VIDEO_FOLDER

logger = logging.getLogger('django')

class VideoAlreadyExistsException(Exception):
    pass

class VideosApi(APIView):

    def get(self, _):
        response = {
            "message": {},
            "status_code": 200
        }
        try:
            complete_video_list = []
            for each_video in Video.objects.all():
                complete_video_list.append(
                    helpers.get_video_metadata_response(each_video)
                )
            response["message"] = {"videos": complete_video_list}
            logger.info(f"Videos found {len(complete_video_list)}")
        except Exception as err:
            logger.error(err)
            response["message"] = "Fetching Video list failed"
            response['status_code'] = 500
        return Response(response['message'], status=response['status_code'], headers=None)

    def post(self, request):
        response = {
            "message": {},
            "status_code": 201
        }
        try:
            file_name = helpers.get_field_value('video_name', request)
            video_folder = helpers.get_field_value('video_folder', request)

            if helpers.video_exists(video_folder + file_name):
                raise VideoAlreadyExistsException

            file_path = helpers.save_video_file(
                video_folder + file_name, request.data.get('video_file'))
            video_metadata = helpers.get_video_data(
                DEFAULT_VIDEO_FOLDER + file_path)

            new_video = Video(name=file_name,
                              size=video_metadata['format']['size'],
                              path=video_folder,
                              duration=video_metadata['format']['duration'],
                              codec=video_metadata['format']['format_name'],
                              container=video_metadata['format']['tags']['major_brand'])
            new_video.save()
            response["message"] = helpers.get_video_metadata_response(
                new_video)
            logger.info(f"video upload successfully with id {new_video.id}")
        except VideoAlreadyExistsException as err:
            logger.error(err)
            response["message"] = f"Video with the name {file_name} already present at that path {video_folder}."
            logger.error(response["message"])
            response['status_code'] = 409
        except Exception as err:
            logger.error(err)
            try:
                # delete video if it was present
                helpers.delete_video_file(file_path)
                logger.info("video deleted")
            except:
                logger.info("video was not saved")
            response["message"] = "Video Upload failed, please try again"
            response['status_code'] = 500

        return Response(response['message'], status=response['status_code'], headers=None)

class VideoMetadataApi(APIView):
    def get(self, _, video_id):
        response = {
            "message": {},
            "status_code": 200
        }
        try:
            required_video = get_video_by_id(video_id)
            response["message"] = helpers.get_video_metadata_response(
                required_video)
            logger.info(f"video found with ID {required_video.id}")
        except Exception as err:
            logger.error(err)
            response["message"] = f"Video with ID {video_id} not found in system"
            response['status_code'] = 404

        return Response(response['message'], status=response['status_code'], headers=None)

    def put(self, request, video_id):
        response = {
            "message": {},
            "status_code": 200
        }
        try:
            required_video = get_video_by_id(video_id)
            current_path = required_video.path + required_video.name
            updated_fields = request.data

            for each_field_name, each_field_value in updated_fields.items():
                if each_field_name == "video_name":
                    required_video.name = each_field_value
                elif each_field_name == "video_folder":
                    required_video.path = each_field_value

            if current_path != required_video.path + required_video.name:       # move file if change in name/folder
                logger.info("path not same")
                logger.debug(
                    f"Current Path: {current_path}, new path: {required_video.path + required_video.name}")

                helpers.move_file(DEFAULT_VIDEO_FOLDER + current_path,
                                  DEFAULT_VIDEO_FOLDER + required_video.path + required_video.name)
                logger.info(
                    f"file moved to destination {DEFAULT_VIDEO_FOLDER + required_video.path + required_video.name} for video id {video_id}")
            required_video.save()       # save entry in db
            response["message"] = helpers.get_video_metadata_response(
                required_video)
        except Exception as err:
            logger.error(err)
            response["message"] = "Error occured while updating video metadata"
            response['status_code'] = 500
        return Response(response['message'], status=response['status_code'], headers=None)

    def delete(self, _, video_id):

        response = {
            "message": {},
            "status_code": 200
        }
        try:
            required_video = get_video_by_id(video_id)
            response["message"] = helpers.get_video_metadata_response(
                required_video)

            file_path = helpers.delete_video_file(
                required_video.path + required_video.name)  # delete video from filesystem

            required_video.delete()
            logger.error(f"Video deleted with ID {video_id}")
        except Exception as err:
            logger.error(err)
            response["message"] = f"Video with ID {video_id} not found"
            logger.error(response["message"])
            response['status_code'] = 404
        return Response(response['message'], status=response['status_code'], headers=None)


"""Wrapper for Torch Dataset class to enable contrastive training
"""
import torch
from torch import Tensor
from torch.utils.data import Dataset
from torchaudio_augmentations import Compose
from typing import Tuple, List

class ContrastiveDataset(Dataset):
    def __init__(self, dataset: Dataset, input_shape: List[int], transform: Compose):
        self.dataset = dataset
        self.transform = transform
        self.input_shape = input_shape
        self.ignore_idx = []

    def __getitem__(self, idx) -> Tuple[Tensor, Tensor]:
        if idx in self.ignore_idx:
            return self[idx + 1]

        audio, label = self.dataset[idx]

        if audio.shape[1] < self.input_shape[1]:
            self.ignore_idx.append(idx)
            return self[idx + 1]

        if self.transform:
            audio = self.transform(audio)
        return audio, label

    def __len__(self) -> int:
        return len(self.dataset)

    def concat_clip(self, n: int, audio_length: float) -> Tensor:
        audio, _ = self.dataset[n]
        batch = torch.split(audio, audio_length, dim=1)
        batch = torch.cat(batch[:-1])
        batch = batch.unsqueeze(dim=1)

        if self.transform:
            batch = self.transform(batch)

        return batch


#！ -*- coding:utf-8 -*-
import sys
import pdb
import codecs
import MySQLdb

sys.path.insert(0, '..')
from conn import ConnectDBA

class CommentJiebaDB(ConnectDBA):
    """
    create kw tables
    """
    STOP_WORDS = 0
    COMPONENT_WORDS = 1
    STOPWORDS_FILE = 'stopwords.txt'
    COMPONENT_FILE = 'component.txt'
    KEYWORD_FILE = 'keywords.txt'

    def create_kw(self):
        """
        create kw table 来自结巴
        """
        sql = """
              create table if not exists kw (
                  id int(32) not null auto_increment,
                  keyword varchar(64),
                  counter int default 1,
                  mark int default 0,
                  primary key (id),
                  unique index kw_index (keyword)
              )engine=myisam auto_increment = 1 default charset = utf8;
        """
        self.cursor.execute(sql)
        self.db.commit()

    def create_kw_search(self):
        """
        create kw table
        """
        sql = """
              create table if not exists kw_search (
                  id int(32) not null auto_increment,
                  keyword varchar(64),
                  counter int default 1,
                  mark int default 0,
                  primary key (id),
                  unique index kw_index (keyword)
              )engine=myisam auto_increment = 1 default charset = utf8;
        """
        self.cursor.execute(sql)
        self.db.commit()

    def create_kw_dict(self):
        """
        词库:存储无效词type=0，如：标点符号：，、等
             存储组合词type=1，将有特殊意义的词作为一个关键词整体
        """
        sql = """
              create table if not exists kw_dict (
                  id int(32) not null auto_increment,
                  keyword varchar(64),
                  counter int default 1,
                  type tinyint  ,
                  primary key (id),
                  unique index kw_dict_index (keyword, type)
              )engine=myisam auto_increment = 1 default charset = utf8;
        """
        self.cursor.execute(sql)
        self.db.commit()

    def insertMany_kw_dict(self, kw_list):
        """
        插入数据：kw_dict
        """
        sql = 'insert ignore into kw_dict(keyword, type) values( %s, %s)  '
        try:
            self.cursor.executemany(sql, kw_list)
            self.db.commit()
        except MySQLdb.Error as e:
            print ('kw_list:{0} exist already.'.format(str(e)))

    def create_kw_cloud(self):
        """
        词云图
        tips:
        中文分词和词性标注的性能对关键词抽取的效果至关重要
        +--------+---------+
        | 词库名称| type值  |
        +--------+---------+
        | 正面评价|   0     |
        +--------+---------+
        | 负面评价|   1     |
        +--------+---------+
        |  bug   |    2    |
        +--------+---------+
        | 建议意见|    3    |
        +--------+---------+
        |  刷 评  |    4    |
        +--------+---------+
        """
        sql = """
              create table if not exists kw_cloud (
                  id int(32) not null auto_increment,
                  keyword varchar(64),
                  counter int default 1,
                  primary key (id),
                  type tinyint
              )engine=myisam auto_increment = 1 default charset = utf8;
        """
        self.cursor.execute(sql)
        self.db.commit()

class UpdateKw(CommentJiebaDB):
    """
    更新词库
    """
    def update_dict(self, dict_type = CommentJiebaDB.STOP_WORDS):
        """
        从stopwords.txt中更新无效词/停用词库
        或
        从component.txt中更新组合词库

        默认更新的是无效词库
        dict_type = 1时，更新的是组合词库
        """
        if dict_type == self.STOP_WORDS:
            f = codecs.open(self.STOPWORDS_FILE, 'r', 'utf-8') # 停用词库
        else:
            f = codecs.open(self.COMPONENT_FILE, 'r', 'utf-8') # 组合词库

        if f:
            content = f.read()
            content = content.replace('\r\n', ' ')
            kw_list = content.split(' ')
            try:
                kw_list.remove('')
            except ValueError:
                pass
            #
            t_kw = [(x, dict_type ) for x in kw_list]
            self.insertMany_kw_dict(t_kw)
            f.close()
        else:
            print("ERROR: Can not find the files...")

    def read_kw(self, kw_tb="kw_search"):
        """
        从kw表或者kw_search表中读取未归类的数据
        当kw_tb == kw的时候，从kw表中读取，否则从kw_search表中读取
        """

        sql = "select keyword, id from {0} where mark = 0 limit 10000".format(kw_tb)
        try:
            self.cursor.execute(sql)
            keywords = self.cursor.fetchall()
            content = ''
            for keyword in keywords:
                content = content+','+keyword[0]

            f = codecs.open(self.KEYWORD_FILE, 'a', 'utf-8') # 组合词库
            f.write(content)
            f.close()

            ids = [kw[1] for kw in keywords]
            print(tuple(ids))

            # 更新数据库
            updatesql = "update {0} set mark = 1 where id in {1}".format(kw_tb, tuple(ids))
            self.cursor.execute(updatesql)
            self.db.commit()
        except MySQLdb.Error as e:
            print ('ERROR: read_kw:{0}.'.format(str(e)))

if __name__ == "__main__":
    cjd = UpdateKw()
    #cjd.update_dict() # 停用词更新
    cjd.read_kw()
    #cjd.update_dict(cjd.COMPONENT_WORDS) # 更新组合词
    cjd.close()


'''
Exercise of subset of Regular Expressions implement.
'''

from decorator import decorator

null = frozenset()
dot = lambda text: set([text[1:]]) if text else null
eol = lambda text: set(['']) if text == '' else null

def search(pattern, text):
    "Match pattern anywhere in text; return longest earliest match or None."
    for i in range(len(text)):
        m = match(pattern, text[i:])
        if m is not None:
            return m

def match(pattern, text):
    "Match pattern against start of text; return longest match found or None."
    remainders = pattern(text)
    if remainders:
        shortest = min(remainders, key=len)
        return text[:len(text) - len(shortest)]

def lit(s):
    return lambda text: set([text[len(s):]]) if text.startswith(s) else null

def seq(x, y):
    return lambda text: set.union(*map(y, x(text)))

def alt(x, y):
    return lambda text: set.union(x(text), y(text))

def star(x):
    return lambda text: set([text]) | set(t2 for t1 in x(text) if t1 != text for t2 in star(x)(t1))

def plus(x):
    return seq(x, star(x))

def opt(x):
    return alt(lit(''), x)  #opt(x) means that x is optional

def oneof(chars):
    return lambda text: set([text[1:]]) if text and text[0] in chars else null

@decorator
def n_ary(f):
    """Given binary function f(x, y), return an n_ary function such
    that f(x, y, z) = f(x, f(y,z)), etc. Also allow f(x) = x."""

    def n_ary_f(x, *args):
        # if not args:
        #     return x
        # elif len(args) == 1:
        #     return f(x, *args)
        # return f(x, n_ary_f(args[0], *args[1:]))
        return x if not args else f(x, n_ary_f(*args))

    return n_ary_f

def matchset(pattern, text):
    '''
    Match pattern at start of text; return a set of remainders of text.
    '''
    op, x, y = components(pattern)
    if op == 'lit':
        return set([text[len(x):]]) if text.startswith(x) else null
    elif op == 'seq':
        return set(t2 for t1 in matchset(x, text) for t2 in matchset(y, t1))
    elif op == 'alt':
        return matchset(x, text) | matchset(y, text)
    elif op == 'dot':
        return set([text[1:]]) if text else null
    elif op == 'oneof':
        return set([text[1:]]) if text.startswith(x) else null
    elif op == 'eol':
        return set(['']) if text == '' else null
    elif op == 'star':
        return (set([text]) | set(
            t2 for t1 in matchset(x, text) for t2 in matchset(pattern, t1)
            if t1 != text))
    else:
        raise ValueError('unknown pattern: {}'.format(pattern))

def components(pattern):
    "Return the op, x, and y arguments; x and y are None if missing."
    x = pattern[1] if len(pattern) > 1 else None
    y = pattern[2] if len(pattern) > 2 else None
    return pattern[0], x, y

def test():
    "Tests."
    assert lit('abc') == ('lit', 'abc')
    assert seq(('lit', 'a'), ('lit', 'b')) == ('seq', ('lit', 'a'), ('lit',
                                                                     'b'))
    assert alt(('lit', 'a'), ('lit', 'b')) == ('alt', ('lit', 'a'), ('lit',
                                                                     'b'))
    assert star(('lit', 'a')) == ('star', ('lit', 'a'))
    assert plus(('lit', 'c')) == ('seq', ('lit', 'c'), ('star', ('lit', 'c')))
    assert opt(('lit', 'x')) == ('alt', ('lit', ''), ('lit', 'x'))
    assert oneof('abc') == ('oneof', ('a', 'b', 'c'))

    assert matchset(('lit', 'abc'), 'abcdef') == set(['def'])
    assert matchset(('seq', ('lit', 'hi '), ('lit', 'there ')),
                    'hi there nice to meet you') == set(['nice to meet you'])
    assert matchset(('alt', ('lit', 'dog'), ('lit', 'cat')),
                    'dog and cat') == set([' and cat'])
    assert matchset(('dot', ), 'am i missing something?') == set(
        ['m i missing something?'])
    assert matchset(oneof('a'), 'aabc123') == set(['abc123'])
    assert matchset(oneof('ab'), 'aabc123') == set(['abc123'])
    assert matchset(('eol', ), '') == set([''])
    assert matchset(('eol', ), 'not end of line') == frozenset([])
    assert matchset(('star', ('lit', 'hey')),
                    'heyhey!') == set(['!', 'heyhey!', 'hey!'])

    assert match(('star', ('lit', 'a')), 'aaabcd') == 'aaa'
    assert match(('alt', ('lit', 'b'), ('lit', 'c')), 'ab') is None
    assert match(('alt', ('lit', 'b'), ('lit', 'a')), 'ab') == 'a'
    assert search(('alt', ('lit', 'b'), ('lit', 'c')), 'ab') == 'b'

    g = alt(lit('a'), lit('b'))
    assert g('abc') == set(['bc'])

    return 'tests pass'

def test2():
    def f(x, y):
        return x + y

    fn = n_ary(f)

    print(fn(1))
    print(fn(1, 2))
    print(fn(1, 2, 3))
    print(fn(1, 2, 3, 4))

    def test_fun(x, y):
        return ('seq', x, y)

    test_fun = n_ary(test_fun)
    print(test_fun(1, 2, 3, 4))

    @n_ary
    def test_fun2(x, y):
        "test_fun2 wrapped in decorator"
        return ('seq', x, y)

    print(test_fun2(1, 2, 3, 4))
    help(test_fun2)
    help(n_ary)
    print('test2 success')

if __name__ == '__main__':
    # print(test())
    test2()


"""
Created on Wed Sep  1 16:07:35 2021

@author: John Waldo (jw922)
@author: Adam Wolfson (amw337)

HW0

Assumptions:
    Assuming that the iris data and names are in the current
    directory. If not, please update the main function with
    the proper path.

"""

from matplotlib import pyplot as plt
import math

def getFeaturePairs(data, feature1, feature2):
    '''
    Pulls the two features of interest.

    Parameters
    ----------
    Data : Matrix
        Matrix of data.
    feature1 : Int
        Index of first feature
    feature2: Int
        Index of second feature

    Returns
    -------
    firstFeatures : All datapoints of first feature
    secondFeatures : All datapoints of second feature
    '''

    firstFeatures = data[feature1]
    secondFeatures = data[feature2]

    return [firstFeatures, secondFeatures]

def getPlotDimensions(featureCount):
    '''
    Get optimal dimensions of subplots

    Parameters
    ----------
    featureCount : Int
        The total number of features.

    Returns
    -------
    plotRows : The optimal number of rows
    plotCols : The optimal number of cols

    '''

    totalPlots = (featureCount - 1)*featureCount/2

    rootSize = math.sqrt(totalPlots)

    lowerBound = math.floor(rootSize)
    upperBound = math.ceil(rootSize)

    if lowerBound*upperBound >= totalPlots:
        plotRows = lowerBound
        plotCols = upperBound
    else:
        plotRows = upperBound
        plotCols = upperBound

    return [plotRows, plotCols]

class IrisPlotter(object):
    """
    Class that handles visualization of iris data
    """

    def __init__(self, datasource, namesource):

        self.datasource = datasource
        self.namesource = namesource

    def main(self):
        '''
        Main function for this class.
        Manages pulling, parsing, and plotting the data
        '''

        irisData = self.pullData(self.datasource)

        # irisNames = pullNames(self.namesource)
        # HARD CODED NAMES:
        irisNames = ['sepal length', 'sepal width', 'petal length', 'petal width']

        self.plotPairwiseData(irisData, irisNames)

    def pullData(self,source):
        '''
        Load the raw data from the given path
        as a dictionary ordered by Iris type

        Parameters
        ----------
        source : String
            Path to data file

        Returns
        -------
        irisData : Dictionary
            Dictionary with iris type as key.
            Values are the feature data for
            instances of the given type.
        '''

        irisData = {}

        for line in open(source):
            details = line[:-2].split(",")
            if len(details) > 1:
                flower = details[-1]
                data = details[:-1]

                if flower not in irisData:
                    irisData[flower] = []

                    for d in range(len(data)):
                        irisData[flower].append([])

                for d in range(len(data)):
                    irisData[flower][d].append(float(data[d]))

        return irisData

    def plotPairwiseData(self, data, featNames):

        '''
        Plots the feature data in pairs.

        Parameters
        ----------
        data : Dictionary
            Dictionary with iris type as key.
            Values are the feature data for instances of the given type.

        featNames : List
            List of feature names

        Returns
        -------
        None

        '''

        flowers = list(data.keys())

        totalFeatures = len(data[flowers[0]])

        [rows, cols] = getPlotDimensions(totalFeatures)

        fig, axes = plt.subplots(rows, cols)

        for feat in range(len(flowers)):
            flower = flowers[feat]

            plotNumber = [0,0]

            for f1 in range(totalFeatures):
                for f2 in range(f1):
                    [firstFeatures, secondFeatures] = getFeaturePairs(data[flower], f1, f2)

                    axes[plotNumber[0],plotNumber[1]].scatter(firstFeatures,
                                                              secondFeatures,
                                                              label = flower)

                    axes[plotNumber[0],plotNumber[1]].set_xlabel(featNames[f1])
                    axes[plotNumber[0],plotNumber[1]].set_ylabel(featNames[f2])

                    plotNumber[1] += 1

                    if plotNumber[1] >= cols:
                        plotNumber[0] += 1
                        plotNumber[1] = 0

        fig.suptitle("Iris Data")
        fig.tight_layout()
        plt.legend(bbox_to_anchor=(0, -0.35, 1, 0), mode="expand", borderaxespad=0)

        plt.show()

if __name__ == "__main__":
    source = './iris.data'
    names = './iris.names'
    irisObj = IrisPlotter(source, names)
    irisObj.main()


"""
Q-Learning
MiniMax
(2 in 1)
"""

from typing import List, Tuple
from enum import Enum
from math import inf
from copy import deepcopy
from collections import UserDict
from random import random, choice

class Player(Enum):
    X = 'X'   # MAX-player
    O = 'O'   # MIN-player
    EMPTY = chr(0x25a1)
    DRAW = "DRAW"

class State():
    gamma = 0.9
    def __init__(self, *args, **kwargs):
        self._mx = list(args[0]) if args else [[Player.EMPTY,]*3 for _ in range(3)]
        self.utility_value = kwargs.get("utility")   # None by default
        self._terminal = None

        # Check state validity
        l = sum(self._mx, [])
        xx,oo = (l.count(v) for v in (Player.X, Player.O))
        if not(0 <= (xx - oo) <=1):
            raise ValueError("Invalid board state")

    def player(self) -> Player:
        """Returns the player who's turn it is"""
        l = sum(self._mx, [])
        xx,oo = (l.count(v) for v in (Player.X, Player.O))
        return Player.X if xx - oo == 0 else Player.O

    def actions(self) -> List[Tuple[int,int]]:
        """Returns valid moves from this state"""
        return [(i,j) for i in range(3) for j in range(3) if self._mx[i][j] == Player.EMPTY]

    def transition(self, action) -> 'State':
        """returns the next state"""
        assert min(action) >= 0 and max(action) <= 2
        copy = deepcopy(self._mx)
        r,c = action
        if copy[r][c] != Player.EMPTY:
            raise ValueError("bad action (tuple-index)")
        player = self.player()
        copy[r][c] = player

        new = State(copy)

        # for printing purposes
        r,c = [(i,j) for i in range(3) for j in range(3) if self._mx[i][j] != new._mx[i][j]][0]
        new.move = "ABC"[r] + str(c+1)

        return new

    def children(self) -> List:
        """returns next states from this state.
        This is a wrapper-function around actions() and transition() methods"""
        return [self.transition(action) for action in self.actions()]

    @property
    def terminal(self) -> bool:
        """is this state terminal?"""
        if self.utility():
            return True

        l = sum(self._mx, [])

        # This line is for RL (play to the last square, even when draw)
        return False if Player.EMPTY in l else True

        # This block is for Adversarial Search (returns True when it is clear that it is a draw)
        slices = [slice(i,None,3) for i in range(3)] + [slice(None,None,4), slice(2,-1,2)]

        for e in self._mx + slices:
            S = set(l[e] if isinstance(e, slice) else e)
            if not {Player.X, Player.O}.issubset(S) and Player.EMPTY in S:
                return False
        return True

    def winner(self):
        if not self.terminal:
            return None
        cost = self.utility()
        winner = {0:Player.DRAW, 1:Player.X, -1:Player.O}[cost]
        return winner

    def utility(self, depth=None) -> int or float:
        """returns the utility (used in Adverserial Search)"""
        l = sum(self._mx, [])
        slices = [slice(i,None,3) for i in range(3)] + [slice(None,None,4), slice(2,-1,2)]
        depth = abs(depth or 1)

        for e in self._mx + slices:
            t = l[e] if isinstance(e, slice) else e
            if t.count(Player.X) == 3:
                return 1 / depth
            elif t.count(Player.O) == 3:
                return -1 / depth
        return 0

    def __str__(self):
        return str.join('\n', ((str.join('', (v.value for v in row))) for row in self._mx))

    def __repr__(self):
        d = {Player.X: [], Player.O: []}
        for i,p in enumerate(sum(self._mx, [])):
            if p == Player.EMPTY:
                continue
            d[p].append(i+1)

        s = 'X:' + str(d[Player.X])[1:-1].replace(' ', '') + " / O:" + str(d[Player.O])[1:-1].replace(' ', '')
        return self.__class__.__name__ + f"({s})"

    def __lt__(self, other):
        return self.utility_value < (other.utility_value if isinstance(other, State) else other)
    def __gt__(self, other):
        return self.utility_value > (other.utility_value if isinstance(other, State) else other)

    def __hash__(self):
        return hash(tuple(sum(self._mx, [])))
    def __eq__(self, other):
        if type(self) is not type(other):
            raise TypeError(f"equality operation is not supported between '{self.__class__.__name__}' and '{other.__class__.__name__}'")
        return hash(self) == hash(other)

    @property
    def user_friendly_board(self):
        l = ["  123"] + [c+' '+line for c,line in zip("ABC", str(self).strip().split('\n'))]
        return str.join('\n', l)

    def reward(self, action=None):
        """returns immediate reward from the standpoint of player O"""
        d = {Player.X: -100, Player.O: 100, Player.DRAW: 0, None: 0}
        state = self if not action else self.transition(action)
        winner = state.winner()
        return d[winner]

    def value(self):
        """returns the value of the state (recursive function with a base case)"""
        if self.terminal:
            return self.reward()
        values = (s.value() for s in self.children())
        return self.reward() + self.gamma * max(values)

class QTable(UserDict):
    def __getitem__(self, index):
        if type(index) not in (State, tuple):
            raise TypeError("bad argument type")

        s,a = index if type(index) is tuple else (index, None)

        if s not in self:
            self.data[s] = {a:0 for a in s.actions()}
        return self.data[s] if a is None else self.data[s][a]

    def __setitem__(self, index, item):
        if not (type(index) is tuple and type(item) in (int,float)):
            raise TypeError("bad arguments")
        s,a = index
        self[s][a] = item

def policy(state, Q_table, epsilon=0):
    # deterministic
    if not epsilon or random() < (1 - epsilon):
        return max(Q_table[state], key=lambda k: Q_table[state][k])
    # stochastic
    return choice(tuple(Q_table[state]))

def min_play(state, depth=0):
    # Base case
    if state.terminal:
        if depth==0: return state
        return state.utility(depth=depth)
    # Recursive
    best = inf
    for child in state.children():
        v = max_play(child, depth-1)
        if depth==0:
            child.utility_value = v
            v = child
        best = min(v, best)

    # Figure out what move the ai has made for printing purposes
    if depth == 0 and isinstance(best, State):
        r,c = [(i,j) for i in range(3) for j in range(3) if state._mx[i][j] != best._mx[i][j]][0]
        best.move = "ABC"[r] + str(c+1)
    return best

def max_play(state, depth=0):
    # Base case
    if state.terminal:
        if depth==0: return state
        return state.utility(depth=depth)
    # Recursive
    best = -inf
    for child in state.children():
        v = min_play(child, depth-1)
        if depth==0:
            child.utility_value = v
            v = child
        best = max(v, best)

    # Figure out what move the ai has made for printing purposes
    if depth == 0 and isinstance(best, State):
        r,c = [(i,j) for i in range(3) for j in range(3) if state._mx[i][j] != best._mx[i][j]][0]
        best.move = "ABC"[r] + str(c+1)
    return best

def decorator(func):
    """Checks whether the square chosen by the user is occupied"""
    def closure(*args, **kwargs):
        state = kwargs.get("state")
        actions = kwargs.get("actions")

        while True:
            user_input = func()
            if not(state or actions):
                return user_input

            # Get allowed actions (i.e. moves) - if not provided by the user
            actions = actions or state.actions()

            # Check validity of the move
            if user_input not in actions:
                print("The selected square is taken.")
            else:
                return user_input
    return closure

@decorator
def get_input():
    rows = {'a':0, 'b':1, 'c':2}
    cols = {str(i+1):i for i in range(3)}
    while True:
        s = input("Your move: ")
        s = s.lower().strip()
        if len(s)!=2 or s[0] not in rows.keys() or s[1] not in cols.keys():
            print("bad input - accpted format: A1")
            continue
        else:
            return (rows[s[0]], cols[s[1]])

def check_winner(state):
    print(">>>WINNER:", state.winner())

    if state.terminal:
        print(state.user_friendly_board)
        cost = state.utility()
        winner = {0:None, 1:Player.X, -1:Player.O}[cost]
        if not winner:
            print("\nDRAW")
        else:
            print("\nWINNER:", winner.value)
        return winner or "DRAW"
    return False

def controller(player=None, Q_table=None):
    """
    Controlls the game.
    The AI is able to play in two modes: MiniMax or Q-Learning.

    If player=None  then the user is asked.
    """

    print("\nYour AI opponent: " + ("RL" if Q_table else "MiniMax"))

    human_as_X = None if player is None else True if 'X' in str(player).upper() else False

    if human_as_X is None:
        print("Do you want to play as 'X' or 'O'?\t\t(the 'X' starts the game)")
        answer = input("X or O? ")[-1].upper(); print()
        human_as_X = 'X' == answer

    # Initial state
    s = State()

    # If the human goes first
    if human_as_X:
        print(s.user_friendly_board)
        a = get_input(state=s)
        s = s.transition(a)
        print(s.user_friendly_board)

    # The main loop
    while True:
        # AI's move
        if Q_table:
            a = policy(s, Q)
            s = s.transition(a)
        else:
            s = (min_play if human_as_X else max_play)(s)

        print(f"\nMy move: {s.move}")
        print(s.user_friendly_board)

        winner = s.winner()
        if winner: break

        # Human's move
        a = get_input(state=s)
        s = s.transition(a)

        print(s.user_friendly_board)

        winner = s.winner()
        if winner: break

    print("\nThe winner is: " + winner.value)
    return winner

#_____________________________________________________________________________

## Q-learning
η = 0.9
γ = 0.9

r = 1
r_draw = 0.2
γ = 0.9
ε = 0.3
η = 0.25

Q = QTable()

for episode in range(100000):
    s = State()
    path = []

    while not s.terminal:
        a = policy(s, Q, ε)
        path.append((s,a))
        s = s.transition(a)

    # Who's the winner?
    winner = s.winner()

    #
    for (s,a) in path[::-1]:
        if winner == Player.DRAW:
            TD = r_draw - Q[s,a]
        elif s.player() == winner:
            TD = r - Q[s,a]
            #Q[s,a] += r
        else:
            TD = -r - Q[s,a]
            #Q[s,a] -= r
        Q[s,a] = Q[s,a] + η * TD

        # doscount the reward
        r *= γ
        r_draw *= γ

#______________________________________________________

winner = controller(Q_table=Q)


# -*- coding: utf-8 -*-
# @Time    : 18/7/27 上午10:40
# @Author  : Edward
# @Site    :
# @File    : tfidf.py
# @Software: PyCharm Community Edition

from collections import Counter

import jieba
from gensim.models import TfidfModel
from gensim.corpora.dictionary import Dictionary

class TfIdf(object):
    '''
    该类适用于文本关键词抽取或同性质特征抽取任务
    该类使用TfIdf模型抽取关键词

    >>> sentences = ['a b c d', 'a b c e', 'a c c e', 'b a e d']

    >>> tfidf = TfIdf(sentences, cutter=lambda x: x.split(' '))
    >>> ret = tfidf.get_sentence_key_words('c e f g')
    >>> ret
    [('c', 0.7071067811865476), ('e', 0.7071067811865476)]
    >>> ret = tfidf.get_key_words()
    >>> ret
    [('b', 3), ('c', 3), ('e', 3)]
    '''

    def __init__(self, sentences, stop_words=set(), cutter=jieba.cut):
        '''
        初始化TfIdf特征抽取器

        :param sentences: 文本列表
        :param stop_words: 停用词表，需要为list/set/tuple
        :param cutter: 分词方式，默认为使用jieba分词处理中文，如果要处理英文或者其他分词方式，只要丢入一个分词函数就OK
        '''
        self.stop_words = set(stop_words)
        self.cutter = cutter
        self.common_texts = [
            [word for word in cutter(sentence) if word not in self.stop_words]
            for sentence in set(sentences)
            if isinstance(sentence, str)
        ]
        self.common_dictionary = Dictionary(self.common_texts)
        self.common_corpus = [self.common_dictionary.doc2bow(text) for text in self.common_texts]

        self.tfidf = TfidfModel(self.common_corpus)
        self.vectors = [
            self.tfidf[self.common_corpus[index]]
            for index in range(len(self.common_corpus))
            if self.tfidf[self.common_corpus[index]
            ]
        ]

    def get_key_words(self, top_n=10, top_word=1):
        '''
        获取初始化文本中的关键词
        计算方式是通过获取每个句子中，tfidf值最高的top_word个词。然后统计出现次数获得的
        :param top_n: 返回前top_n个关键词
        :param top_word: 每个句子的前top_word个词
        :return: 返回列表，每一项分别为(词, 词频)
        '''
        top_word_index = [
            sorted(vector, key=lambda x: -x[1])[:top_word]
            for vector in self.vectors
        ]

        # 转化为关键词并统计出现次数
        key_words_index = []
        for line in top_word_index:
            key_words_index += [item[0] for item in line]

        key_words_index = Counter(key_words_index).items()
        key_words = [(self.common_dictionary[item[0]], item[1]) for item in key_words_index]

        return sorted(key_words, key=lambda x: (-x[1], x[0]))[:top_n]

    def get_sentence_key_words(self, sentence, top_n=3):
        '''
        获取句子关键词
        :param sentence: 待抽取的文本
        :param top_n: 抽取top_n个关键词
        :return: 返回一个列表，每一项分别为(词, 词权重)
        '''
        ret = self.transform(sentence, False)
        return sorted(ret, key=lambda x: (-x[1], x[0]))[:top_n]

    def transform(self, sentence, return_word=True):
        '''
        获取句子每个词的权重
        :param sentence: 待获取文本
        :param return_word: 返回的词权重是否使用词。True则返回词，False则返回index
        :return: 返回一个列表，每一项分别为(词, 词权重)
        '''
        words_list = list(self.cutter(sentence))
        common_corpus = self.common_dictionary.doc2bow(words_list)
        words_tfidf_value = self.tfidf[common_corpus]
        return [(self.common_dictionary[item[0]], item[1]) for item in words_tfidf_value] \
            if return_word else words_tfidf_value

if __name__ == '__main__':
    import doctest
    doctest.testmod()


"""
This is an implimentation of the structured bayesian optimisation approach based on
BOAT: Building Auto-Tuners with Structured Bayesian Optimization
Valentin Dalibard, Michael Schaarschmidt, Eiko Yoneki

"""

import copy
import numpy as np

import torch
import torch.nn as nn
from torch.distributions import constraints, transform_to
import torch.optim as optim
import torch.autograd as autograd

import pyro
import pyro.distributions as dist
import pyro.contrib.gp as gp
from pyro.nn import PyroSample, PyroModule
from pyro.infer import autoguide, SVI, Trace_ELBO

class TargetFunction:
    """A class to represent target function"""

    def __init__(self, ranges):
        """
        Constructor for a target function - processess ranges into constrains.
            ranges: range for each dimension

        """

        self.ranges = ranges

        self.contstraints = []

        for range_el in ranges:
            self.contstraints.append(
                constraints.interval(range_el[0], range_el[1]))

    def eval(self, x):
        """
        Evaluates target function any set of input points
            x: input points
        """

        raise NotImplementedError

normal_phi = lambda x: torch.exp(-x.pow(2) / 2) / np.sqrt(2 * np.pi)
normal_Phi = lambda x: (1 + torch.erf(x / np.sqrt(2))) / 2

class SemiParametricModel(PyroModule):

    def __init__(self, X, y, parametric_mean, kernel, jitter=1e-06):
        """ Defines a semi-parametric model, where the `parametric_mean` is a `PyroModule` """
        super().__init__()

        self.X = X
        self.y = y

        self.parametric_mean = parametric_mean

        self.kernel = kernel
        self.gp = gp.models.GPRegression(X, y, self.kernel, jitter=jitter)

    @pyro.nn.pyro_method
    def model(self):
        # Model definition code: explicitly subtract out the parametric mean, then call gp.model
        self.gp.set_data(self.X, self.y - self.parametric_mean(self.X))
        return self.gp.model()

    def forward(self, X):
        ''' Predict on new data points '''

        # reset "data" of GP to reflect mean estimate
        self.gp.set_data(self.X, self.y - self.parametric_mean(self.X))

        # sample mu, sigma
        mu, sigma = self.gp(X)

        # sample value of y
        y_hat = mu + self.parametric_mean(X)
        pyro.sample('y', dist.Normal(y_hat, sigma))

        # compute expected improvement
        y_min = self.y.min()
        delta = y_min - y_hat
        EI = delta.clamp_min(0.0) + sigma * normal_phi(
            delta / sigma) - delta.abs() * normal_Phi(delta / sigma)

        pyro.sample('EI', dist.Delta(-EI))

        # return the mean, in case we want to ignore the GP noise for some reason later
        return y_hat

def train(model, optimizer, loss, num_steps=1000):
    """ Trains the semi-parametric model. """

    # Autoguide
    guide = autoguide.AutoMultivariateNormal(model.model)

    svi = SVI(model.model, guide, optimizer, loss)

    # do gradient steps
    losses = []
    for _ in range(num_steps):
        losses.append(svi.step())

    return guide, losses

def transf_values(values, constr, dims, inv_mode=False):
    """ Transforming (un)constrained variables to (un)constrained domain """

    x_tmp = ()
    for i in range(dims):
        if inv_mode:
            x_tmp += (transform_to(constr[i]).inv(values[:, i]),)
        else:
            x_tmp += (transform_to(constr[i])(values[:, i]),)

    x = torch.stack(x_tmp, dim=1)

    return x

def find_a_candidate(model_predict, return_site, optimizer, x_init, constr, num_steps=1000, num_samples=5, lr=0.1):
    """ Finds new candidate """

    x_dims = x_init.shape[-1]

    x_uncon_init = transf_values(x_init, constr, x_dims, inv_mode=True)
    x_uncon = x_uncon_init.clone().detach().requires_grad_(True)

    minimizer = optimizer([x_uncon], lr=lr)

    def closure():
        minimizer.zero_grad()
        x = transf_values(x_uncon, constr, x_dims)

        y = model_predict(x)[return_site].mean(0)

        autograd.backward(x_uncon, autograd.grad(y, x_uncon))
        return y

    for _ in range(num_steps):
        minimizer.step(closure)

    x = transf_values(x_uncon, constr, x_dims)

    return x.detach()

def next_x(model_predict, return_site, optimizer, target, num_steps=1000, num_candidates=5, num_samples=5, lr=0.1):
    """ Finds the next best candidate on the acquisition function surface """

    candidates = []
    values = []

    # starts with the best candidate so far
    min_y = model_predict(model_predict.model.X)[return_site].mean(0)

    x_init = (model_predict.model.X[torch.min(min_y, dim=0)[1].item()]).unsqueeze(dim=0)

    for _ in range(num_candidates):
        x_can = find_a_candidate(model_predict, return_site,
                                 optimizer, x_init,
                                 target.contstraints,
                                 num_steps=num_steps,
                                 num_samples=num_samples,
                                 lr=lr)

        y = model_predict(x_can)[return_site].mean(0)

        candidates.append(x_can)
        values.append(y.detach())

        # a new random attempt initial point
        for _ in range(100):

            x_list = []
            for i in range(x_can.shape[-1]):
                x_list.append(x_can[:, i].new_empty(1).uniform_(
                    target.ranges[i][0], target.ranges[i][1]))

            x_init = torch.stack(x_list, dim=1)

            y_init = model_predict(x_init)[return_site].mean(0)

            # since we are minimising, we check
            if y_init < 0.0:
                break

    if num_candidates > 1:
        argmin = torch.min(torch.cat(values), dim=0)[1].item()

        return candidates[argmin]
    else:
        return candidates[0]

def update_posterior(model, optimizer, loss, obj_function, x_new, num_steps=1000):
    """
    Updates
        model:
        optimizer:
        loss:
        obj_function: objectivefunction
        x_new: a new value of x
        num_steps:

    """

    # evaluate f at new point
    bh_y = obj_function.eval(x_new)

    # incorporate new evaluation
    X = torch.cat([model.X, x_new])
    y = torch.cat([model.y, bh_y])

    model.X = X
    model.y = y

    # TODO: Check if this necessary
    model.gp.set_data(X, y)

    guide, losses = train(model, optimizer, loss, num_steps=num_steps)

    return guide, losses

def step(model,
         guide,
         optimizer,
         loss,
         target,
         acqf_optimizer,
         opti_num_steps=1000,
         acqf_opti_num_steps=1000,
         acqf_opti_lr=0.1,
         num_samples=1,
         num_candidates=5,
         return_site='EI'):
    """
    Performs a bayesian optimisation step. This includes generating a predictive model,
        which is used to find a new value of x at which the target function needs to be
        evaluated, and updating the model after the new x is obtained.

        model:
        guide:
        optimizer:
        loss:
        target:
        acqf_optimizer:
        opti_num_steps:
        acqf_opti_num_steps:
        num_samples:
        num_candidates:
        return_site:
    """

    if guide is not None:

        # TODO: copy.copy is a hack around predictive model being linked with a model
        # Constructs predictive distribution
        predict = pyro.infer.Predictive(copy.copy(model), guide=guide,
                                        num_samples=num_samples, return_sites=('y', return_site))

        # Finds the next candidate
        x_new = next_x(predict, return_site, acqf_optimizer, target,
                        num_steps=acqf_opti_num_steps, num_candidates=num_candidates,
                        num_samples=num_samples, lr=acqf_opti_lr)

        # Updates posterior
        new_guide, losses = update_posterior(model, optimizer, loss, target,
                                            x_new, num_steps=opti_num_steps)
    else:
        predict = None

        # trains the model
        new_guide, losses = train(model, optimizer, loss, num_steps=opti_num_steps)

    return new_guide, predict, losses