#-*- coding:utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt

def evaluate_gradient(loss_function, data, params):
    """计算loss_function对params的梯度，根据data的大小有不同策略。
    """
    pass

#batch gradient descent
#目标函数是凸函数，保证收敛到全局最优点
#目标函数是非凸函数，保证收敛到局部最优点
#重复计算，每次都计算全部样本的梯度，在迭代轮次之间，可能会重复计算相同的梯度
#无法在线更新
#for i in range(nb_epochs):
#  params_grad = evaluate_gradient(loss_function, data, params)
#  params = params - learning_rate * params_grad

#stochastic gradient descent
#每次选择1个样本，计算梯度。
#导致每次的梯度变化比较大，梯度的方差比较大。loss下降的不是很平滑。可以指数降低lr
#有机会跳出鞍点
#计算简单，可以支持在线更新
#for i in range(nb_epochs):
#  np.random.shuffle(data)
#  for example in data:
#    params_grad = evaluate_gradient(loss_function, example, params)
#    params = params - learning_rate * params_grad

#mini-batch gradient descent
#综合利用了batch和stochastic的优点
#降低stochastic的梯度方差，loss下降更平滑。
#能利用矩阵计算达到降低计算复杂度的问题
#for i in range(nb_epochs):
#  np.random.shuffle(data)
#  for batch in get_batches(data, batch_size=50):
#    params_grad = evaluate_gradient(loss_function, batch, params)
#    params = params - learning_rate * params_grad

#梯度下降法的缺点：
#1.学习速率很难确定，需要交叉验证(CV)
#2.学习速率调度问题。某些场景下，需要对学习速率进行衰减。预定义衰减策略没有利用到数据的信息
#3.所有参数的学习速率是一致的。特征稀疏程度不一样，需要不同的学习速率，对于较少出现的特征，需要增加学习速率，对于较多的特征，需要降低学习速率
#跳出鞍点

#momentum
#冲量算法，物理意义表示力F作用一段时间t后的效果：F*t = Mv
#v_now = rho * v_prev + lr * d_now(params)
#params = params - v_now
#v_prev = v_now
#收敛速度更快，loss的变化的方差更小

#nesterov accelerated gradient
#v_now = rho * v_prev + lr * d_now(params - rho * v_prev)
#params = params - v_now

#上述方法解决的是，学习方向的问题，可以动态调整学习速率
#下面介绍解决动态调整学习速率的问题

#adagrad jeff dean
#g_t_i = d(theta_t-1_i)
#theta_t_i = theta_t-1_i - lr / (math.sqrt(G_t-1_ii) + epsilon) * g_t_i
#G_t_ii = sigma_k=0^t g_k_i * g_k_i
#G是对角矩阵，每个对角线上的元素是过去梯度的平方和
#为什么要除以根号G，目前暂时不明朗。试验结果不加根号的话，效果反而很差
#学习速率越来越小，最后出现参数不更新的情况

#RMSprop, hinton
#对G矩阵做指数加权平均，G_t = gamma * G_t-1 + (1 - gamma) * g_t * g_t

#Adam
#将冲量法和自适应学习速率法结合
#方向g_t = beta1 * g_t-1 + (1 - beta1) * d_now(theta)
#学习速率衰减银子G_t = beta2 * G_t-1 + (1 - beta2) * d_now(theta) * d_now(theta)
#g_t = g_t / (1 - beta1 ** t) 偏差修正，原因：因为如果beta取值较大的情况下，g和G会取趋向于0的值，除以1-beta**t是为了使得g_t是梯度的无偏估计，对G同理
#G_t = G_t / (1 - beta2 ** t)
#theta_t = theta_t-1 - lr / (math.sqrt(G_t-1_ii) + epsilon) * g_t
#推荐值：lr=0.002 beta1 = 0.9 beta2=0.999, eps=1e-8

#AdaMax
#将学习速率衰减的2次方换成p次方，p越大，数值会越不稳定,why?
#p->无穷大时，数值稳定
#G_t = beta2 ** p * G_t-1 + (1 - beta2 ** p) * d_now(theta) ** p
#p->无穷大时,上式等于下式
#G_t = max(beta2 * G_t-1, math.abs(d_now(theta)))
#theta_t = theta_t-1 - lr / G_t * g_t
#推荐值：lr=0.002 beta1 = 0.9 beta2=0.999

#

def SGD(X, y, params, learning_rate=0.1):
    params_gradient = eval_gradient(X, y, params)
    params -= lr * params_gradient
    return params

def momentum(X, y, params, velocity=0.0, gamma=0.9, learning_rate=0.1):
    params_gradient = eval_gradient(X, y, params)
    velocity = gamma * velocity + learning_rate * params_gradient
    params -= velocity
    return params, velocity

def nag(X, y, params, velocity=0.0, gamma=0.9, learning_rate=0.1):
    params_gradient = eval_gradient(X, y, params - gamma * velocity)
    velocity = gamma * velocity + learning_rate * params_gradient
    params -= velocity
    return params, velocity

def adagrad(X, y, params, G=0, eps=1e-8, learning_rate=0.1):
    params_gradient = eval_gradient(X, y, params)
    G += params_gradient ** 2
    params -= learning_rate / np.sqrt(G + eps) * params_gradient
    return params, G

def RMSprop(X, y, params, G=0, gamma=0.9, eps=1e-8, learning_rate=0.1):
    params_gradient = eval_gradient(X, y, params)
    G = gamma * G + (1 - gamma) * params_gradient ** 2
    params -= learning_Rate / np.sqrt(G + eps) * params_gradient
    return params, G

def adam(X, y, params, correction=False, iter_num=1, velocity=0, beta1=0.9, G=0, beta2=0.99, eps=1e-8, learning_rate=0.1):
    params_gradient = eval_gradient(X, y, params)
    velocity = beta1 * velocity + (1 - beta1) * params_gradient
    G = beta2 * G + (1 - beta2) * params_gradient ** 2
    if correction:
        velocity /= (1 - beta1 ** iter_num)
        G /= (1 - beta2 ** iter_num)
    params -= learning_rate / (np.sqrt(G) + eps) * velocity
    return params, velocity, G

import numpy as np
import matplotlib.pyplot as plt

# 目标函数:y=x^2
def func(x):
    return np.square(x)

# 目标函数一阶导数:dy/dx=2*x
def dfunc(x):
    return 2 * x

def GD_momentum(x_start, df, epochs, lr, momentum):
    """
    带有冲量的梯度下降法。
    :param x_start: x的起始点
    :param df: 目标函数的一阶导函数
    :param epochs: 迭代周期
    :param lr: 学习率
    :param momentum: 冲量
    :return: x在每次迭代后的位置（包括起始点），长度为epochs+1
    """
    xs = np.zeros(epochs+1)
    x = x_start
    xs[0] = x
    v = 0
    for i in range(epochs):
        dx = df(x)
        # v表示x要改变的幅度
        v = - dx * lr + momentum * v
        x += v
        xs[i+1] = x
    return xs

def demo2_GD_momentum():
    line_x = np.linspace(-5, 5, 100)
    line_y = func(line_x)
    plt.figure('Gradient Desent: Learning Rate, Momentum', figsize=(20, 20))

    x_start = -5
    epochs = 6

    lr = [0.01, 0.1, 0.6, 0.9]
    momentum = [0.0, 0.1, 0.5, 0.9]

    color = ['k', 'r', 'g', 'y']

    row = len(lr)
    col = len(momentum)
    size = np.ones(epochs+1) * 10
    size[-1] = 70
    for i in range(row):
        for j in range(col):
            x = GD_momentum(x_start, dfunc, epochs, lr=lr[i], momentum=momentum[j])
            plt.subplot(row, col, i * col + j + 1)
            plt.plot(line_x, line_y, c='b')
            plt.plot(x, func(x), c=color[i], label='lr={}, mo={}'.format(lr[i], momentum[j]))
            plt.scatter(x, func(x), c=color[i], s=size)
            plt.legend(loc=0)
    plt.show()

if __name__ == "__main__":
    demo2_GD_momentum()


import sys

tables = ["customer", "nation", "lineitem", "orders", "part", "partsupp", "region", "supplier"]

scale = 100
partition = '512M'
num_runs = 10
schema = f"tpchs{scale}"
file_format = "parquet"
ddl_query_file = f"row-ddl-{file_format}.sql"
data_base_dir = f"/mnt/DeepOLA/resources/tpc-h/data/scale={scale}/partition={partition}/{file_format}"
hdfs_dir_path = f"/tpch-data/scale={scale}"

def create_hdfs_dir():
    command = f"hdfs dfs -mkdir -p {hdfs_dir_path}"
    return [command]

def load_hdfs_data():
    commands = []
    for table in tables:
        command = f"hdfs dfs -put {data_base_dir}/{table}.{file_format}* /tpch-data/scale={scale}"
        commands.append(command)
    return commands

def create_hive_schema():
    commands = []
    commands.append(f"hive -e 'DROP DATABASE IF EXISTS {schema} CASCADE'")
    commands.append(f"hive -e 'CREATE SCHEMA {schema}'")
    return commands

def create_hive_tables():
    query_file = open(ddl_query_file,"r").read()
    command = f'hive -e "{query_file}"'
    return [command]

def load_hive_data():
    commands = []
    for table in tables:
        command = f"hive -e 'LOAD DATA INPATH \"/tpch-data/scale={scale}/{table}.{file_format}*\" INTO TABLE {schema}.{table}'"
        commands.append(command)
    return commands

def set_trace():
    return ["set -o xtrace"]

def set_schema():
    return [f"schema={schema}"]

def run_presto_queries():
    command = f"./presto-script.sh {scale} {num_runs} > logs/scale={scale}.log 2>&1"
    return [command]

if __name__ == "__main__":
    commands = []
    commands.extend(set_trace())
    commands.extend(set_schema())
    commands.extend(create_hdfs_dir())
    commands.extend(load_hdfs_data())
    commands.extend(create_hive_schema())
    commands.extend(create_hive_tables())
    commands.extend(load_hive_data())
    commands.extend(run_presto_queries())
    print("\n".join(commands))


# import the necessary modules
from tkinter import Tk, Button, Frame, END, Label, Text, Scrollbar
from tkinter import simpledialog
from tkinter.filedialog import askopenfilename, asksaveasfilename
row = 0

#open a file
def openfile():
    global currentFile
    inputfile = askopenfilename(filetypes=[("text files", "*.txt"), ("all files", "*.*")])
    if inputfile is not None:
        textbox.delete("1.0", END)
        with open(inputfile, mode="r", encoding="utf-8") as file:
            currentFile = file
            textbox.insert(END, file.read())
            window.title("minitext - " + file.name)

# open a file from a pinned button
def open2(inputfile):
    with open(inputfile, mode="r", encoding="utf-8") as file:
        textbox.delete("1.0", END)
        currentFile = file
        textbox.insert(END, file.read())
        window.title("minitext - " + file.name)

#save the text to a file
def savefileas():
    types = [("text file (.txt)", "*.txt"), ("all files", "*.*")]
    inputfile = asksaveasfilename(filetypes=types, defaultextension=types)
    with open(inputfile, mode="w", encoding="utf-8") as file:
        text = textbox.get("1.0", END)
        file.write(text)

#save the file
def savefile():
    if currentFile is not None:
        with open(currentFile.name, mode="w") as file:
            text = textbox.get("1.0", END)
            file.write(text)

#start a new file
def newfile():
    global currentFile
    currentFile = None
    textbox.delete("1.0", END)
    window.title("minitext - untitled")

#add a button to the pinned buttons column
def addbutton():
    global row
    types = [("text file (.txt)", "*.txt"), ("all files", "*.*")]
    inputfile = askopenfilename(filetypes=types)
    if inputfile != None:
        name = simpledialog.askstring("display name", "type a display name for this file")
        if name != "" or None:
            b = Button(pin, text=name, border=0, command=lambda inputfile=inputfile: open2(inputfile))
            b.grid(row=row, column=0, sticky="ew", padx=5, pady=2.5)
            row += 1

#initialize the window
window = Tk()
window.resizable(width=False, height=False)
window.title("minitext")

#add the basic components (text, sidebar, scrollbar)
textbox = Text(window)
sidebar = Frame(window, bg="#d4d4d4", bd=2)
pin = Frame(window, bg="#e4e4e4", bd=2)
vertical = Scrollbar(window, orient="vertical", command=textbox.yview)

#add buttons to the sidebar
l = Label(sidebar, text="-file-", bg="#d2d2d2")
openbutton = Button(sidebar, text="open", command=openfile, border=0)
saveasbutton = Button(sidebar, text="save as...", command=savefileas, border=0)
savebutton = Button(sidebar, text="save", command=savefile, border=0)
newbutton = Button(sidebar, text="new", command=newfile, border=0)
add = Button(sidebar, text="pin file...", command=addbutton, border=0)

#arrange the components
l.grid(row=0, column=0, sticky="ew", padx=5)
openbutton.grid(row=2, column=0, sticky="ew", padx=5)
saveasbutton.grid(row=3, column=0, sticky="ew", padx=5, pady=5)
savebutton.grid(row=4, column=0, sticky="ew", padx=5)
newbutton.grid(row=1, column=0, sticky="ew", padx=5, pady=5)
add.grid(row=5, column=0, sticky="ew", padx=5, pady=5)
sidebar.grid(row=0, column=0, sticky="ns")
textbox.grid(row=0, column=2, sticky="nsew")
pin.grid(row=0, column=1, sticky="ns")
vertical.grid(row=0, column=3, sticky="nsew")
window.mainloop()


from __future__ import print_function

from bisect import bisect_left
from itertools import chain, islice
import operator

try:
    from collections.abc import MutableSet
except ImportError:
    from collections import MutableSet

try:
    from .typeutils import make_sentinel
    _MISSING = make_sentinel(var_name='_MISSING')
except ImportError:
    _MISSING = object()

__all__ = ['IndexedSet', 'complement']

_COMPACTION_FACTOR = 8

# TODO: inherit from set()
# TODO: .discard_many(), .remove_many()
# TODO: raise exception on non-set params?
# TODO: technically reverse operators should probably reverse the
# order of the 'other' inputs and put self last (to try and maintain
# insertion order)

class IndexedSet(MutableSet):
    """``IndexedSet`` is a :class:`collections.MutableSet` that maintains
    insertion order and uniqueness of inserted elements. It's a hybrid
    type, mostly like an OrderedSet, but also :class:`list`-like, in
    that it supports indexing and slicing.

    Args:
        other (iterable): An optional iterable used to initialize the set.

    >>> x = IndexedSet(list(range(4)) + list(range(8)))
    >>> x
    IndexedSet([0, 1, 2, 3, 4, 5, 6, 7])
    >>> x - set(range(2))
    IndexedSet([2, 3, 4, 5, 6, 7])
    >>> x[-1]
    7
    >>> fcr = IndexedSet('freecreditreport.com')
    >>> ''.join(fcr[:fcr.index('.')])
    'frecditpo'

    Standard set operators and interoperation with :class:`set` are
    all supported:

    >>> fcr & set('cash4gold.com')
    IndexedSet(['c', 'd', 'o', '.', 'm'])

    As you can see, the ``IndexedSet`` is almost like a ``UniqueList``,
    retaining only one copy of a given value, in the order it was
    first added. For the curious, the reason why IndexedSet does not
    support setting items based on index (i.e, ``__setitem__()``),
    consider the following dilemma::

      my_indexed_set = [A, B, C, D]
      my_indexed_set[2] = A

    At this point, a set requires only one *A*, but a :class:`list` would
    overwrite *C*. Overwriting *C* would change the length of the list,
    meaning that ``my_indexed_set[2]`` would not be *A*, as expected with a
    list, but rather *D*. So, no ``__setitem__()``.

    Otherwise, the API strives to be as complete a union of the
    :class:`list` and :class:`set` APIs as possible.
    """
    def __init__(self, other=None):
        self.item_index_map = dict()
        self.item_list = []
        self.dead_indices = []
        self._compactions = 0
        self._c_max_size = 0
        if other:
            self.update(other)

    # internal functions
    @property
    def _dead_index_count(self):
        return len(self.item_list) - len(self.item_index_map)

    def _compact(self):
        if not self.dead_indices:
            return
        self._compactions += 1
        dead_index_count = self._dead_index_count
        items, index_map = self.item_list, self.item_index_map
        self._c_max_size = max(self._c_max_size, len(items))
        for i, item in enumerate(self):
            items[i] = item
            index_map[item] = i
        del items[-dead_index_count:]
        del self.dead_indices[:]

    def _cull(self):
        ded = self.dead_indices
        if not ded:
            return
        items, ii_map = self.item_list, self.item_index_map
        if not ii_map:
            del items[:]
            del ded[:]
        elif len(ded) > 384:
            self._compact()
        elif self._dead_index_count > (len(items) / _COMPACTION_FACTOR):
            self._compact()
        elif items[-1] is _MISSING:  # get rid of dead right hand side
            num_dead = 1
            while items[-(num_dead + 1)] is _MISSING:
                num_dead += 1
            if ded and ded[-1][1] == len(items):
                del ded[-1]
            del items[-num_dead:]

    def _get_real_index(self, index):
        if index < 0:
            index += len(self)
        if not self.dead_indices:
            return index
        real_index = index
        for d_start, d_stop in self.dead_indices:
            if real_index < d_start:
                break
            real_index += d_stop - d_start
        return real_index

    def _get_apparent_index(self, index):
        if index < 0:
            index += len(self)
        if not self.dead_indices:
            return index
        apparent_index = index
        for d_start, d_stop in self.dead_indices:
            if index < d_start:
                break
            apparent_index -= d_stop - d_start
        return apparent_index

    def _add_dead(self, start, stop=None):
        # TODO: does not handle when the new interval subsumes
        # multiple existing intervals
        dints = self.dead_indices
        if stop is None:
            stop = start + 1
        cand_int = [start, stop]
        if not dints:
            dints.append(cand_int)
            return
        int_idx = bisect_left(dints, cand_int)
        dint = dints[int_idx - 1]
        d_start, d_stop = dint
        if start <= d_start <= stop:
            dint[0] = start
        elif start <= d_stop <= stop:
            dint[1] = stop
        else:
            dints.insert(int_idx, cand_int)
        return

    # common operations (shared by set and list)
    def __len__(self):
        return len(self.item_index_map)

    def __contains__(self, item):
        return item in self.item_index_map

    def __iter__(self):
        return (item for item in self.item_list if item is not _MISSING)

    def __reversed__(self):
        item_list = self.item_list
        return (item for item in reversed(item_list) if item is not _MISSING)

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, list(self))

    def __eq__(self, other):
        if isinstance(other, IndexedSet):
            return len(self) == len(other) and list(self) == list(other)
        return set(self) == set(other)

    @classmethod
    def from_iterable(cls, it):
        "from_iterable(it) -> create a set from an iterable"
        return cls(it)

    # set operations
    def add(self, item):
        "add(item) -> add item to the set"
        if item not in self.item_index_map:
            self.item_index_map[item] = len(self.item_list)
            self.item_list.append(item)

    def remove(self, item):
        "remove(item) -> remove item from the set, raises if not present"
        try:
            didx = self.item_index_map.pop(item)
        except KeyError:
            raise KeyError(item)
        self.item_list[didx] = _MISSING
        self._add_dead(didx)
        self._cull()

    def discard(self, item):
        "discard(item) -> discard item from the set (does not raise)"
        try:
            self.remove(item)
        except KeyError:
            pass

    def clear(self):
        "clear() -> empty the set"
        del self.item_list[:]
        del self.dead_indices[:]
        self.item_index_map.clear()

    def isdisjoint(self, other):
        "isdisjoint(other) -> return True if no overlap with other"
        iim = self.item_index_map
        for k in other:
            if k in iim:
                return False
        return True

    def issubset(self, other):
        "issubset(other) -> return True if other contains this set"
        if len(other) < len(self):
            return False
        for k in self.item_index_map:
            if k not in other:
                return False
        return True

    def issuperset(self, other):
        "issuperset(other) -> return True if set contains other"
        if len(other) > len(self):
            return False
        iim = self.item_index_map
        for k in other:
            if k not in iim:
                return False
        return True

    def union(self, *others):
        "union(*others) -> return a new set containing this set and others"
        return self.from_iterable(chain(self, *others))

    def iter_intersection(self, *others):
        "iter_intersection(*others) -> iterate over elements also in others"
        for k in self:
            for other in others:
                if k not in other:
                    break
            else:
                yield k
        return

    def intersection(self, *others):
        "intersection(*others) -> get a set with overlap of this and others"
        if len(others) == 1:
            other = others[0]
            return self.from_iterable(k for k in self if k in other)
        return self.from_iterable(self.iter_intersection(*others))

    def iter_difference(self, *others):
        "iter_difference(*others) -> iterate over elements not in others"
        for k in self:
            for other in others:
                if k in other:
                    break
            else:
                yield k
        return

    def difference(self, *others):
        "difference(*others) -> get a new set with elements not in others"
        if len(others) == 1:
            other = others[0]
            return self.from_iterable(k for k in self if k not in other)
        return self.from_iterable(self.iter_difference(*others))

    def symmetric_difference(self, *others):
        "symmetric_difference(*others) -> XOR set of this and others"
        ret = self.union(*others)
        return ret.difference(self.intersection(*others))

    __or__  = __ror__  = union
    __and__ = __rand__ = intersection
    __sub__ = difference
    __xor__ = __rxor__ = symmetric_difference

    def __rsub__(self, other):
        vals = [x for x in other if x not in self]
        return type(other)(vals)

    # in-place set operations
    def update(self, *others):
        "update(*others) -> add values from one or more iterables"
        if not others:
            return  # raise?
        elif len(others) == 1:
            other = others[0]
        else:
            other = chain(others)
        for o in other:
            self.add(o)

    def intersection_update(self, *others):
        "intersection_update(*others) -> discard self.difference(*others)"
        for val in self.difference(*others):
            self.discard(val)

    def difference_update(self, *others):
        "difference_update(*others) -> discard self.intersection(*others)"
        if self in others:
            self.clear()
        for val in self.intersection(*others):
            self.discard(val)

    def symmetric_difference_update(self, other):  # note singular 'other'
        "symmetric_difference_update(other) -> in-place XOR with other"
        if self is other:
            self.clear()
        for val in other:
            if val in self:
                self.discard(val)
            else:
                self.add(val)

    def __ior__(self, *others):
        self.update(*others)
        return self

    def __iand__(self, *others):
        self.intersection_update(*others)
        return self

    def __isub__(self, *others):
        self.difference_update(*others)
        return self

    def __ixor__(self, *others):
        self.symmetric_difference_update(*others)
        return self

    def iter_slice(self, start, stop, step=None):
        "iterate over a slice of the set"
        iterable = self
        if start is not None:
            start = self._get_real_index(start)
        if stop is not None:
            stop = self._get_real_index(stop)
        if step is not None and step < 0:
            step = -step
            iterable = reversed(self)
        return islice(iterable, start, stop, step)

    # list operations
    def __getitem__(self, index):
        try:
            start, stop, step = index.start, index.stop, index.step
        except AttributeError:
            index = operator.index(index)
        else:
            iter_slice = self.iter_slice(start, stop, step)
            return self.from_iterable(iter_slice)
        if index < 0:
            index += len(self)
        real_index = self._get_real_index(index)
        try:
            ret = self.item_list[real_index]
        except IndexError:
            raise IndexError('IndexedSet index out of range')
        return ret

    def pop(self, index=None):
        "pop(index) -> remove the item at a given index (-1 by default)"
        item_index_map = self.item_index_map
        len_self = len(item_index_map)
        if index is None or index == -1 or index == len_self - 1:
            ret = self.item_list.pop()
            del item_index_map[ret]
        else:
            real_index = self._get_real_index(index)
            ret = self.item_list[real_index]
            self.item_list[real_index] = _MISSING
            del item_index_map[ret]
            self._add_dead(real_index)
        self._cull()
        return ret

    def count(self, val):
        "count(val) -> count number of instances of value (0 or 1)"
        if val in self.item_index_map:
            return 1
        return 0

    def reverse(self):
        "reverse() -> reverse the contents of the set in-place"
        reversed_list = list(reversed(self))
        self.item_list[:] = reversed_list
        for i, item in enumerate(self.item_list):
            self.item_index_map[item] = i
        del self.dead_indices[:]

    def sort(self, **kwargs):
        "sort() -> sort the contents of the set in-place"
        sorted_list = sorted(self, **kwargs)
        if sorted_list == self.item_list:
            return
        self.item_list[:] = sorted_list
        for i, item in enumerate(self.item_list):
            self.item_index_map[item] = i
        del self.dead_indices[:]

    def index(self, val):
        "index(val) -> get the index of a value, raises if not present"
        try:
            return self._get_apparent_index(self.item_index_map[val])
        except KeyError:
            cn = self.__class__.__name__
            raise ValueError('%r is not in %s' % (val, cn))

def complement(wrapped):
    """Given a :class:`set`, convert it to a **complement set**.

    Whereas a :class:`set` keeps track of what it contains, a
    `complement set
    <https://en.wikipedia.org/wiki/Complement_(set_theory)>`_ keeps
    track of what it does *not* contain. For example, look what
    happens when we intersect a normal set with a complement set::

    >>> list(set(range(5)) & complement(set([2, 3])))
    [0, 1, 4]

    We get the everything in the left that wasn't in the right,
    because intersecting with a complement is the same as subtracting
    a normal set.

    Args:
        wrapped (set): A set or any other iterable which should be
           turned into a complement set.

    All set methods and operators are supported by complement sets,
    between other :func:`complement`-wrapped sets and/or regular
    :class:`set` objects.

    Because a complement set only tracks what elements are *not* in
    the set, functionality based on set contents is unavailable:
    :func:`len`, :func:`iter` (and for loops), and ``.pop()``. But a
    complement set can always be turned back into a regular set by
    complementing it again:

    >>> s = set(range(5))
    >>> complement(complement(s)) == s
    True

    .. note::

       An empty complement set corresponds to the concept of a
       `universal set <https://en.wikipedia.org/wiki/Universal_set>`_
       from mathematics.

    Complement sets by example
    ^^^^^^^^^^^^^^^^^^^^^^^^^^

    Many uses of sets can be expressed more simply by using a
    complement. Rather than trying to work out in your head the proper
    way to invert an expression, you can just throw a complement on
    the set. Consider this example of a name filter::

        >>> class NamesFilter(object):
        ...    def __init__(self, allowed):
        ...        self._allowed = allowed
        ...
        ...    def filter(self, names):
        ...        return [name for name in names if name in self._allowed]
        >>> NamesFilter(set(['alice', 'bob'])).filter(['alice', 'bob', 'carol'])
        ['alice', 'bob']

    What if we want to just express "let all the names through"?

    We could try to enumerate all of the expected names::

       ``NamesFilter({'alice', 'bob', 'carol'})``

    But this is very brittle -- what if at some point over this
    object is changed to filter ``['alice', 'bob', 'carol', 'dan']``?

    Even worse, what about the poor programmer who next works
    on this piece of code?  They cannot tell whether the purpose
    of the large allowed set was "allow everything", or if 'dan'
    was excluded for some subtle reason.

    A complement set lets the programmer intention be expressed
    succinctly and directly::

       NamesFilter(complement(set()))

    Not only is this code short and robust, it is easy to understand
    the intention.

    """
    if type(wrapped) is _ComplementSet:
        return wrapped.complemented()
    if type(wrapped) is frozenset:
        return _ComplementSet(excluded=wrapped)
    return _ComplementSet(excluded=set(wrapped))

def _norm_args_typeerror(other):
    '''normalize args and raise type-error if there is a problem'''
    if type(other) in (set, frozenset):
        inc, exc = other, None
    elif type(other) is _ComplementSet:
        inc, exc = other._included, other._excluded
    else:
        raise TypeError('argument must be another set or complement(set)')
    return inc, exc

def _norm_args_notimplemented(other):
    '''normalize args and return NotImplemented (for overloaded operators)'''
    if type(other) in (set, frozenset):
        inc, exc = other, None
    elif type(other) is _ComplementSet:
        inc, exc = other._included, other._excluded
    else:
        return NotImplemented, None
    return inc, exc

class _ComplementSet(object):
    """
    helper class for complement() that implements the set methods
    """
    __slots__ = ('_included', '_excluded')

    def __init__(self, included=None, excluded=None):
        if included is None:
            assert type(excluded) in (set, frozenset)
        elif excluded is None:
            assert type(included) in (set, frozenset)
        else:
            raise ValueError('one of included or excluded must be a set')
        self._included, self._excluded = included, excluded

    def __repr__(self):
        if self._included is None:
            return 'complement({0})'.format(repr(self._excluded))
        return 'complement(complement({0}))'.format(repr(self._included))

    def complemented(self):
        '''return a complement of the current set'''
        if type(self._included) is frozenset or type(self._excluded) is frozenset:
            return _ComplementSet(included=self._excluded, excluded=self._included)
        return _ComplementSet(
            included=None if self._excluded is None else set(self._excluded),
            excluded=None if self._included is None else set(self._included))

    __invert__ = complemented

    def complement(self):
        '''convert the current set to its complement in-place'''
        self._included, self._excluded = self._excluded, self._included

    def __contains__(self, item):
        if self._included is None:
            return not item in self._excluded
        return item in self._included

    def add(self, item):
        if self._included is None:
            if item in self._excluded:
                self._excluded.remove(item)
        else:
            self._included.add(item)

    def remove(self, item):
        if self._included is None:
            self._excluded.add(item)
        else:
            self._included.remove(item)

    def pop(self):
        if self._included is None:
            raise NotImplementedError  # self.missing.add(random.choice(gc.objects()))
        return self._included.pop()

    def intersection(self, other):
        try:
            return self & other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __and__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return _ComplementSet(included=inc - self._excluded)
            else:  # - -
                return _ComplementSet(excluded=self._excluded.union(other._excluded))
        else:
            if inc is None:  # + -
                return _ComplementSet(included=exc - self._included)
            else:  # + +
                return _ComplementSet(included=self._included.intersection(inc))

    __rand__ = __and__

    def __iand__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                self._excluded = inc - self._excluded  # TODO: do this in place?
            else:  # - -
                self._excluded |= exc
        else:
            if inc is None:  # + -
                self._included -= exc
                self._included, self._excluded = None, self._included
            else:  # + +
                self._included &= inc
        return self

    def union(self, other):
        try:
            return self | other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __or__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return _ComplementSet(excluded=self._excluded - inc)
            else:  # - -
                return _ComplementSet(excluded=self._excluded.intersection(exc))
        else:
            if inc is None:  # + -
                return _ComplementSet(excluded=exc - self._included)
            else:  # + +
                return _ComplementSet(included=self._included.union(inc))

    __ror__ = __or__

    def __ior__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                self._excluded -= inc
            else:  # - -
                self._excluded &= exc
        else:
            if inc is None:  # + -
                self._included, self._excluded = None, exc - self._included   # TODO: do this in place?
            else:  # + +
                self._included |= inc
        return self

    def update(self, items):
        if type(items) in (set, frozenset):
            inc, exc = items, None
        elif type(items) is _ComplementSet:
            inc, exc = items._included, items._excluded
        else:
            inc, exc = frozenset(items), None
        if self._included is None:
            if exc is None:  # - +
                self._excluded &= inc
            else:  # - -
                self._excluded.discard(exc)
        else:
            if inc is None:  # + -
                self._included &= exc
                self._included, self._excluded = None, self._excluded
            else:  # + +
                self._included.update(inc)

    def discard(self, items):
        if type(items) in (set, frozenset):
            inc, exc = items, None
        elif type(items) is _ComplementSet:
            inc, exc = items._included, items._excluded
        else:
            inc, exc = frozenset(items), None
        if self._included is None:
            if exc is None:  # - +
                self._excluded.update(inc)
            else:  # - -
                self._included, self._excluded = exc - self._excluded, None
        else:
            if inc is None:  # + -
                self._included &= exc
            else:  # + +
                self._included.discard(inc)

    def symmetric_difference(self, other):
        try:
            return self ^ other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __xor__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return _ComplementSet(excluded=self._excluded - inc)
            else:  # - -
                return _ComplementSet(included=self._excluded.symmetric_difference(exc))
        else:
            if inc is None:  # + -
                return _ComplementSet(excluded=exc - self._included)
            else:  # + +
                return _ComplementSet(included=self._included.symmetric_difference(inc))

    __rxor__ = __xor__

    def symmetric_difference_update(self, other):
        inc, exc = _norm_args_typeerror(other)
        if self._included is None:
            if exc is None:  # - +
                self._excluded |= inc
            else:  # - -
                self._excluded.symmetric_difference_update(exc)
                self._included, self._excluded = self._excluded, None
        else:
            if inc is None:  # + -
                self._included |= exc
                self._included, self._excluded = None, self._included
            else:  # + +
                self._included.symmetric_difference_update(inc)

    def isdisjoint(self, other):
        inc, exc = _norm_args_typeerror(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return inc.issubset(self._excluded)
            else:  # - -
                return False
        else:
            if inc is None:  # + -
                return self._included.issubset(exc)
            else:  # + +
                return self._included.isdisjoint(inc)

    def issubset(self, other):
        '''everything missing from other is also missing from self'''
        try:
            return self <= other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __le__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return False
            else:  # - -
                return self._excluded.issupserset(exc)
        else:
            if inc is None:  # + -
                return self._included.isdisjoint(exc)
            else:  # + +
                return self._included.issubset(inc)

    def __lt__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return False
            else:  # - -
                return self._excluded > exc
        else:
            if inc is None:  # + -
                return self._included.isdisjoint(exc)
            else:  # + +
                return self._included < inc

    def issuperset(self, other):
        '''everything missing from self is also missing from super'''
        try:
            return self >= other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __ge__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return not self._excluded.intersection(inc)
            else:  # - -
                return self._excluded.issubset(exc)
        else:
            if inc is None:  # + -
                return False
            else:  # + +
                return self._included.issupserset(inc)

    def __gt__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return not self._excluded.intersection(inc)
            else:  # - -
                return self._excluded < exc
        else:
            if inc is None:  # + -
                return False
            else:  # + +
                return self._included > inc

    def difference(self, other):
        try:
            return self - other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __sub__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return _ComplementSet(excluded=self._excluded | inc)
            else:  # - -
                return _ComplementSet(included=exc - self._excluded)
        else:
            if inc is None:  # + -
                return _ComplementSet(included=self._included & exc)
            else:  # + +
                return _ComplementSet(included=self._included.difference(inc))

    def __rsub__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        # rsub, so the expression being evaluated is "other - self"
        if self._included is None:
            if exc is None:  # - +
                return _ComplementSet(included=inc & self._excluded)
            else:  # - -
                return _ComplementSet(included=self._excluded - exc)
        else:
            if inc is None:  # + -
                return _ComplementSet(excluded=exc | self._included)
            else:  # + +
                return _ComplementSet(included=inc.difference(self._included))

    def difference_update(self, other):
        try:
            self -= other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __isub__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                self._excluded |= inc
            else:  # - -
                self._included, self._excluded = exc - self._excluded, None
        else:
            if inc is None:  # + -
                self._included &= exc
            else:  # + +
                self._included.difference_update(inc)
        return self

    def __eq__(self, other):
        return (
            type(self) is type(other)
            and self._included == other._included
            and self._excluded == other._excluded) or (
            type(other) in (set, frozenset) and self._included == other)

    def __hash__(self):
        return hash(self._included) ^ hash(self._excluded)

    def __len__(self):
        if self._included is not None:
            return len(self._included)
        raise NotImplementedError('complemented sets have undefined length')

    def __iter__(self):
        if self._included is not None:
            return iter(self._included)
        raise NotImplementedError('complemented sets have undefined contents')

    def __bool__(self):
        if self._included is not None:
            return bool(self._included)
        return True

    __nonzero__ = __bool__  # py2 compat


import requests
import time
import random
import itertools
from datetime import datetime
import os
import logging

log = logging.getLogger("Hentai")

headers: dict = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                  'AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/75.0.3770.142 '
                  'Safari/537.36'
}
file_ext_map: dict = {"j": "jpg", "p": "png", "g": "gif", "b": "bmp", "s": "svg", "a": "apng", "m": "mp4"}

class nHentai():
    def __init__(self, id_: int):
        self.__id: int = int(id_)
        self.__url: str = f"https://nhentai.net/g/{str(id_)}"
        self.__api: str = f"https://nhentai.net/api/gallery/{str(id_)}"
        self.__info: dict = dict()
        self.__meta: dict = dict()
        self.__alive: bool = True
        self.__get_information()

    def __get_information(self):

        def extract_key(v: dict):
            return v['type']

        count: int = 0
        while self.__alive:
            time.sleep(random.random())
            r = requests.get(self.__api, headers=headers)
            r.encoding = 'utf-8'
            status = r.status_code
            if count > 3:
                self.__alive = False
                log.warning(f"HTTP_STATUS: {status}."
                            f" Discard this gallery. {self.__url}")
                continue
            if r.status_code == 200:
                self.__meta = r.json()
                log.info(f"Download gallery {self.__id} information from {self.__url}")
                break
            else:
                log.warning(f"HTTP_STATUS: {status}. Gallery {self.__id} information failed to download. "
                            f"Retry in 1 seconds. Retries: {count}/3")
                count += 1
                time.sleep(1)

        if self.__alive:
            self.__info: dict = {
                "id": self.__meta['id'],
                "title": self.__meta['title'],
                'category': [{k: [x["name"] for x in g]}
                             for k, g in itertools.groupby(self.__meta["tags"], extract_key)],
                'upload_date': {
                    "key": self.__meta['upload_date'],
                    "key_as_string": datetime.utcfromtimestamp(self.__meta['upload_date']).strftime('%Y-%m-%dT%H:%M:%SZ')
                },
                'media_id': self.__meta['media_id'],
                'num_pages': self.__meta['num_pages'],
                'pages': [{"page": i+1, "file_ext": j['t']} for i, j in enumerate(self.__meta["images"]['pages'])],
                'url': self.__url
            }
    def info(self):
        return self.__info
    def pic_url(self):
        return ["https://i.nhentai.net/galleries/{}/{}.{}".format(
                                                                self.__info['media_id'],
                                                                i['page'],
                                                                file_ext_map[i['file_ext']]
                                                            )
                for i in self.__info['pages']]
    def download_task_render(self, path_: str):
        return [(
                    self.__id,
                    "https://i.nhentai.net/galleries/{}/{}.{}".format(
                            self.__info['media_id'],
                            i['page'],
                            file_ext_map[i['file_ext']]
                        ),
                    os.path.join(path_, str(self.__id), "{}.{}".format(i['page'], file_ext_map[i['file_ext']])),
                    self.__info['title']['japanese'],
                    i['page'],
                    self.__info['num_pages'],
                ) for i in self.__info['pages']]
    def live(self):
        return self.__alive


import sys
from typing import List

#Floyd Warshall's algorithm

class FindTheCityWithTheSmallestNumberOfNeighboursAtAThresholdDistance:
    def findTheCity(self, n: int, edges: List[List[int]], distanceThreshold: int) -> int:
        distances = [[sys.maxsize] * n for _ in range(n)]

        for u, v, w in edges:
            distances[u][v] = w
            distances[v][u] = w

        for i in range(n):
            distances[i][i] = 0

        for k in range(n):
            for i in range(n):
                for j in range(n):
                    distances[i][j] = min(distances[i][j], distances[i][k] + distances[k][j])

        counts = {}

        for i, dist in enumerate(distances):
            counts[len([d for d in dist if d <= distanceThreshold])] = i

        return counts[min(counts)]

if __name__ == '__main__':
    init = FindTheCityWithTheSmallestNumberOfNeighboursAtAThresholdDistance()
    print(init.findTheCity(n = 4, edges = [[0,1,3],[1,2,1],[1,3,4],[2,3,1]], distanceThreshold = 4)) #3
    print(init.findTheCity(n = 5, edges = [[0,1,2],[0,4,8],[1,2,3],[1,4,2],[2,3,1],[3,4,1]], distanceThreshold = 2)) #0


from database import sql_select

def all_friends(user_id):
    friends = sql_select("SELECT id, name FROM friendships LEFT JOIN users ON friend2_id = id WHERE friend1_id = %s", [user_id])
    friend_names = []
    for item in friends:
        friend_names.append({
            "id": item[0],
            "name": item[1]
            })
    return friend_names


from skimage import io
import utils
import keras
from keras import models
import matplotlib.pyplot as plt
import numpy as np
import os
import sys

# Choix de path
console = False
if console:
    ROOT_DIR = os.path.abspath("/home/achauviere/Bureau/Projet_Detection_Metastase_Souris/deepmeta-master/")
    sys.path.append(ROOT_DIR)
    PATH_GIT = os.path.join(ROOT_DIR, "../deepmeta-master/")
    PATH_DATA = os.path.join(ROOT_DIR, "../DATA/")
else:
    PATH_GIT = "../deepmeta-master/"
    PATH_DATA = "../DATA/"

######## Visualisation des features maps ########

# On choisit une image :
souris_28 = os.path.join(PATH_DATA, "Souris_Test/Souris/souris_28.tif")
data = utils.contraste_and_reshape(io.imread(souris_28))
img = data[50]
img = img.reshape(1, 128, 128, 1)

# On load le modele
path_model_seg = os.path.join(PATH_GIT, "Poumons/model/model_seg.h5")
modele_seg = keras.models.load_model(path_model_seg, custom_objects={'mean_iou': utils.mean_iou})

# Extracts the outputs of every layers
layer_name = None
outputs = [layer.output for layer in modele_seg.layers if layer.name == layer_name or layer_name is None][1:]

# Creates a model that will return these outputs, given the model input
activation_model = models.Model(inputs=modele_seg.input, outputs=outputs)

# Returns a list of five Numpy arrays: one array per layer activation
activations = activation_model.predict(img)

# Représentation des Features maps
layer_names = []
for layer in modele_seg.layers:
    layer_names.append(layer.name)  # Names of the layers, so you can have them as part of your plot

images_per_row = 16

for layer_name, layer_activation in zip(layer_names, activations):  # Displays the feature maps
    n_features = layer_activation.shape[-1]  # Number of features in the feature map
    size = layer_activation.shape[1]  # The feature map has shape (1, size, size, n_features).
    n_cols = n_features // images_per_row  # Tiles the activation channels in this matrix
    display_grid = np.zeros((size * n_cols, images_per_row * size))
    for col in range(n_cols):  # Tiles each filter into a big horizontal grid
        for row in range(images_per_row):
            channel_image = layer_activation[0, :, :, col * images_per_row + row]
            channel_image -= channel_image.mean()  # Post-processes the feature to make it visually palatable
            channel_image /= channel_image.std()
            channel_image *= 64
            channel_image += 128
            channel_image = np.clip(channel_image, 0, 255).astype('uint8')
            display_grid[col * size: (col + 1) * size,  # Displays the grid
            row * size: (row + 1) * size] = channel_image
    scale = 1. / size
    plt.figure(figsize=(scale * display_grid.shape[1],
                        scale * display_grid.shape[0]))
    plt.title(layer_name)
    plt.grid(False)
    plt.imshow(display_grid, aspect='auto', cmap='viridis')


from grafos2023 import GrafoLista

# Crear un objeto GrafoLista
grafo = GrafoLista()
print("Grafo: ",grafo)

# Agregamos algunos nodos
grafo.adicionarVertice("A")
grafo.adicionarVertice("B")
grafo.adicionarVertice("C")

# Agregamos algunos arcos
grafo.adicionarArco("A", "B")
grafo.adicionarArco("C", "B")
grafo.adicionarArco("B", "C")
grafo.adicionarArco("C", "A")

print("Grafo: ",grafo)

# Probamos la función gradoEntrada para el nodo "C"
grado_entrada = grafo.gradoEntrada("C")#1
print(grado_entrada)
# Debería imprimir 1, ya que hay un arco entrando al nodo "C"

# Probamos la función gradoEntrada para el nodo "B"
grado_entrada = grafo.gradoEntrada("B")#2
print(grado_entrada)
# Debería imprimir 2, ya que hay un arco entrando al nodo "B"


def main():
    """
    Visualize the outputs of the train dataset analyses.
    """

    import matplotlib.pyplot as plt
    import pandas as pd

    # The original data set
    df_0 = pd.DataFrame.from_csv('arr_dep.csv')
    # The data set with the multiplication interaction feature.
    df_times = pd.DataFrame.from_csv('arr_times_dep.csv')
    # The data set with the subtraction interaction feature.
    df_minus = pd.DataFrame.from_csv('arr_minus_dep.csv')

    alpha = .04
    # Show the relationships between the original features and
    # the target.
    fig = plt.figure(1)
    plt.subplot(1,2,1)
    plt.plot(df_0.index, df_0.iloc[:,1], color='red',
             marker='.', linewidth=0, alpha=alpha)
    plt.xlabel('feature 0')
    plt.ylabel('regression target')

    plt.subplot(1,2,2)
    plt.plot(df_0.iloc[:,0], df_0.iloc[:,1], color='blue',
             marker='.', linewidth=0, alpha=alpha)
    plt.xlabel('feature 1')
    fig.tight_layout()
    fig.savefig('original.png')

    # Show the relationships between the multiplicative feature and
    # the target.
    fig = plt.figure(2)
    plt.subplot(1,2,1)
    plt.plot(df_times.index, df_times.iloc[:,1], color='red',
             marker='.', linewidth=0, alpha=alpha)
    plt.xlabel('feature 0')
    plt.ylabel('regression target')

    plt.subplot(1,2,2)
    plt.plot(df_times.iloc[:,2], df_times.iloc[:,1], color='orange',
             marker='.', linewidth=0, alpha=alpha)
    plt.xlabel('feature 0 x feature 1')
    fig.tight_layout()
    fig.savefig('multiply.png')

    # Show the relationships between the subtractive feature and
    # the target.
    fig = plt.figure(3)
    plt.subplot(1,2,1)
    plt.plot(df_minus.index, df_minus.iloc[:,1], color='red',
             marker='.', linewidth=0, alpha=alpha)
    plt.xlabel('feature 0')
    plt.ylabel('regression target')

    alpha = .02
    plt.subplot(1,2,2)
    plt.plot(df_minus.iloc[:,2], df_minus.iloc[:,1], color='green',
             marker='.', linewidth=0, alpha=alpha)
    plt.xlabel('feature 1 - feature 0')
    fig.tight_layout()
    fig.savefig('subtraction.png')
    plt.show()

if __name__ == '__main__':
    main()


from . import anchor as _anchor

from ..commons import logger, InstanceTable, siibra_tqdm, __version__
from ..core import concept
from ..core import space, region, parcellation

from typing import Union, TYPE_CHECKING, List, Dict, Type, Tuple, BinaryIO
from hashlib import md5
from collections import defaultdict
from zipfile import ZipFile

if TYPE_CHECKING:
    from ..retrieval.datasets import EbrainsDataset
    TypeDataset = EbrainsDataset

class ParseLiveQueryIdException(Exception):
    pass

class EncodeLiveQueryIdException(Exception):
    pass

class NotFoundException(Exception):
    pass

_README_TMPL = """
Downloaded from siibra toolsuite.
siibra-python version: {version}

All releated resources (e.g. doi, web resources) are categorized under publications.

Name
----
{name}

Description
-----------
{description}

Modality
--------
{modality}

{publications}
"""
_README_PUBLICATIONS = """
Publications
------------
{doi}

{ebrains_page}

{authors}

{publication_desc}

"""

class Feature:
    """
    Base class for anatomically anchored data features.
    """

    SUBCLASSES: Dict[Type['Feature'], List[Type['Feature']]] = defaultdict(list)

    CATEGORIZED: Dict[str, Type['InstanceTable']] = defaultdict(InstanceTable)

    category: str = None

    def __init__(
        self,
        modality: str,
        description: str,
        anchor: _anchor.AnatomicalAnchor,
        datasets: List['TypeDataset'] = []
    ):
        """
        Parameters
        ----------
        modality: str
            A textual description of the type of measured information
        description: str
            A textual description of the feature.
        anchor: AnatomicalAnchor
        datasets : list
            list of datasets corresponding to this feature
        """
        self._modality_cached = modality
        self._description = description
        self._anchor_cached = anchor
        self.datasets = datasets

    @property
    def modality(self):
        # allows subclasses to implement lazy loading of the modality
        return self._modality_cached

    @property
    def anchor(self):
        # allows subclasses to implement lazy loading of an anchor
        return self._anchor_cached

    def __init_subclass__(cls, configuration_folder=None, category=None, do_not_index=False):

        # Feature.SUBCLASSES serves as an index where feature class inheritance is cached. When users
        # queries a branch on the hierarchy, all children will also be queried. There are usecases where
        # such behavior is not desired (e.g. ProxyFeature, which wraps livequery features id to capture the
        # query context).
        # do_not_index flag allow the default index behavior to be toggled off.

        if do_not_index is False:

            # extend the subclass lists
            # Iterate over all mro, not just immediate base classes
            for BaseCls in cls.__mro__:
                # some base classes may not be sub class of feature, ignore these
                if not issubclass(BaseCls, Feature):
                    continue
                cls.SUBCLASSES[BaseCls].append(cls)

        cls._live_queries = []
        cls._preconfigured_instances = None
        cls._configuration_folder = configuration_folder
        cls.category = category
        if category is not None:
            cls.CATEGORIZED[category].add(cls.__name__, cls)
        return super().__init_subclass__()

    @classmethod
    def _get_subclasses(cls):
        return {Cls.__name__: Cls for Cls in cls.SUBCLASSES}

    @property
    def description(self):
        """Allows subclasses to overwrite the description with a function call."""
        if self._description:
            return self._description
        for ds in self.datasets:
            if ds.description:
                return ds.description
        return ''

    @property
    def LICENSE(self) -> str:
        return '\n'.join([ds.LICENSE for ds in self.datasets])

    @property
    def doi_or_url(self) -> str:
        return '\n'.join([
            url.get("url")
            for ds in self.datasets
            for url in ds.urls
        ])

    @property
    def authors(self):
        return [
            contributer['name']
            for ds in self.datasets
            for contributer in ds.contributors
        ]

    @property
    def name(self):
        """Returns a short human-readable name of this feature."""
        return f"{self.__class__.__name__} ({self.modality}) anchored at {self.anchor}"

    @classmethod
    def get_instances(cls, **kwargs) -> List['Feature']:
        """
        Retrieve objects of a particular feature subclass.
        Objects can be preconfigured in the configuration,
        or delivered by Live queries.
        """
        if not hasattr(cls, "_preconfigured_instances"):
            return []

        if cls._preconfigured_instances is not None:
            return cls._preconfigured_instances

        if cls._configuration_folder is None:
            cls._preconfigured_instances = []
            return cls._preconfigured_instances

        from ..configuration.configuration import Configuration
        conf = Configuration()
        Configuration.register_cleanup(cls.clean_instances)
        assert cls._configuration_folder in conf.folders
        cls._preconfigured_instances = [
            o for o in conf.build_objects(cls._configuration_folder)
            if isinstance(o, cls)
        ]
        logger.debug(
            f"Built {len(cls._preconfigured_instances)} preconfigured {cls.__name__} "
            f"objects from {cls._configuration_folder}."
        )
        return cls._preconfigured_instances

    def plot(self, *args, **kwargs):
        """Feature subclasses override this with their customized plot methods."""
        raise NotImplementedError("Generic feature class does not have a standardized plot.")

    @classmethod
    def clean_instances(cls):
        """ Removes all instantiated object instances"""
        cls._preconfigured_instances = None

    def matches(self, concept: concept.AtlasConcept) -> bool:
        if self.anchor and self.anchor.matches(concept):
            self.anchor._last_matched_concept = concept
            return True
        self.anchor._last_matched_concept = None
        return False

    @property
    def last_match_result(self):
        return None if self.anchor is None \
            else self.anchor.last_match_result

    @property
    def last_match_description(self):
        return "" if self.anchor is None \
            else self.anchor.last_match_description

    @property
    def id(self):
        prefix = ''
        for ds in self.datasets:
            if hasattr(ds, "id"):
                prefix = ds.id + '--'
                break
        return prefix + md5(self.name.encode("utf-8")).hexdigest()

    def _export(self, fh: ZipFile):
        """
        Internal implementation. Subclasses can override but call super()._export(fh).
        This allows all classes in the __mro__ to have the opportunity to append files
        of interest.
        """
        ebrains_page = "\n".join(
            {ds.ebrains_page for ds in self.datasets if getattr(ds, "ebrains_page", None)}
        )
        doi = "\n".join({
            u.get("url")
            for ds in self.datasets if ds.urls
            for u in ds.urls
        })
        authors = ", ".join({
            cont.get('name')
            for ds in self.datasets if ds.contributors
            for cont in ds.contributors
        })
        publication_desc = "\n".join({ds.description for ds in self.datasets})
        if (ebrains_page or doi) and authors:
            publications = _README_PUBLICATIONS.format(
                ebrains_page="EBRAINS page\n" + ebrains_page if ebrains_page else "",
                doi="DOI\n" + doi if doi else "",
                authors="Authors\n" + authors if authors else "",
                publication_desc="Publication description\n" + publication_desc if publication_desc else ""
            )
        else:
            publications = "Note: could not obtain any publication information. The data may not have been published yet."
        fh.writestr(
            "README.md",
            _README_TMPL.format(
                version=__version__,
                name=self.name,
                description=self.description,
                modality=self.modality,
                publications=publications
            )
        )

    def export(self, filelike: Union[str, BinaryIO]):
        """
        Export as a zip archive.

        Args:
            filelike (string or filelike): name or filehandle to write the zip file. User is responsible to ensure the correct extension (.zip) is set.
        """
        fh = ZipFile(filelike, "w")
        self._export(fh)
        fh.close()

    @staticmethod
    def serialize_query_context(feat: 'Feature', concept: concept.AtlasConcept) -> str:
        """
        Serialize feature from livequery and query context.

        It is currently impossible to retrieve a livequery with a generic UUID.
        As such, the query context (e.g. region, space or parcellation) needs to
        be encoded in the id.

        Whilst it is possible to (de)serialize *any* queries, the method is setup to only serialize
        livequery features.

        The serialized livequery id follows the following pattern:

        <livequeryid_version>::<feature_cls_name>::<query_context>::<unserialized_id>

        Where:

        - livequeryid_version: version of the serialization. (e.g. lq0)
        - feature_cls_name: class name to query. (e.g. BigBrainIntensityProfile)
        - query_context: string to retrieve atlas concept in the query context. Can be one of the following:
            - s:<space_id>
            - p:<parcellation_id>
            - p:<parcellation_id>::r:<region_id>
        - unserialized_id: id prior to serialization

        See test/features/test_feature.py for tests and usages.
        """
        if not hasattr(feat.__class__, '_live_queries'):
            raise EncodeLiveQueryIdException(f"generate_livequery_featureid can only be used on live queries, but {feat.__class__.__name__} is not.")

        encoded_c = []
        if isinstance(concept, space.Space):
            encoded_c.append(f"s:{concept.id}")
        elif isinstance(concept, parcellation.Parcellation):
            encoded_c.append(f"p:{concept.id}")
        elif isinstance(concept, region.Region):
            encoded_c.append(f"p:{concept.parcellation.id}")
            encoded_c.append(f"r:{concept.name}")

        if len(encoded_c) == 0:
            raise EncodeLiveQueryIdException("no concept is encoded")

        return f"lq0::{feat.__class__.__name__}::{'::'.join(encoded_c)}::{feat.id}"

    @classmethod
    def deserialize_query_context(Cls, feature_id: str) -> Tuple[Type['Feature'], concept.AtlasConcept, str]:
        """
        Deserialize id into query context.

        See docstring of serialize_query_context for context.
        """
        lq_version, *rest = feature_id.split("::")
        if lq_version != "lq0":
            raise ParseLiveQueryIdException("livequery id must start with lq0::")

        clsname, *concepts, fid = rest

        Features = Cls.parse_featuretype(clsname)

        if len(Features) == 0:
            raise ParseLiveQueryIdException(f"classname {clsname!r} could not be parsed correctly. {feature_id!r}")
        F = Features[0]

        concept = None
        for c in concepts:
            if c.startswith("s:"):
                if concept is not None:
                    raise ParseLiveQueryIdException("Conflicting spec.")
                concept = space.Space.registry()[c.replace("s:", "")]
            if c.startswith("p:"):
                if concept is not None:
                    raise ParseLiveQueryIdException("Conflicting spec.")
                concept = parcellation.Parcellation.registry()[c.replace("p:", "")]
            if c.startswith("r:"):
                if concept is None:
                    raise ParseLiveQueryIdException("region has been encoded, but parcellation has not been populated in the encoding, {feature_id!r}")
                if not isinstance(concept, parcellation.Parcellation):
                    raise ParseLiveQueryIdException("region has been encoded, but previous encoded concept is not parcellation")
                concept = concept.get_region(c.replace("r:", ""))
        if concept is None:
            raise ParseLiveQueryIdException(f"concept was not populated: {feature_id!r}")

        return (F, concept, fid)

    @classmethod
    def parse_featuretype(cls, feature_type: str) -> List[Type['Feature']]:
        ftypes = {
            feattype
            for FeatCls, feattypes in cls.SUBCLASSES.items()
            if all(w.lower() in FeatCls.__name__.lower() for w in feature_type.split())
            for feattype in feattypes
        }
        if len(ftypes) > 1:
            return [ft for ft in ftypes if getattr(ft, 'category')]
        else:
            return list(ftypes)

    @classmethod
    def livequery(cls, concept: Union[region.Region, parcellation.Parcellation, space.Space], **kwargs) -> List['Feature']:
        if not hasattr(cls, "_live_queries"):
            return []

        live_instances = []
        for QueryType in cls._live_queries:
            argstr = f" ({', '.join('='.join(map(str,_)) for _ in kwargs.items())})" \
                if len(kwargs) > 0 else ""
            logger.info(
                f"Running live query for {QueryType.feature_type.__name__} "
                f"objects linked to {str(concept)}{argstr}"
            )
            q = QueryType(**kwargs)
            features = [
                Feature.wrap_livequery_feature(feat, Feature.serialize_query_context(feat, concept))
                for feat in q.query(concept)
            ]
            live_instances.extend(features)
        return live_instances

    @classmethod
    def match(cls, concept: Union[region.Region, parcellation.Parcellation, space.Space], feature_type: Union[str, Type['Feature'], list], **kwargs) -> List['Feature']:
        """
        Retrieve data features of the desired modality.

        Parameters
        ----------
        concept: AtlasConcept
            An anatomical concept, typically a brain region or parcellation.
        modality: subclass of Feature
            specififies the type of features ("modality")
        """
        if isinstance(feature_type, list):
            # a list of feature types is given, collect match results on those
            assert all(
                (isinstance(t, str) or issubclass(t, cls))
                for t in feature_type
            )
            return list(dict.fromkeys(
                sum((
                    cls.match(concept, t, **kwargs) for t in feature_type
                ), [])
            ))

        if isinstance(feature_type, str):
            # feature type given as a string. Decode the corresponding class.
            # Some string inputs, such as connectivity, may hit multiple matches.
            ftype_candidates = cls.parse_featuretype(feature_type)
            if len(ftype_candidates) == 0:
                raise ValueError(
                    f"feature_type {str(feature_type)} did not match with any "
                    f"features. Available features are: {', '.join(cls.SUBCLASSES.keys())}"
                )
            logger.info(
                f"'{feature_type}' decoded as feature type/s: "
                f"{[c.__name__ for c in ftype_candidates]}."
            )
            return cls.match(concept, ftype_candidates, **kwargs)

        assert issubclass(feature_type, Feature)

        if not isinstance(concept, (region.Region, parcellation.Parcellation, space.Space)):
            raise ValueError(
                "Feature.match / siibra.features.get only accepts Region, "
                "Space and Parcellation objects as concept."
            )

        msg = f"Matching {feature_type.__name__} to {concept}"
        instances = [
            instance
            for f_type in cls.SUBCLASSES[feature_type]
            for instance in f_type.get_instances()
        ]

        preconfigured_instances = [
            f for f in siibra_tqdm(
                instances, desc=msg, total=len(instances), disable=(not instances)
            ) if f.matches(concept)
        ]

        live_instances = feature_type.livequery(concept, **kwargs)

        return list(dict.fromkeys(preconfigured_instances + live_instances))

    @classmethod
    def get_instance_by_id(cls, feature_id: str, **kwargs):
        try:
            F, concept, fid = cls.deserialize_query_context(feature_id)
            return [
                f
                for f in F.livequery(concept, **kwargs)
                if f.id == fid or f.id == feature_id
            ][0]
        except ParseLiveQueryIdException:
            return [
                inst
                for Cls in Feature.SUBCLASSES[Feature]
                for inst in Cls.get_instances()
                if inst.id == feature_id
            ][0]
        except IndexError:
            raise NotFoundException

    @classmethod
    def get_ascii_tree(cls):
        # build an Ascii representation of class hierarchy
        # under this feature class
        from anytree.importer import DictImporter
        from anytree import RenderTree

        def create_treenode(feature_type):
            return {
                'name': feature_type.__name__,
                'children': [
                    create_treenode(c)
                    for c in feature_type.__subclasses__()
                ]
            }
        D = create_treenode(cls)
        importer = DictImporter()
        tree = importer.import_(D)
        return "\n".join(
            "%s%s" % (pre, node.name)
            for pre, _, node in RenderTree(tree)
        )

    @staticmethod
    def wrap_livequery_feature(feature: 'Feature', fid: str):
        """
        Wrap live query features, override only the id attribute.

        Some features do not have setters for the id property. The ProxyFeature class
        allow the id property to be overridden without touching the underlying class.

        See docstring of serialize_query_context for further context.
        """
        class ProxyFeature(feature.__class__, do_not_index=True):

            # override __class__ property
            # some instances of features accesses inst.__class__
            @property
            def __class__(self):
                return self.inst.__class__

            def __init__(self, inst: Feature, fid: str):
                self.inst = inst
                self.fid = fid

            def __str__(self) -> str:
                return self.inst.__str__()

            def __repr__(self) -> str:
                return self.inst.__repr__()

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

            def __getattr__(self, __name: str):
                return getattr(self.inst, __name)

        return ProxyFeature(feature, fid)


def findMaxConsecutiveOnes(nums):
        # iterate the list
        counter = 0
        ans = []
        for num in nums:
            # check if num in nums is 1
            if num == 1:
                # if it is counter += 1
                counter += 1
            # if it is not append counter to an empty and counter becomes 0
            else:
                ans.append(counter)
                counter = 0
        # return maximum number in the list
        ans.append(counter)
        return max(ans)

input = [1,1,0,1,1,0]
print(findMaxConsecutiveOnes(input))


#!/usr/bin/env python
"""Command Line Podcast Manager

You can add podcast RSS feeds, download podcasts, and sync them to a directory.
If the podcast ID3 tags are messy or unclear you can also automatically edit
them by setting the id3_edit config.

id3_edit structure:
    "id3_edit": {
        "podcast table id": {
          "album": "string to manually set album to",
          "artist": "string to manually set artist to",
          "title": "optional parameter: if set to copy_item will set to title
                    from the rss feed, otherwise if set to true will set
                    title to rss published date + album config"
        }
    }
"""
import json
import argparse
import urllib.request, urllib.parse, urllib.error
import os
import sys
import shutil
import urllib.parse
import subprocess
import feedparser
from peewee import CharField, ForeignKeyField, DateTimeField, BooleanField, SqliteDatabase, Model, IntegrityError, TextField
from time import mktime, sleep
from datetime import datetime, timedelta
from downloader import Download, DownloadError
import mutagen
from mutagen import easyid3
from bs4 import BeautifulSoup
import unicodedata
import textwrap
from terminaltables import AsciiTable
from terminaltables.width_and_alignment import max_dimensions
from terminaltables.build import flatten
from blessings import Terminal
from gevent import monkey
monkey.patch_all()
import gevent

def load_config():
    f = open('podcli_config.json', 'r')
    return json.load(f)

def get_enclosure(links):
    for x in links:
        if x['rel'] == 'enclosure':
            return x['href']

# Models
db = SqliteDatabase(load_config()['db'])

class PodcastTable(Model):
    feed = CharField(unique=True)
    title = CharField()

    class Meta:
        database = db  # this model uses the podcli database

class EpisodeTable(Model):
    podcast = ForeignKeyField(PodcastTable, related_name='episodes')
    title = CharField()
    published = DateTimeField()
    enclosure = CharField()
    summary = TextField(null=True)
    new = BooleanField()

    class Meta:
        database = db  # this model uses the podcli database

def create_tables():
    PodcastTable.create_table(fail_silently=True)
    EpisodeTable.create_table(fail_silently=True)

def ascii_table_last(ascii_table):
    dimensions = max_dimensions(ascii_table.table_data,
                                ascii_table.padding_left,
                                ascii_table.padding_right)[:3]
    whole_table = ascii_table.table_data
    ascii_table.table_data = (ascii_table.table_data[-1], )
    last_table = flatten(ascii_table.gen_table(*dimensions))
    ascii_table.table_data = whole_table
    return last_table

def get_max_dimensions(ascii_table):
    return max_dimensions(ascii_table.table_data, ascii_table.padding_left,
                          ascii_table.padding_right)[:3]

class PodCli(object):
    def __init__(self):
        self.config = load_config()
        self.download_dir = self.get_download_dir()
        self.check_download_dir()

    def get_download_dir(self):
        if 'download_folder' in list(self.config.keys()):
            if os.path.isabs(self.config['download_folder']):
                return self.config['download_folder']
            else:
                return os.path.join(
                    os.path.dirname(os.path.realpath(sys.argv[0])),
                    self.config['download_folder'])
        else:
            return os.path.dirname(os.path.realpath(sys.argv[0]))

    def check_download_dir(self):
        if not os.path.exists(self.download_dir):
            os.mkdir(self.download_dir)

    def add_podcast(self, rss_url):
        feed = feedparser.parse(rss_url)
        try:
            PodcastTable.create(feed=rss_url, title=feed['feed']['title'])
        except IntegrityError:
            print('Podcast already exists.')
        for pod in PodcastTable.select():
            print(pod.title, pod.feed)

    def get_summary(self, item):
        text = BeautifulSoup(item["summary"], "html.parser").get_text()
        return unicodedata.normalize("NFKD", text)

    def print_summary(self, summary):
        if summary:
            term = Terminal()
            for line in textwrap.wrap(
                    summary,
                    term.width,
                    initial_indent='    ',
                    subsequent_indent='    '):
                print(line)

    def print_summary_table(self, items=None):
        table_headers = [['title', 'summary']]
        table_data = []
        ascii_table = None
        if not items:
            items = EpisodeTable.select().where(EpisodeTable.new)
        for item in items:
            term = Terminal()
            summ = ""
            for line in textwrap.wrap(
                    item.summary,
                    term.width * 0.7,
                    initial_indent=' ',
                    subsequent_indent=' '):
                summ += line + "\n"
            table_data.append([item.podcast.title, summ])
            ascii_table = AsciiTable(table_headers + table_data)
            ascii_table.inner_row_border = True
        if ascii_table:
            print(ascii_table.table)
        else:
            "Nothing to show"

    def print_download_item(self, item, ascii_table):
        dimensions = get_max_dimensions(ascii_table)
        title = ""
        for line in textwrap.wrap(
                item.podcast.title,
                dimensions[0][0],
                initial_indent=' ',
                subsequent_indent=' '):
            title += line + "\n"
        summ = ""
        for line in textwrap.wrap(
                item.summary,
                dimensions[0][1],
                initial_indent=' ',
                subsequent_indent=' '):
            summ += line + "\n"
        if ascii_table:
            ascii_table.table_data.append([title, summ])

            print(ascii_table_last(ascii_table))
            return ascii_table
        else:
            table_headers = [['title', 'summary']]
            table_data = [[title, summ]]
            ascii_table = AsciiTable(table_headers + table_data)
            ascii_table.inner_row_border = True
            print(ascii_table.table)
            return ascii_table

    def refresh_all(self):
        # print("Refreshing feeds ...")
        spawned = []
        for pod in PodcastTable.select():
            spawned.append(gevent.spawn(self.get_podcast_feed, pod.feed, pod))
        gevent.iwait
        dots = 1
        while True:
            alive = False
            for gl in spawned:
                if not gl.dead:
                    alive = True
            if not alive:
                print("\n")
                break
            message = "Refreshing feeds " + "." * dots
            sys.stdout.write('\x1b[2K\r' + message)
            sys.stdout.flush()
            dots += 1
            if dots > 4:
                dots = 1
            gevent.sleep(0.5)

    def get_podcast_feed(self, url, pod):
        feed = feedparser.parse(url)
        for item in feed['entries']:
            enclosure = self.get_enclosure(item)
            if not enclosure:
                # print('%s has no link, skipping...' % item.title)
                continue
            # If episode enclosure doesn't exist, add it
            if EpisodeTable.select().where(
                    EpisodeTable.enclosure == enclosure).count() < 1:
                dt = datetime.fromtimestamp(mktime(item['published_parsed']))
                summary = self.get_summary(item)
                # print('New Episode: ', pod.title, " -- ", item['title'], dt.strftime('%d/%m/%Y'))
                # self.print_summary(summary)
                # print("\n")
                EpisodeTable.create(
                    podcast=pod,
                    title=item['title'],
                    published=dt,
                    enclosure=enclosure,
                    summary=summary,
                    new=True)
        # print("Refreshed feed: %s" % pod.title)

    def get_enclosure(self, episode):
        if 'links' not in list(episode.keys()):
            return False
        for link in episode['links']:
            if link['rel'] == 'enclosure':
                return link['href']

    def is_downloaded(self, url, filename):
        if not os.path.exists(filename):
            return False
        try:
            df = Download(url, filename)
            filesize = df.get_url_file_size()
        except urllib.error.HTTPError:
            print("HTTTP Error Skipping")
            return True
        if not filesize:
            return False
        elif int(filesize) > os.path.getsize(filename):
            print("filesize mismatch: %s %s" % (filesize,
                                                os.path.getsize(filename)))
            return False
        else:
            return True

    def download_all_new(self):
        print("Downloading ...")
        spawned = []
        ascii_table = None
        for item in EpisodeTable.select().where(EpisodeTable.new):
            filename = self.get_fullpath(item.enclosure)
            if not self.is_downloaded(item.enclosure, filename):
                ascii_table = self.print_download_item(item, ascii_table)
                spawned.append(
                    gevent.spawn(self.download, item.enclosure, filename,
                                 item))
        gevent.joinall(spawned)

    def download(self, url, fullpath, item):
        try:
            df = Download(url, fullpath)
            df.download()
        except urllib.error.HTTPError:
            print("Http Error, skipping")
            return False
        self.check_id3_edit(item.podcast.id, fullpath, item)
        return

    def get_fullpath(self, url):
        return os.path.join(
            self.download_dir,
            urllib.parse.unquote(
                os.path.basename(urllib.parse.urlparse(url).path)))

    def list(self, which):
        if which == 'new':
            self.print_summary_table()
            # for item in EpisodeTable.select().where(EpisodeTable.new):
            #     self.print_summary(item.summary)
            #     print("\n")
        if which == 'pod':
            table_headers = [['id', 'title']]
            table_data = []
            for item in PodcastTable.select():
                table_data.append([str(item.id), item.title])
            ascii_table = AsciiTable(table_headers + table_data)
            ascii_table.inner_row_border = True
            print(ascii_table.table)

    def check_id3_edit(self, podcast_id, filename, item):
        if str(podcast_id) in list(self.config['id3_edit'].keys()):
            id3_config = self.config['id3_edit'][str(podcast_id)]
            album = id3_config['album']
            artist = id3_config['artist']
            if 'title' in list(id3_config.keys()):
                if id3_config['title'] == 'copy_item':
                    title = item.title
                else:
                    title = item.published.strftime('%d/%m-') + album
                self.edit_id3(filename, album, artist, title)
            else:
                self.edit_id3(filename, album, artist)

    def edit_id3(self, filename, album, artist, title=None):
        try:
            audio = easyid3.EasyID3(filename)
        except mutagen.id3.ID3NoHeaderError:
            audio = mutagen.File(filename, easy=True)
            audio.add_tags()
        audio["album"] = album
        audio["artist"] = artist
        audio["genre"] = "Podcast"
        if title:
            audio["title"] = title
        audio.save()

    def sync(self, which):
        if which == 'new':
            print("syncing ...")
            for item in EpisodeTable.select().where(EpisodeTable.new):
                filename = self.get_fullpath(item.enclosure)
                if not os.path.exists(filename):
                    print("Haven't downloaded %s yet." %
                          os.path.basename(filename))
                    continue
                self.print_summary_table([item])
                if self.config["folder_mode"]:
                    pod_dir = os.path.join(self.config['sync_to'],
                                           item.podcast.title)
                    writetopath = os.path.join(pod_dir,
                                               os.path.basename(filename))
                    if not os.path.exists(pod_dir):
                        os.mkdir(pod_dir)
                else:
                    writetopath = os.path.join(self.config['sync_to'],
                                               os.path.basename(filename))
                shutil.copyfile(filename, writetopath)
                item.new = False
                item.save()

    def delete_podcast(self, podcast_id):
        podcast = PodcastTable.select().\
            where(PodcastTable.id == podcast_id).get()
        podcast.delete_instance(recursive=True)

    def delete_old(self, location):
        if location == 'local':
            self.delete_files_local(self.download_dir)
        elif location == 'player':
            self.delete_files(self.config['sync_to'])

    def delete_files(self, direc, num_days=14):
        cur_dir = os.getcwd()
        os.chdir(direc)
        pod_dirs = os.listdir(direc)
        for pod_dir in pod_dirs:
            os.chdir(cur_dir)
            podcast = PodcastTable.select().\
                where(PodcastTable.title == pod_dir).get()
            os.chdir(direc)
            files = os.listdir(pod_dir)
            os.chdir(pod_dir)
            for filename in files:
                file_age = (datetime.now() -\
                    datetime.fromtimestamp(os.path.getctime(filename))).days
                if str(podcast.id) in self.config["podcast_age"].keys():
                    conf_age = self.config["podcast_age"][str(podcast.id)]
                else:
                    conf_age = num_days
                if file_age > num_days or file_age > conf_age:
                    print('removing %s' % (str(filename)))
                    os.remove(filename)
            os.chdir(direc)
        os.chdir(cur_dir)

    def delete_files_local(self, direc, num_days=14):
        cur_dir = os.getcwd()
        os.chdir(direc)
        files = os.listdir(direc)
        for filename in files:
            if (datetime.now() - datetime.fromtimestamp(
                    os.path.getctime(filename))).days > num_days:
                print('removing %s' % (str(filename)))
                os.remove(filename)
        os.chdir(cur_dir)

    def mark_old(self, days, podcast_id=False):
        if podcast_id:
            podcast = PodcastTable.select().where(
                PodcastTable.id == podcast_id).get()
            episodes = EpisodeTable.select().\
                where(EpisodeTable.new, EpisodeTable.podcast == podcast)
        else:
            episodes = EpisodeTable.select().where(EpisodeTable.new)
        for item in episodes:
            if item.published < datetime.now() - timedelta(days):
                print('Marking old: ', item.title)
                item.new = False
                item.save()

    def eject(self):
        while subprocess.call(
            ['diskutil', 'unmount', self.config['eject_point']]):
            print("Attempting to eject.")
            sleep(5)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("-a", "--add_podcast", help="add new podcast")
    parser.add_argument(
        "-r",
        "--refresh_all",
        help="refresh all podcasts",
        nargs='?',
        const=True)
    parser.add_argument(
        "-d",
        "--download_all_new",
        help="download all new podcasts",
        nargs='?',
        const=True)
    parser.add_argument(
        "-l",
        "--list",
        help=
        "with no argument lists all new podcasts, with argument pod it lists all podcasts",
        nargs='?',
        const='new')
    parser.add_argument(
        "-s", "--sync", help="sync all new podcasts", nargs='?', const='new')
    parser.add_argument(
        "--delete", help="delete podcast, must specify podcast id")
    parser.add_argument(
        "--delete_old",
        help=
        "delete old episode downloads, defaults to local episodes, other option player",
        nargs='?',
        const='local')
    parser.add_argument(
        "--mark_old",
        help="mark episodes older than arg as old",
        nargs='?',
        const='7')
    parser.add_argument(
        "--mark_old_podcast",
        help="specifies mark_old podcast to mark old episodes",
        nargs='?',
        const=False)
    parser.add_argument(
        "-e", "--eject", help="eject player", nargs='?', const=True)
    args = parser.parse_args()
    podcli = PodCli()

    if args.add_podcast:
        podcli.add_podcast(args.add_podcast)
    if args.refresh_all:
        podcli.refresh_all()
    if args.download_all_new:
        podcli.download_all_new()
    if args.list:
        podcli.list(args.list)
    if args.sync:
        podcli.sync(args.sync)
    if args.delete:
        podcli.delete_podcast(args.delete)
    if args.delete_old:
        podcli.delete_old(args.delete_old)
    if args.eject:
        podcli.eject()
    if args.mark_old:
        podcli.mark_old(int(args.mark_old), args.mark_old_podcast)


#!/usr/bin/env python3

from time import sleep

import ev3dev.fonts as fonts
from ev3dev.ev3 import *

lcd = Screen()

def lcd_print(text):
    lcd.clear()
    lcd.draw.text((40, 50), text, font=fonts.load('helvB24'))
    lcd.update()

lcd_print('Start')
sleep(2)
lcd_print('Exit')
sleep(5)


from celery import shared_task
from .models import User, Transaction
import math
from datetime import timedelta

@shared_task
def calculate_credit_score(user_id):
    user = User.objects.get(id=user_id)

    # Calculate the total account balance for the user
    transactions = Transaction.objects.filter(user=user)
    total_balance = sum(tr.amount if tr.transaction_type == 'CREDIT' else -tr.amount for tr in transactions)

    if total_balance >= 1000000:
        user.credit_score = 900
    elif total_balance <= 100000:
        user.credit_score = 300
    else:
        balance_diff = total_balance - 100000
        credit_score_change = balance_diff // 15000 * 10
        user.credit_score = 300 + credit_score_change

    user.save()

@shared_task
def calculate_emis(loan_amount, interest_rate, term_period, disbursement_date, monthly_income):
    # Calculate the monthly interest rate
    monthly_interest_rate = (interest_rate / 12) / 100

    # Calculate the EMI amount
    emi_numerator = loan_amount * monthly_interest_rate
    emi_denominator = 1 - math.pow(1 + monthly_interest_rate, -term_period)
    emi_amount = emi_numerator / emi_denominator

    # Initialize variables
    emi_dates = []
    remaining_principal = loan_amount
    total_interest = 0

    for month in range(term_period):

        monthly_interest = remaining_principal * monthly_interest_rate

        # Calculate principal for the month
        principal_payment = emi_amount - monthly_interest

        if emi_amount > (0.6 * monthly_income):
            return None

        total_interest += monthly_interest

        due_date = disbursement_date + timedelta(days=30)

        # Add EMI details to the list
        emi_dates.append({
            "Date": due_date,
            "Amount_due": emi_amount
        })

        remaining_principal -= principal_payment

    if total_interest <= 10000:
        return None  # Total interest is not sufficient

    return emi_dates


#!/usr/bin/env python3

import gym
import time
import procgen
import cv2
import numpy as np
import torch as th
from abc import abstractmethod
from typing import Tuple, Optional, Dict, Any
from pathlib import Path

_AGENT_COLOR: Tuple[int, int, int] = (127, 255, 127)
_WALL_COLOR: Tuple[int, int, int] = (191, 127, 63)
_WINDOW_SIZE: Tuple[int, int] = (7, 7)

class FlattenWrapper(gym.ObservationWrapper):
    def __init__(self, env: gym.Env):
        super().__init__(env)
        self.observation_space = gym.spaces.Box(
            low=0, high=255, shape=(
                np.prod(_WINDOW_SIZE) * 3,), dtype=np.uint8)

    def observation(self, observation: np.ndarray):
        # return observation.ravel()
        return (observation.ravel() / 255.0) - 0.5

class CustomEnvWrapper(gym.Wrapper):
    def __init__(self, env: gym.Env):
        super().__init__(env)
        self.env = env
        self.action_space = gym.spaces.Discrete(5)
        self.observation_space = gym.spaces.Box(
            low=0, high=255, shape=(_WINDOW_SIZE + (3,)), dtype=np.uint8)
        # left, down, up, right, place-marker
        self._actions = [1, 3, 5, 7, -1]
        self._markers = []
        self._history = set()

    def _get_pos(self, obs: np.ndarray) -> Tuple[int, int]:
        return tuple(np.argwhere(np.equal(obs, _AGENT_COLOR).all(
            axis=-1)).mean(axis=0).astype(np.int32))

    def _add_markers(self, obs: np.ndarray) -> np.ndarray:
        if len(self._markers) <= 0:
            return obs
        for m in self._markers:
            obs[m[0], m[1]] = np.bitwise_or(obs[m[0], m[1]], (255, 0, 0))
        return obs

    def _down_obs(self, obs: np.ndarray):
        # NOTE(ycho): [15/64] only works for `easy`
        return cv2.resize(obs, None, fx=(15 / 64), fy=(15 / 64),
                          interpolation=cv2.INTER_NEAREST_EXACT)

    def _crop_obs(self, obs: np.ndarray, loc: Tuple[int, int]):
        # cv2.imshow('obs_', obs)
        # cv2.waitKey(1)
        # return obs
        radius: Tuple[int, int] = (_WINDOW_SIZE[0] // 2, _WINDOW_SIZE[1] // 2)
        out = np.full(_WINDOW_SIZE + (3,),
                      255,
                      dtype=obs.dtype)

        idx0 = (max(0, loc[0] - radius[0]), max(0, loc[1] - radius[1]))
        idx1 = (min(loc[0] + radius[0] + 1, obs.shape[0]),
                min(loc[1] + radius[1] + 1, obs.shape[1]))
        offset = np.subtract(idx0, (loc[0] - radius[0], loc[1] - radius[1]))
        roi = obs[idx0[0]:idx1[0], idx0[1]:idx1[1]]
        out[offset[0]:offset[0] + roi.shape[0],
            offset[1]:offset[1] + roi.shape[1], :] = roi
        return out

    def _recolor_obs(self, obs: np.ndarray):
        obs[(obs == _AGENT_COLOR).all(axis=-1)] = (0, 255, 0)
        obs[(obs == _WALL_COLOR).all(axis=-1)] = (0, 0, 255)
        return obs

    def reset(self):
        self._markers = []
        self._history = set()
        obs = self._down_obs(self.env.reset())
        loc = self._get_pos(obs)
        self._history.add(loc)
        return self._recolor_obs(self._crop_obs(obs, loc))

    def step(self, action: int):
        # Take action.
        if action == 4:
            # NOTE(ycho): 4 = "null action" in procgen.
            obs, rew, done, info = self.env.step(4)
        else:
            obs, rew, done, info = self.env.step(self._actions[action])

        # Process observations.
        info['obs0'] = obs
        obs = self._down_obs(obs)
        loc = self._get_pos(obs)

        # For marker-placement actions,
        # track marker placements and add
        # extra penalty for rewards.
        if action == 4:
            rew -= 0.01
            self._markers.append(loc)
        obs = self._add_markers(obs)

        # Penalize each timestep.
        rew -= 0.01

        # Especially penalize revisits.
        if loc in self._history:
            rew -= 0.05

        # Track history, format observation and
        # and return results.
        self._history.add(loc)
        return self._recolor_obs(self._crop_obs(
            obs, loc)), rew, done, info

class MazeEnv(gym.Env):
    def __init__(self, env_config: Dict[str, Any]):
        super().__init__()
        env = gym.make('procgen:procgen-maze-v0',
                       # render_mode=render_mode,
                       # rand_seed=i,
                       start_level=0,
                       use_backgrounds=False,
                       use_monochrome_assets=True,
                       distribution_mode='easy',
                       # *args,
                       **env_config)
        env = CustomEnvWrapper(env)
        env = FlattenWrapper(env)
        self.env = env

        self.observation_space = gym.spaces.Box(
            low=-0.5, high=0.5, shape=(7 * 7 * 3,),
            dtype=np.float32)
        self.action_space = gym.spaces.Discrete(5)

    def reset(self):
        return self.env.reset()

    def step(self, action):
        return self.env.step(action)

def make_env(*args, **kwds) -> gym.Env:
    env = gym.make('procgen:procgen-maze-v0',
                   # render_mode=render_mode,
                   # rand_seed=i,
                   start_level=0,
                   # center_agent=True,
                   use_backgrounds=False,
                   use_monochrome_assets=True,
                   distribution_mode='easy',
                   # distribution_mode='memory'
                   *args, **kwds)
    env = CustomEnvWrapper(env)
    env = FlattenWrapper(env)
    return env