import numpy as np
from scipy.sparse import dok_matrix, csr_matrix
from multiprocessing import Pool, current_process

class Tools:
    def __init__(self):
        pass

    def get_co_oc_matrix(self, vocabulary_size, word2ind, corpus_list, context_size = 3):
        corpus_size = len(corpus_list)
        comat = np.zeros((vocabulary_size, vocabulary_size))
        for i in range(corpus_size): #main word
            left_context_ids = [word2ind[ind] for ind in corpus_list[max(0, i - context_size): i]]  #left context
            right_context_ids = [word2ind[ind] for ind in corpus_list[i + 1: min(i + context_size + 1, corpus_size)]] #right context
            ind = word2ind[corpus_list[i]]
            for left_ind, lind in enumerate(left_context_ids):
                comat[ind, lind] += 1./(len(left_context_ids) - left_ind) #symmetrically
            for right_ind, rind in enumerate(right_context_ids):
                comat[ind, rind] += 1./(right_ind + 1)
        return comat

    def get_co_oc_matrix_sparse(self, vocabulary_size, word2ind, corpus_list, context_size = 3, n_proc = 1):
        corpus_len = len(corpus_list)
        chunk_len = int(corpus_len / n_proc + 1)
        chunk_idx = [x for x in range(0, corpus_len, chunk_len)]
        chunk_idx.append(corpus_len)

        pool = Pool(processes=n_proc)
        args = [[corpus_list[chunk_idx[i]:chunk_idx[i+1]], word2ind,
                 vocabulary_size, context_size]
                for i in range(0, len(chunk_idx)-1)]
        results = pool.starmap(func=self.get_co_oc_matrix_workers, iterable=args)
        # print(type(results[0]))
        sparse_matrix = csr_matrix((vocabulary_size, vocabulary_size))
        i = 0
        for res in results:
            print("i = {}".format(i))
            i += 1
            sparse_matrix += res
        return sparse_matrix

    # Get a random batch
    def get_batch(self, vocab_size, batch_size):
        np.random.seed(1)
        in_index = np.random.choice(np.arange(vocab_size), size=batch_size, replace=False)
        out_index = np.random.choice(np.arange(vocab_size), size=batch_size, replace=False)
        return in_index, out_index

    def get_co_oc_matrix_workers(self, corpus_list, word2ind, vocabulary_size, context_size = 3 ):
        corpus_size = len(corpus_list)
        comat = dok_matrix((vocabulary_size, vocabulary_size)) # dok_matrix is the fastest in csc_matrix, csr_matrix, and dok_matrix
        for i in range(corpus_size):
            left_context_ids = [word2ind[ind] for ind in corpus_list[max(0, i - context_size): i]]  #left context
            right_context_ids = [word2ind[ind] for ind in corpus_list[i + 1: min(i + context_size + 1, corpus_size)]] #right context
            ind = word2ind[corpus_list[i]]
            for left_ind, lind in enumerate(left_context_ids):
                comat[ind, lind] += 1./(len(left_context_ids) - left_ind) #symmetrically
            for right_ind, rind in enumerate(right_context_ids):
                comat[ind, rind] += 1./(right_ind + 1)
            if i % 100 == 0:
                print("{} Done {:f}%".format(current_process(), float(i/corpus_size)))
        return csr_matrix(comat)
        # return comat


import operator,functools,copy,random,math,cmath

class Matrix:
    #%%Initialization and Representation

    def __init__(self,M,N):
        """initializes instance of class; attributes include entries as rows, # of rows, and # of columns"""
        self.rows=[[0]*N for n in range(M)]
        self.m=M
        self.n=N

    def __getitem__(self, idx):
        """returns the rows of given matrix"""
        return self.rows[idx]

    def __setitem__(self, idx, row):
        """sets the rows of given matrix and reassigns instance attributes"""
        self.rows[idx] = row
        self.m=len(self.rows)
        self.n=len(row)

    def __repr__(self):
        """list representation of given matrix"""
        return str(self.rows)

    def __str__(self):
        """string representation of given matrix with each row being a newline"""
        return '\n'.join([" ".join([str(item) for item in row]) for row in self.rows])+'\n'
    #%% Boolean Operations

    def __bool__(self):
        """compares given matrix to the null matrix"""
        return self!=Matrix(self.m,self.n)

    def __eq__(self,other):
        """compares rows of given matrix to determine equality"""
        return self.rows==other.rows

    def __ne__(self,other):
        """compares rows of given matrix to determine inequality"""
        return self.rows!=other.rows

    #%% Basic Operations
    def __add__(self,other):
        """adds entries of each row of given matrices"""
        m,n=self.m,self.n
        if m==other.m and n==other.n:
            matrix=Matrix(m,n)
            matrix.rows=[list(map(operator.add,self.rows[i],other.rows[i])) for i in range(self.m)]
            return matrix
        else:
            raise Exception("Matrix addition of unidentical dimensions")

    def __iadd__(self,other):
        return self+other

    def __sub__(self, other):
        """subtracts entries of each row of given matrices"""
        m,n=self.m,self.n
        if m==other.m and n==other.n:
            matrix=Matrix(m,n)
            matrix.rows=[list(map(operator.sub,self.rows[i],other.rows[i])) for i in range(self.m)]
            return matrix
        else:
            raise Exception("Matrix subtraction of unidentical dimensions")

    def __isub(self,other):
        return self-other

    def __mul__(self,other):
        """performs non-associative multiplication of given matrix with another matrix or scalar"""
        M,N=self.m,other.n
        matrix=Matrix(M,N)
        if type(self)!=type(other):
            matrix.rows=list([[other*entry for entry in row] for row in self.rows])
            return matrix
        elif self.n==other.m:
            matrix.rows=[[functools.reduce(lambda x,y:x+y,list(map(operator.mul,self_row,other_row))) for other_row in other.Transpose().rows] for self_row in self.rows]
            return matrix
        else:
            raise Exception("Matrix multiplication of invalid dimensions")

    def __imul__(self,other):
        return self*other

    def __pow__(self,exp):
        m,n=self.m,self.n
        if self.m==self.n:
            if n==0:
                return Matrix.Identity(m,n)
            matrix=copy.deepcopy(self)
            for n in range(1,exp):
                matrix*=matrix
            return matrix
        else:
            raise Exception("Matrix powers of non-square matrix")

    def __reversed__(self):
        """reverses all rows and columns of given matrix"""
        m,n=self.m,self.n
        matrix=Matrix(m,n)
        matrix.rows=[row[::-1] for row in self.rows[::-1]]
        return matrix

    #%% Matrix Specific Operations
    def Dimensions(self):
        """returns # of rows and # of columns of given matrix"""
        return self.m,self.n

    def Transpose(self):
        """transposes (exchanges rows and columns) of given matrix"""
        m,n=self.n,self.m
        matrix=Matrix(m,n)
        matrix.rows=list(map(list,zip(*self.rows)))
        return matrix

    def Conjugate(self):
        """returns the conjugate matrix of a given matrix"""
        m,n=self.n,self.m
        matrix=Matrix(m,n)
        matrix.rows=[[item.real-item.imag*1j for item in row] for row in self.rows]
        return matrix

    def Hermitian(self):
        """returns the Hermitian of a given matrix"""
        return self.Conjugat().Transpose()

    def Gram_Schmidt(self):
        """returns an orthogonormal matrix for the column space"""
        m,n=self.m,self.n
        basis=Matrix(n,m)
        MT=self.Transpose()
        first=Vector(MT.rows[0])
        basis.rows[0]=list(first*(1/first.norm()))
        for i in range(1,n):
            v=Vector(MT.rows[i])
            v_orthogonal=copy.deepcopy(v)
            for j in range(i):
                u=Vector(basis.rows[j])
                v_orthogonal-=u*(u.dot(v)/u.norm())
            v_normalized=v_orthogonal/v_orthogonal.norm()
            basis.rows[i]=list(v_normalized)
        return basis.Transpose()

    def Index(self,item):
        """finds index of item, if the item is not found returns (-1,-1)"""
        for i in range(self.m):
            try:
                idx=self.rows[i].index(item)
                return i,idx
            except:
                pass
        return -1,-1

    def Count(self,item):
        """finds the count of a particular item; this is especially useful for determing sparse/dense matrices"""
        count=0
        for row in self.rows:
            for entry in row:
                if entry==item:
                    count+=1
        return count

    def Entry(self,idx1,idx2,item):
        """modifies entry at the given column and row indices"""
        self.rows[idx1][idx2]=item

    def ExchangeRows(self,idx1,idx2):
        """exchanges rows at the given indices"""
        self.rows[idx1],self.rows[idx2]=self.rows[idx2],self.rows[idx1]

    def ExchangeCols(self,idx1,idx2):
        """exchanges columns at the given indices"""
        for row in self.rows:
            row[idx1],row[idx2]=row[idx2],row[idx1]

    def Identity(M,N):
        """produces identity matrix of dimensions M by N"""
        I=Matrix(M,N)
        for idx in range(M):
            I.Entry(idx,idx,1)
        return I

    def Random(M,N,lower=0,upper=10):
        """produces a random matrix of dimensions M by N within specified bounds"""
        R=Matrix(M,N)
        for idx in range(M):
            R[idx]=[random.random()*(upper-lower)+(lower) for n in range(N)]
        return R

    def Permute(self):
        """ """
    #%% Matrix Factorizations and Solvers

    def LU_Decomposition(self):
        """decomposes matrix into the lower and upper triangle forms"""
        U=copy.deepcopy(self)
        L=Matrix.Identity(U.m,U.n)

        for i in range(U.m):
            for j in range(i):
                c=-U.rows[i][j]/U.rows[j][j]
                R=[c*entry for entry in U.rows[j]]
                U[i]=list(map(operator.add,U[i],R))
                L.Entry(i,j,-c)
        return L,U

#Update QR using householder reflections

    def QR_Decomposition(self):
        """decomposes matrix into the orthonormal basis and upper triangular form"""
        Q=self.Gram_Schmidt()
        R=Q.Transpose()*self
        return Q,R

    def LDLT_Decomposition(self):
        """decomposes symmetric matrix by utilizing the property that such matrices have equivalent transposes and inverses"""
        LT=copy.deepcopy(self)
        D=Matrix.Identity(LT.m,LT.n)
        for i in range(LT.m):
            for j in range(i):
                c=-LT.rows[i][j]/LT.rows[j][j]
                R=[c*entry for entry in LT.rows[j]]
                LT[i]=list(map(operator.add,LT[i],R))
            d=LT[i][i]
            LT[i]=[entry/d for entry in LT[i]]
            D[i]=[entry*d for entry in D[i]]
        return LT.Transpose(),D,LT

    def Cholesky_Decomposition(self):
        """"""
    def Eigenvalue_Decomposition(self):
        """Use Householder reflections"""
    def QS_Decomposition(self):
        """"""
    def SVD_Decomposition(self):
        """"""

    def Determinant(L,U):
        """finds the determinant of a matrix, by utilizing LU Decomposition"""
        det=1
        for idx in range(L.m):
            det*=L[idx][idx]*U[idx][idx]
        return det

    def Solve(L,U,b):
        """"solves the equation Ax=b by utilizing LU matrices"""

        def ForwardSubstitution(L,b):
            """solves by examining the lower triangular matrix"""
            y=[]
            for i in range(L.m):
                row=L.rows[i]
                current=b[i][0]
                for j in range(i):
                    current+=-row[j]*y[j]
                current/=row[i]
                y.append(current)
            return y

        def BackSubstitution(U,y):
            """solves by examining the upper triangular matrix"""
            x=[]
            for i in range(U.m):
                row=U.rows[U.m-i-1]
                current=y[-1-i]
                for j in range(len(x)):
                    current+=-row[U.n-j-1]*x[j]
                current/=row[-1-i]
                x.append(current)
            return x[::-1]

        return BackSubstitution(U,ForwardSubstitution(L,b))

    def Inverse(self):
        """finds the inverse of a matrix through Gauss-Jordan Method """

        def ForwardStep():
            """performs Gaussian Elimination to augment the Identity Matrix through forward substitution"""
            A=copy.deepcopy(self)
            B=Matrix.Identity(A.m,A.n)
            for i in range(A.m):
                for j in range(i):
                    c=-A.rows[i][j]/A.rows[j][j]
                    R=[c*entry for entry in A.rows[j]]
                    S=[c*entry for entry in B.rows[j]]
                    A[i]=list(map(operator.add,A[i],R))
                    B[i]=list(map(operator.add,B[i],S))
            return A,B

        def BackwardStep(A,B):
            """performs portion of Gauss-Jordan Elimination to augment the Identity Matrix through back substitution"""
            for i in range(A.m):
                idx=-i-1
                A_pivot=A.rows[idx][idx]
                if A_pivot!=1:
                    B.rows[idx]=[entry/A_pivot for entry in B.rows[idx]]
                    A.rows[idx]=[entry/A_pivot for entry in A.rows[idx]]

                for j in range(i):
                    c=-1*A.rows[idx][-j-1]/A.rows[-j-1][-j-1]
                    A.rows[idx][-j-1]=0
                    B.rows[idx]=list(map(operator.add,B.rows[idx],map(lambda x: c*x,B.rows[-j-1])))
            return B
        A,B=ForwardStep()
        return BackwardStep(A,B)

    #%% Read and Write Matrices

    def Write(self,filename):
        """writes the given matrix as a text file with the given name"""
        file=open(filename+".txt","w")
        file.write(str(self.rows))
        file.close()
        print ("Done.")

    def Read(filename):
        """reads a text file as a matrix with the given name"""
        file=open(filename+".txt","r")
        contents=eval(file.read())
        matrix=Matrix(len(contents),len(contents[0]))
        matrix.rows=[row for row in contents]
        print("Done.")
        return matrix

class Vector:

    #%% Initialization and Representation
    def __init__(self,row=None,length=None):
        """initializes vector from a given list; if no list is specified, length is a required argument to produce the null vector"""
        if row!=None:
            self.entries=row
            self.length=len(row)
        else:
            self.entries=[0 for i in range(length)]
            self.length=length

    def __repr__(self):
        """list representation of a given vector"""
        return str(self.entries)

    def __getitem__(self,idx):
        """returns specific element of vector at the given index"""
        return self.entries[idx]

    def __setitem__(self,idx,item):
        """sets vector element to item at the specificied index"""
        self.entries[idx]=item

    def __add__(self,other):
        """adds each element of vectors, producing a new vector"""
        return Vector([self.entries[i]+other.entries[i] for i in range(self.length)])

    def __sub__(self,other):
        """subtracts each element of vectors, producing a new vector"""
        return Vector([self.entries[i]-other.entries[i] for i in range(self.length)])

    def __isub__(self,other):
        return self-other

    def __mul__(self,other):
        """multiplies vector by scalar; does not obey the principle of associativity"""
        if type(other)==type(self):
            raise Exception("cannot multiply two vectors together; can only multiply by scalar")
        else:
            return Vector([other*self.entries[i] for i in range(self.length)])

    def __truediv__(self,other):
        """divides vector by scalar; does not obey the principle of associativity"""
        if type(other)==type(self):
            raise Exception("cannot divide two vectors together; can only divide by scalar")
        else:
            return Vector([self.entries[i]/other for i in range(self.length)])

    def dot(self,other):
        """returns the standard inner product between two vectors"""
        return sum([self.entries[i]*other.entries[i] for i in range(self.length)])

    def norm(self):
        """returns the standard Euclidean norm of a vector"""
        return math.sqrt(self.dot(self))

def Polynomial_Least_Squares(x,y,order=1):
    """performs least square fit in the form 'a0*x**0+a1*x**1+a2*x**2...' for a given order (degree)"""
    A=Matrix(len(x),order+1)
    A.rows=[[entry**n for n in range(order+1)] for entry in x]
    b=Matrix(len(y),1)
    b.rows=[[entry] for entry in y]
    AT=A.Transpose()
    c=(AT*A).Inverse()*AT*b
    y_predict=lambda x:sum([c.rows[n][0]*x**n for n in range(order+1)])
    y_average=sum(y)/len(y)
    RSS=0
    Syy=0
    for i in range(len(x)):
        RSS+=(y[i]-y_predict(x[i]))**2
        Syy+=(y_average-y[i])**2
    r=(1-RSS/Syy)
    return y_predict,r,c

def Exponential1_Least_Squares(x,y):
    """performs least square fit in the form 'y=a*e**x'"""
    Y=[math.log(entry) for entry in y]
    y_predict,r,c=Polynomial_Least_Squares(x,Y)
    c.rows[0][0]=math.exp(c.rows[0][0])
    y=lambda x: c.rows[0][0]*math.exp(c.rows[1][0]*x)
    return y,r,c

def Exponential2_Least_Squares(x,y):
    """performs least square fit in the form 'y=a*b**x'"""
    Y=[math.log(entry) for entry in y]
    y_predict,r,c=Polynomial_Least_Squares(x,Y)
    c.rows[0][0]=math.exp(c.rows[0][0])
    c.rows[1][0]=math.exp(c.rows[1][0])
    y=lambda x: c.rows[0][0]*math.pow(c.rows[1][0],x)
    return y,r,c

def Logarithmic_Least_Squares(x,y):
    """performs least square fit in the form 'y=a+b*ln(x)'"""
    X=[math.log(entry) for entry in x]
    y_predict,r,c=Polynomial_Least_Squares(X,y)
    y=lambda x: c.rows[0][0]+c.rows[1][0]*math.log(x)
    return y,r,c

def Power_Least_Squares(x,y):
    """performs least square fit in the form 'y=a*x**b'"""
    X=[math.log(entry) for entry in x]
    Y=[math.log(entry) for entry in y]
    y_predict,r,c=Polynomial_Least_Squares(X,Y)
    y=lambda x: math.exp(c.rows[0][0])*math.exp(x)*math.exp(c.rows[0][0])
    return y,r,c

def Trignometric_Least_Squares(x,y):
    """performs least square fit in the form 'y=a+b*cos(x)'"""
    X=[math.cos(entry) for entry in x]
    y_predict,r,c=Polynomial_Least_Squares(X,y)
    y=lambda x: c.rows[0][0]+c.rows[1][0]*math.cos(x)
    return y,r,c

def Saturation_Least_Squares(x,y):
    """performs least square fit in the form '1/y=b/a*1/x+1/a'"""
    Y=[1/entry for entry in y]
    X=[1/entry for entry in x]
    y_predict,r,c=Polynomial_Least_Squares(X,Y)
    A=1/c.rows[0][0]
    B=c.rows[1][0]/c.rows[0][0]
    c.rows[0][0],c.rows[1][0]=A,B
    y=lambda x: A+B*x
    return y,r,c


import uvicorn
from fastapi import FastAPI
from chatbot import ChatBot

app = FastAPI()

@app.get('/{message}')
async def chat(message: str):
    answer = chatbot.get_msg(message)
    return {'Bot:': answer}

if __name__ == '__main__':
    chatbot = ChatBot()
    uvicorn.run(app, host="0.0.0.0", port=8000)


from __future__ import print_function

# system imports
from datetime import datetime, timedelta
import urllib
try:
	# noinspection PyUnresolvedReferences
	from typing import Callable, Optional, List, Tuple  # pylint: disable=unused-import
except ImportError:
	pass

# enigma2 imports
from Components.Sources.List import List as ListSource
from Components.Sources.Boolean import Boolean
from Components.ActionMap import ActionMap, NumberActionMap
from Components.config import config, configfile
from Components.Label import Label
from Components.ScrollLabel import ScrollLabel
from Components.Slider import Slider
from Components.ServiceEventTracker import InfoBarBase
from Components.Input import Input
from Components.MenuList import MenuList
from Components.Pixmap import Pixmap
from Components.GUIComponent import GUIComponent
from Screens.InfoBarGenerics import InfoBarPlugins, InfoBarExtensions, \
	InfoBarNotifications, InfoBarAudioSelection, InfoBarSubtitleSupport, InfoBarSummary
from Screens.Screen import Screen
from Screens.MessageBox import MessageBox
from Screens.MinuteInput import MinuteInput
from Screens.ChoiceBox import ChoiceBox
from Screens.InputBox import InputBox
from Tools.LoadPixmap import LoadPixmap
from Tools.Directories import resolveFilename, SCOPE_CURRENT_SKIN, SCOPE_SKIN, SCOPE_SYSETC, SCOPE_CURRENT_PLUGIN

# enigma2 core imports
from enigma import eListboxPythonMultiContent, RT_HALIGN_LEFT, RT_HALIGN_RIGHT, RT_VALIGN_CENTER, \
	eLabel, eSize, ePoint
from enigma import eServiceReference
from skin import parseFont, parseColor

# plugin imports
from .layer import eTimer
from .common import NumberEnter
from .utils import trace, tdSec, secTd, syncTime, APIException, APIWrongPin, EPG, timeit
from .api.abstract_api import AbstractStream
from .loc import translate as _
from .common import ShowHideScreen, AutoAudioSelection, MainMenuScreen
from .standby import standbyNotifier
from .cache import LiveEpgWorker
from .lib.epg import EpgProgress
from .lib.tv import SortOrderSettings, Picon

SKIN_PATH = resolveFilename(SCOPE_SKIN, 'IPtvDream')
ENIGMA_CONF_PATH = resolveFilename(SCOPE_SYSETC, 'enigma2')
EPGMAP_PATH = resolveFilename(SCOPE_SYSETC, 'iptvdream')

rec_png = LoadPixmap(cached=True, path=SKIN_PATH + '/rec.png')
EPG_UPDATE_INTERVAL = 60  # Seconds, in channel list.
PROGRESS_TIMER = 1000*60  # Update progress in infobar.
PROGRESS_SIZE = 500
ARCHIVE_TIME_FIX = 5  # sec. When archive paused, we could miss some video

class IPtvDreamStreamPlayer(
		ShowHideScreen, AutoAudioSelection, MainMenuScreen,
		InfoBarBase, InfoBarPlugins, InfoBarExtensions,
		InfoBarNotifications, InfoBarAudioSelection, InfoBarSubtitleSupport):
	"""
	:type channels: IPtvDreamChannels
	:type db: AbstractStream
	"""

	ALLOW_SUSPEND = True

	def __init__(self, session, db):
		super(IPtvDreamStreamPlayer, self).__init__(session)
		InfoBarBase.__init__(self, steal_current_service=True)
		InfoBarExtensions.__init__(self)
		InfoBarPlugins.__init__(self)
		InfoBarNotifications.__init__(self)
		InfoBarAudioSelection.__init__(self)
		InfoBarSubtitleSupport.__init__(self)

		trace("start stream player")
		self.db = db
		from .manager import manager
		self.cfg = manager.getConfig(self.db.NAME)
		self.cid = None

		standbyNotifier.onStandbyChanged.append(self.standbyChanged)
		self.onClose.append(lambda: standbyNotifier.onStandbyChanged.remove(self.standbyChanged))

		self.channels = self.session.instantiateDialog(IPtvDreamChannels, self.db, self)
		self.onFirstExecBegin.append(self.start)

		self.setTitle(self.db.NAME)
		self["channelName"] = Label("")
		# Epg widgets
		self["currentName"] = Label("")
		self["nextName"] = Label("")
		self["currentTime"] = Label("")
		self["nextTime"] = Label("")
		self["currentDuration"] = Label("")
		self["nextDuration"] = Label("")
		self["progressBar"] = Slider(0, PROGRESS_SIZE)
		# Buttons
		self["key_red"] = Label(_("Archive"))
		self["key_green"] = Label(_("Settings"))
		self["key_yellow"] = Label(_("Audio"))
		self["key_blue"] = Label(_("Extensions"))

		# TODO: think more
		self["archiveDate"] = Label("")
		self["inArchive"] = Boolean(False)

		self["picon"] = Pixmap()
		self._picon = Picon(self["picon"])

		self["provider"] = Pixmap()
		icon_path = resolveFilename(SCOPE_CURRENT_PLUGIN, 'Extensions/IPtvDream/logo/%s.png' % self.db.NAME)
		self.onFirstExecBegin.insert(0, lambda: self["provider"].instance.setPixmap(LoadPixmap(icon_path)))

		# TODO: ActionMap add help.
		self["actions"] = ActionMap(["IPtvDreamInfobarActions", "ColorActions", "OkCancelActions"], {
				"cancel": self.confirmExit,
				"closePlugin": self.exit,
				"openVideos": self.openVod,
				"red": self.showEpg,
				"green": self.openSettings,
				"openServiceList": self.showList,
				"zapUp": self.previousChannel,
				"zapDown": self.nextChannel,
				"historyNext": self.historyNext,
				"historyBack": self.historyBack,
				"showEPGList": self.showEpg,
			})

		self["live_actions"] = ActionMap(["IPtvDreamLiveActions"], {
				"zapUp": self.previousChannel,
				"zapDown": self.nextChannel,
			}, -1)

		self["archive_actions"] = ActionMap(["IPtvDreamArchiveActions", "NumberActions"], {
				"exitArchive": self.exitArchive,
				"playpause": self.playPauseArchive,
				"play": lambda: self.playPauseArchive(True, False),
				"pause": lambda: self.playPauseArchive(False, True),
				"seekForward": self.archiveSeekFwd,
				"seekBackward": self.archiveSeekRwd,
				"1": lambda: self.jump(1),
				"3": lambda: self.jump(3),
				"4": lambda: self.jump(4),
				"6": lambda: self.jump(6),
				"7": lambda: self.jump(7),
				"9": lambda: self.jump(9),
			}, -1)

		self["number_actions"] = NumberActionMap(["NumberActions"], {
				"1": self.keyNumberGlobal,
				"2": self.keyNumberGlobal,
				"3": self.keyNumberGlobal,
				"4": self.keyNumberGlobal,
				"5": self.keyNumberGlobal,
				"6": self.keyNumberGlobal,
				"7": self.keyNumberGlobal,
				"8": self.keyNumberGlobal,
				"9": self.keyNumberGlobal,
				"0": self.keyNumberGlobal,
			})

		self.currentEpg = None
		self.epgTimer = eTimer()
		self.epgProgressTimer = eTimer()
		self.epgTimer.callback.append(self.epgEvent)
		self.epgProgressTimer.callback.append(self.epgUpdateProgress)

		self.archive_pause = None
		self.shift = 0

	# Init and destroy

	def start(self):
		trace("player start")
		self.showList()

	def exit(self, ret=None):
		self.channels.saveQuery()
		self.session.deleteDialog(self.channels)
		self.close(ret)

	def confirmExit(self):
		def cb(ret):
			if ret:
				self.exit()
		self.session.openWithCallback(cb, MessageBox, _("Exit plugin?"), MessageBox.TYPE_YESNO)

	# Play

	def play(self, cid):
		trace("play cid =", cid)

		self.cid = cid
		self.session.nav.stopService()
		if cid is None:
			return

		if self.db.channels[cid].is_protected:
			trace("protected by api")
			code = self.cfg.parental_code.value
			if code:
				trace("using saved code")
				self.getUrl(code)
			else:
				self.enterPin()
		else:
			self.getUrl(None)

	def enterPin(self):
		self.session.openWithCallback(
				self.getUrl, InputBox, title=_("Enter protect password"),
				windowTitle=_("Channel Locked"), type=Input.PIN)

	def getUrl(self, pin):
		try:
			url = self.db.getStreamUrl(self.cid, pin, self.time())
		except APIWrongPin:
			self.session.openWithCallback(
					lambda ret: self.enterPin(), MessageBox, _("Wrong pin!"),
					MessageBox.TYPE_ERROR, timeout=10, enable_input=False)
			return
		except APIException as e:
			self.showError(_("Error while getting stream url:") + str(e))
			self.updateLabels()
			return

		self.playUrl(url)

	# Player

	def playUrl(self, url):
		cid = self.cid
		if self.cfg.use_hlsgw.value:
			url = "http://localhost:7001/url=%s" % urllib.quote(url)
		trace("play", url)
		ref = eServiceReference(int(self.cfg.playerid.value), 0, url)
		ref.setName(self.db.channels[cid].name)
		ref.setData(1, 1)
		self.session.nav.playService(ref)
		self.updateLabels()

	def updateLabels(self):
		cid = self.cid
		self["channelName"].setText("%d. %s" % (self.db.channels[cid].number, self.db.channels[cid].name))
		self._picon.setIcon(self.db.getPiconUrl(cid))
		self.epgEvent()

	def standbyChanged(self, sleep):
		if sleep:
			trace("entered standby")
			if self.shift and not self.archive_pause:
				self.playPauseArchive()
			else:
				self.session.nav.stopService()
		else:
			trace("exited standby")
			if self.shift:
				self.playPauseArchive()
			else:
				self.play(self.cid)

	# Archive

	def setArchiveShift(self, time_shift):
		self.shift = time_shift
		if time_shift:
			self.archive_pause = None
			self["live_actions"].setEnabled(False)
			self["number_actions"].setEnabled(False)
			self["archive_actions"].setEnabled(True)
			self["inArchive"].setBoolean(True)
		else:
			self["archive_actions"].setEnabled(False)
			self["live_actions"].setEnabled(True)
			self["number_actions"].setEnabled(True)
			self["inArchive"].setBoolean(False)

	def time(self):
		if self.shift:
			return syncTime() + secTd(self.shift)
		return None

	def archiveSeekFwd(self):
		self.session.openWithCallback(self.fwdJumpMinutes, MinuteInput)

	def archiveSeekRwd(self):
		self.session.openWithCallback(self.rwdJumpMinutes, MinuteInput)

	def fwdJumpMinutes(self, minutes):
		return self.fwdJump(minutes * 60)

	def rwdJumpMinutes(self, minutes):
		return self.rwdJump(minutes * 60)

	def fwdJump(self, seconds):
		trace("fwdSeek", seconds)
		self.shift += seconds
		if self.shift > 0:
			self.setArchiveShift(0)
		self.play(self.cid)

	def rwdJump(self, seconds):
		trace("rwdSeek", seconds)
		self.shift -= seconds
		self.play(self.cid)

	def jump(self, n):
		try:
			t13 = config.seek.selfdefined_13.value
			t46 = config.seek.selfdefined_46.value
			t79 = config.seek.selfdefined_79.value
		except AttributeError:
			t13 = 15
			t46 = 60
			t79 = 300

		t = {1: -t13, 3: t13, 4: -t46, 6: t46, 7: -t79, 9: t79}[n]
		if t < 0:
			self.rwdJump(abs(t))
		else:
			self.fwdJump(abs(t))

	def playPauseArchive(self, play=True, pause=True):
		if self.archive_pause and play:
			# do unPause
			self.shift -= tdSec(syncTime()-self.archive_pause)-ARCHIVE_TIME_FIX
			self.archive_pause = None
			self.play(self.cid)
			self.unlockShow()
		elif pause:
			# do pause
			self.archive_pause = syncTime()
			# try to pause and freeze the picture, otherwise stop and show black screen
			service = self.session.nav.getCurrentService()
			pauseable = service and service.pause()
			if pauseable:
				pauseable.pause()
			else:
				self.session.nav.stopService()
			self.lockShow()
			# freeze epg labels
			self.epgTimer.stop()
			self.epgProgressTimer.stop()

	def exitArchive(self):
		self.setArchiveShift(0)
		self.play(self.cid)

	# EPG

	def epgEvent(self):
		# first stop timers
		self.epgTimer.stop()
		self.epgProgressTimer.stop()
		cid = self.cid
		time = syncTime() + secTd(self.shift)

		def setEpgCurrent():
			curr = self.db.channels[cid].epgCurrent(time)
			if not curr:
				return False

			self.currentEpg = curr
			self["currentName"].setText(curr.name)
			self["currentTime"].setText(curr.begin.strftime("%H:%M"))
			self["nextTime"].setText(curr.end.strftime("%H:%M"))
			self.epgTimer.start(curr.timeLeftMilliseconds(time) + 1000)
			self["currentDuration"].setText("+%d min" % (curr.timeLeft(time) / 60))
			self["progressBar"].setValue(curr.percent(time, PROGRESS_SIZE))
			self.epgProgressTimer.start(PROGRESS_TIMER)
			if self.shift:
				self["archiveDate"].setText(curr.begin.strftime("%d.%m"))
				self["archiveDate"].show()
			else:
				self["archiveDate"].hide()
			return True

		if not setEpgCurrent():
			try:
				self.db.loadDayEpg(cid, time)
			except APIException as e:
				trace("ERROR load epg failed! cid =", cid, bool(self.shift), e)
			if not setEpgCurrent():
				self["currentName"].setText('')
				self["currentTime"].setText('')
				self["nextTime"].setText('')
				self["currentDuration"].setText('')
				self["progressBar"].setValue(0)

		def setEpgNext():
			e = self.db.channels[cid].epgNext(time)
			if not e:
				return False
			self['nextName'].setText(e.name)
			self['nextDuration'].setText("%d min" % (e.duration() / 60))
			return True

		if not setEpgNext():
			try:
				self.db.loadDayEpg(cid, time)
			except APIException:
				trace("load epg next failed!")
			if not setEpgNext():
				self["nextName"].setText('')
				self["nextDuration"].setText('')

		self.serviceStarted()

	def epgUpdateProgress(self):
		time = syncTime() + secTd(self.shift)
		self["currentDuration"].setText("+%d min" % (self.currentEpg.timeLeft(time) / 60))
		self["progressBar"].setValue(self.currentEpg.percent(time, PROGRESS_SIZE))
		self.epgProgressTimer.start(PROGRESS_TIMER)

	# Dialogs

	def showEpg(self):
		self.session.openWithCallback(self.programSelected, IPtvDreamEpg, self.db, self.cid, self.shift)

	def programSelected(self, cid=None, time=None):
		if cid is not None and time is not None:
			self.setArchiveShift(tdSec(time-syncTime()))  # shift < 0
			self.play(cid)

	def showList(self):
		self.session.execDialog(self.channels)
		self.channels.callback = self.listClosed

	def listClosed(self, cid=None, time=None, action=None):
		if action is None:
			self.channelSelected(cid, time)
		else:
			self.exit(action)

	def channelSelected(self, cid, time=None):
		if cid is None:
			return
		if time:
			self.programSelected(cid, time)
		elif cid != self.cid:
			self.switchChannel(cid)

	def openSettings(self):
		self.exit('settings')

	def openVod(self):
		self.exit('vod')

	def createSummary(self):
		return InfoBarSummary

	# Channels

	def switchChannel(self, cid):
		# FIXME: zapping breaks archive shift
		trace("switch channel", self.cid)
		self.setArchiveShift(0)
		self.play(cid)

	def nextChannel(self):
		cid = self.channels.nextChannel()
		self.switchChannel(cid)

	def previousChannel(self):
		cid = self.channels.prevChannel()
		self.switchChannel(cid)

	def historyNext(self):
		if self.channels.historyNext():
			cid = self.channels.getCurrent()
			self.switchChannel(cid)

	def historyBack(self):
		if self.channels.historyPrev():
			cid = self.channels.getCurrent()
			self.switchChannel(cid)

	def keyNumberGlobal(self, number):
		self.session.openWithCallback(self.numberEntered, NumberEnter, number)

	def numberEntered(self, num=None):
		trace("numberEntered", num)
		if num is not None and self.channels.goToNumber(num):
			cid = self.channels.getCurrent()
			self.switchChannel(cid)

	# Errors

	def showError(self, msg):
		self.session.open(MessageBox, msg, MessageBox.TYPE_ERROR, 5)

class ChannelList(MenuList):
	def __init__(self):
		MenuList.__init__(self, [], content=eListboxPythonMultiContent, enableWrapAround=True)
		self.list = []
		self.index = {}
		self.col = {}
		self.fontCalc = []

		self.pixmapProgressBar = None
		self.pixmapArchive = None
		self.listItemHeight = 28
		self.listItemWidth = 0
		self.l.setFont(0, parseFont("Regular;22", ((1, 1), (1, 1))))
		self.l.setFont(1, parseFont("Regular;18", ((1, 1), (1, 1))))
		self.l.setFont(2, parseFont("Regular;20", ((1, 1), (1, 1))))
		self.showEpgProgress = config.usage.show_event_progress_in_servicelist.value
		self.num = 0
		self.highlight_cid = 0

		for x in [
				"colorEventProgressbar", "colorEventProgressbarSelected",
				"colorEventProgressbarBorder", "colorEventProgressbarBorderSelected",
				"colorServiceDescription", "colorServiceDescriptionSelected",
				"colorServicePlaying", "colorServicePlayingSelected"]:
			self.col[x] = None

	def postWidgetCreate(self, instance):
		trace("postWidgetCreate")
		MenuList.postWidgetCreate(self, instance)
		# Create eLabel instances, because we can't access eTextPara directly
		self.fontCalc = [eLabel(self.instance), eLabel(self.instance), eLabel(self.instance)]
		self.fontCalc[0].setFont(parseFont("Regular;22", ((1, 1), (1, 1))))
		self.fontCalc[1].setFont(parseFont("Regular;18", ((1, 1), (1, 1))))
		self.fontCalc[2].setFont(parseFont("Regular;20", ((1, 1), (1, 1))))

	def applySkin(self, desktop, parent):
		scale = ((1, 1), (1, 1))
		attribs = []
		if self.skinAttributes is not None:
			for (attrib, value) in self.skinAttributes:
				if attrib in (
						"colorEventProgressbar", "colorEventProgressbarSelected",
						"colorEventProgressbarBorder", "colorEventProgressbarBorderSelected",
						"colorServiceDescription", "colorServiceDescriptionSelected",
						"colorServicePlaying", "colorServicePlayingSelected"):
					self.col[attrib] = parseColor(value).argb()
				elif attrib == "picServiceEventProgressbar":
					pic = LoadPixmap(resolveFilename(SCOPE_CURRENT_SKIN, value))
					if pic:
						self.pixmapProgressBar = pic
				elif attrib == "picServiceArchive":
					pic = LoadPixmap(resolveFilename(SCOPE_CURRENT_SKIN, value))
					if pic:
						self.pixmapArchive = pic
				elif attrib == "serviceItemHeight":
					self.listItemHeight = int(value)
				elif attrib == "serviceNameFont":
					self.l.setFont(0, parseFont(value, scale))
					self.fontCalc[0].setFont(parseFont(value, scale))
				elif attrib == "serviceInfoFont":
					self.l.setFont(1, parseFont(value, scale))
					self.fontCalc[1].setFont(parseFont(value, scale))
				elif attrib == "serviceNumberFont":
					self.l.setFont(2, parseFont(value, scale))
					self.fontCalc[2].setFont(parseFont(value, scale))
				else:
					attribs.append((attrib, value))

		self.skinAttributes = attribs
		res = GUIComponent.applySkin(self, desktop, parent)

		self.l.setItemHeight(self.listItemHeight)
		self.listItemWidth = self.instance.size().width()
		for x in self.fontCalc:
			# resize and move away
			x.resize(eSize(self.listItemWidth, self.listItemHeight))
			x.move(ePoint(int(self.instance.size().width()+10), int(self.instance.size().height()+10)))
			x.setNoWrap(1)
		return res

	def setEnumerated(self, enumerated):
		if enumerated:
			self.num = 1
		else:
			self.num = 0

	def setChannelsList(self, channels):
		self.setList(map(self.buildChannelEntry, channels))
		# Create map from channel id to its index in list
		self.index = dict((entry[0][0].cid, i) for (i, entry) in enumerate(self.list))
		if self.num:
			self.num = 1

	def updateChannel(self, cid, channel):
		try:
			index = self.index[cid]
		except KeyError:
			return
		self.list[index] = self.buildChannelEntry(channel)
		self.l.invalidateEntry(index)

	def updateChannelsProgress(self):
		if self.showEpgProgress:
			self.setList([self.buildChannelEntry(item[0]) for item in self.list])

	def moveEntryUp(self):
		index = self.getSelectedIndex()
		if index == 0:
			return
		self.list[index - 1], self.list[index] = self.list[index], self.list[index - 1]
		self.l.invalidateEntry(index - 1)
		self._updateIndexMap(index - 1)
		self.l.invalidateEntry(index)
		self._updateIndexMap(index)
		self.up()

	def moveEntryDown(self):
		index = self.getSelectedIndex()
		if index + 1 == len(self.list):
			return
		self.list[index], self.list[index + 1] = self.list[index + 1], self.list[index]
		self.l.invalidateEntry(index)
		self._updateIndexMap(index)
		self.l.invalidateEntry(index + 1)
		self._updateIndexMap(index + 1)
		self.down()

	def _updateIndexMap(self, i):
		cid = self.list[i][0][0].cid
		self.index[cid] = i

	def highlight(self, cid):
		self.highlight_cid = cid

	def setGroupList(self, groups):
		self.setList(map(self.buildGroupEntry, groups))
		self.index = {}

	def calculateWidth(self, text, font):
		self.fontCalc[font].setText(text)
		return int(round(self.fontCalc[font].calculateSize().width()*1.1))

	def buildGroupEntry(self, group):
		return [
			group,
			(eListboxPythonMultiContent.TYPE_TEXT, 0, 0, self.listItemWidth, self.listItemHeight,
				0, RT_HALIGN_LEFT | RT_VALIGN_CENTER, group.title)
		]

	def buildChannelEntry(self, entry):
		"""
		:type entry: Tuple[utils.Channel, utils.EPG]
		"""
		c, e = entry
		defaultFlag = RT_HALIGN_LEFT | RT_VALIGN_CENTER
		# Filling from left to right

		lst = [entry]
		xoffset = 1

		if self.num:
			xoffset += 55
			text = str(c.number)
			lst.append(
				(eListboxPythonMultiContent.TYPE_TEXT, 0, 0, xoffset-5, self.listItemHeight,
					2, RT_HALIGN_RIGHT | RT_VALIGN_CENTER, text))

		if self.pixmapArchive:
			width = self.pixmapArchive.size().width()
			height = self.pixmapArchive.size().height()
			if c.has_archive:
				lst.append(
					(eListboxPythonMultiContent.TYPE_PIXMAP_ALPHATEST,
						xoffset, (self.listItemHeight - height) / 2, width, height, self.pixmapArchive))
			xoffset += width+5

		if self.showEpgProgress:
			width = 52
			height = 6
			if e:
				percent = e.percent(syncTime(), 100)
				lst.extend([
					(eListboxPythonMultiContent.TYPE_PROGRESS,
						xoffset+1, (self.listItemHeight-height)/2, width, height,
						percent, 0, self.col['colorEventProgressbar'], self.col['colorEventProgressbarSelected']),
					(eListboxPythonMultiContent.TYPE_PROGRESS,
						xoffset, (self.listItemHeight-height)/2 - 1, width+2, height+2,
						0, 1, self.col['colorEventProgressbarBorder'], self.col['colorEventProgressbarBorderSelected'])
				])
			xoffset += width+7

		text = str(c.name)
		width = self.calculateWidth(text, 0)
		if c.cid != self.highlight_cid:
			lst.append(
				(eListboxPythonMultiContent.TYPE_TEXT, xoffset, 0, width, self.listItemHeight,
					0, defaultFlag, text))
		else:
			lst.append(
				(eListboxPythonMultiContent.TYPE_TEXT, xoffset, 0, width, self.listItemHeight,
					0, defaultFlag, text, self.col['colorServicePlaying'], self.col['colorServicePlayingSelected']))
		xoffset += width+10

		if e:
			text = '(%s)' % e.name
			lst.append(
				(eListboxPythonMultiContent.TYPE_TEXT, xoffset, 0, self.listItemWidth, self.listItemHeight,
					1, defaultFlag, text,
					self.col['colorServiceDescription'], self.col['colorServiceDescriptionSelected']))

		return lst

class History(object):
	def __init__(self, size):
		self._size = size
		self._history = []  # type: List[HistoryEntry]
		self._index = -1

	def isEmpty(self):
		return len(self._history) == 0

	def append(self, val):
		while len(self._history) > self._index + 1:
			self._history.pop()
		self._history.append(val)
		if len(self._history) > self._size:
			self._history.pop(0)
		else:
			self._index += 1

	def historyPrev(self):
		if self._index < 1:
			return None
		else:
			self._index -= 1
			return self._history[self._index]

	def historyNext(self):
		if self._index+1 == len(self._history):
			return None
		else:
			self._index += 1
			return self._history[self._index]

	def now(self):
		if len(self._history):
			return self._history[self._index]
		else:
			return None

	def __repr__(self):
		return map(str, self._history)

class HistoryEntry(object):
	def __init__(self, mode, gid, gr_idx, cid, ch_idx):
		self.mode = mode
		self.gid, self.gr_idx = gid, gr_idx
		self.cid, self.ch_idx = cid, ch_idx

	def copy(self):
		return HistoryEntry(self.mode, self.gid, self.gr_idx, self.cid, self.ch_idx)

	def fromStr(self, s):
		self.mode, self.gid, self.gr_idx, self.cid, self.ch_idx = map(int, s.split(","))

	def toStr(self):
		return ",".join(map(str, (self.mode, self.gid, self.gr_idx, self.cid, self.ch_idx)))

	def makeTuple(self):
		return self.mode, self.gid, self.gr_idx, self.cid, self.ch_idx

	def __repr__(self):
		return "HistoryEntry(%d, (%d, %d), (%d, %d))" % (self.mode, self.gid, self.gr_idx, self.cid, self.ch_idx)

class VerticalLayoutPart(object):
	"""
	Grow program name label while title does not fit, and shrink program description accordingly
	"""

	def __init__(self, screen, widgetsToMove):
		self._name_height = 0
		self._desc_top = 0
		self._desc_height = 0
		self._widgets_top = []
		self.name = screen["epgName"]
		self.desc = screen["epgDescription"]
		self.widgets = widgetsToMove
		screen.onLayoutFinish.append(self.initLayout)

	def initLayout(self):
		self._name_height = self.name.instance.size().height()
		self._desc_top = self.desc.instance.position().y()
		self._desc_height = self.desc.instance.size().height()
		self._widgets_top = [w.instance.position().y() for w in self.widgets]

	def updateLayout(self):
		height = min(self.name.instance.calculateSize().height(), self._name_height * 3)
		dh = height - self._name_height
		self.name.instance.resize(eSize(
			self.name.instance.size().width(), height))
		self.desc.instance.move(ePoint(
			self.desc.instance.position().x(), self._desc_top + dh))
		self.desc.instance.resize(eSize(
			self.desc.instance.size().width(), self._desc_height - dh))
		for w, y in zip(self.widgets, self._widgets_top):
			w.instance.move(ePoint(w.instance.position().x(), y + dh))

class IPtvDreamChannels(Screen):
	"""
	:type db: AbstractStream
	:type saved_state: Optional[HistoryEntry]
	"""

	GROUPS, ALL, GROUP, FAV = range(4)

	def __init__(self, session, db, player_ref):
		Screen.__init__(self, session)

		trace("channels init")
		self.history = History(10)
		self.db = db  # type: AbstractStream
		self.player_ref = player_ref
		from .manager import manager
		self.cfg = manager.getConfig(self.db.NAME)

		self["caption"] = Label(_("Channel Selection"))
		self["key_red"] = Label(_("All"))
		self["key_green"] = Label(_("Groups"))
		self["key_yellow"] = Label(_("Add"))
		self["key_blue"] = Label(_("Favourites"))

		self.list = self["list"] = ChannelList()

		self["channelName"] = Label()
		self["epgName"] = Label()
		self["epgTime"] = Label()
		self["epgDescription"] = Label()
		self["epgNextTime"] = Label()
		self["epgNextName"] = Label()
		self["epgNextDescription"] = Label()

		self["epgProgress"] = Slider(0, 100)
		self["progress"] = self._progress = EpgProgress()
		self._progress.onChanged.append(lambda value: self["epgProgress"].setValue(int(100 * value)))

		# auto resize some widgets
		self._info_part = VerticalLayoutPart(self, (self["epgTime"], self["epgProgress"]))

		self._worker = LiveEpgWorker(db)
		self._worker.onUpdate.append(self.updatePrograms)
		self.onClose.append(self._worker.destroy)

		def workerStandby(sleep):
			if sleep:
				self._worker.stop()
			else:
				self._worker.update()
		standbyNotifier.onStandbyChanged.append(workerStandby)
		self.onClose.append(lambda: standbyNotifier.onStandbyChanged.remove(workerStandby))

		self._progressTimer = eTimer()
		self._progressTimer.callback.append(self.updateProgramsProgress)
		self._progressTimer.start(1000 * 60 * 5)  # every 5 min

		self["actions"] = ActionMap(
			["OkCancelActions", "IPtvDreamChannelListActions"], {
				"cancel": self.exit,
				"ok": self.ok,
				"showAll": self.showAll,
				"showGroups": self.showGroups,
				"addFavourites": self.addRemoveFavourites,
				"showFavourites": self.showFavourites,
				"contextMenu": self.showMenu,
				"showEPGList": self.showEpgList,
				"nextBouquet": self.nextGroup,
				"prevBouquet": self.prevGroup
			}, -1)

		self.list.onSelectionChanged.append(self.selectionChanged)
		self.onClose.append(lambda: self.list.onSelectionChanged.remove(self.selectionChanged))

		start_mode = manager.getStartMode()
		if start_mode == self.FAV and not self.db.selectFavourites():
			start_mode = self.GROUPS

		self.order_config = SortOrderSettings()
		self.mode = start_mode
		self.gid = None
		self.saved_state = None

		self.edit_mode = False
		self.marked = False  # Whether current entry is marked (in edit mode)
		self["move_actions"] = ActionMap(
			["OkCancelActions", "DirectionActions", "IPtvDreamChannelListActions"], {
				"cancel": self.finishEditing,
				"ok": self.toggleMarkForMoving,
				"up": self.moveUp,
				"down": self.moveDown,
				"contextMenu": self.showMenu,
				"addFavourites": self.addRemoveFavourites,
			}, -1)
		self["move_actions"].setEnabled(False)

		self["packetExpire"] = Label()
		if self.db.packet_expire is not None:
			self["packetExpire"].setText(_("Payment expires: ") + self.db.packet_expire.strftime("%d.%m.%Y"))

		self.onLayoutFinish.append(self.fillList)
		self.onShown.append(self.start)

	def start(self):
		trace("Channels list shown")
		if not self.history.isEmpty():
			self.saved_state = self.history.now().copy()
		else:
			self.saved_state = None

	def saveQuery(self):
		trace("save query")
		h = self.history.now()
		if h is not None:
			self.cfg.last_played.value = self.history.now().toStr()
		else:
			self.cfg.last_played.value = ""
		self.cfg.last_played.save()
		configfile.save()

	def createHistoryEntry(self):
		entry = self.getSelected()
		assert entry is not None
		return HistoryEntry(self.mode, self.gid, 0, entry.cid, self.list.getSelectedIndex())

	def recoverState(self, state):
		"""
		:param HistoryEntry state:
		"""
		self.mode, self.gid = state.mode, state.gid
		if self.mode == self.GROUPS:
			self.fillGroupsList()
			self.list.moveToIndex(self.saved_state.gr_idx)
		else:
			self.fillList()
			self.list.moveToIndex(self.saved_state.ch_idx)

	def exit(self):
		if self.saved_state is not None:
			self.recoverState(self.saved_state)
		self.close(None)

	def ok(self, time=None):
		entry = self.getSelected()
		if entry is None:
			return
		if self.mode == self.GROUPS:
			self.mode = self.GROUP
			self.gid = entry.gid
			self.fillList()
			self.list.moveToIndex(0)
		else:
			idx = self.list.getSelectedIndex()
			cid = entry.cid
			self.history.append(HistoryEntry(self.mode, self.gid, 0, cid, idx))
			self.close(cid, time)

	@timeit
	def updateProgramsProgress(self):
		if self.mode != self.GROUPS:
			print("Updating list progressbars")
			self.list.updateChannelsProgress()

	def updatePrograms(self, data):
		# type: (List[Tuple[int, EPG]]) -> None
		if self.mode == self.GROUPS:
			return
		for (cid, epg) in data:
			if epg:
				try:
					channel = self.db.channels[cid]
				except KeyError:
					continue
				self.list.updateChannel(cid, (channel, epg))

	@timeit
	def setChannels(self, channels):
		self.list.setChannelsList((c, self._worker.get(c.cid)) for c in channels)

	def fillGroupsList(self):
		self.setTitle(" / ".join([self.db.NAME, _("Groups")]))
		groups = self.db.selectGroups()
		self.list.setGroupList(groups)
		if self.gid is not None:
			for idx, g in enumerate(groups):
				if g.gid == self.gid:
					self.list.moveToIndex(idx)
					break
			else:
				self.gid = None

	def fillList(self):
		title = [self.db.NAME]
		# Highlight is used for edit mode
		# self.list.highlight(self.player_ref.cid)
		order = self.order_config.getValue()

		if self.mode == self.GROUPS:
			self.fillGroupsList()
			title.append(_("Groups"))
		elif self.mode == self.GROUP:
			self.setChannels(self.db.selectChannels(self.gid, sort_key=order))
			title.append(self.db.groups[self.gid].title)
		elif self.mode == self.ALL:
			self.setChannels(self.db.selectAll(sort_key=order))
			title.append(_("All channels"))
		elif self.mode == self.FAV:
			self.setChannels(self.db.selectFavourites())
			title.append(_("Favourites"))

		self.setTitle(" / ".join(title))
		if self.mode == self.FAV:
			self["key_yellow"].setText(_("Remove"))
		else:
			self["key_yellow"].setText(_("Add"))

	def selectionChanged(self):
		channel = self.getSelected()
		trace("selection =", channel)

		if self.mode == self.GROUPS or channel is None:
			self["channelName"].setText("")
			self.hideEpgLabels()
			self.hideEpgNextLabels()
		else:
			self["channelName"].setText(channel.name)
			self["channelName"].show()
			curr = self._worker.get(channel.cid)
			if curr:
				self["epgTime"].setText("%s - %s" % (curr.begin.strftime("%H:%M"), curr.end.strftime("%H:%M")))
				self["epgName"].setText(curr.name)
				self["epgName"].show()
				self["epgTime"].show()
				self["epgDescription"].setText(curr.description)
				self["epgDescription"].show()
				self._progress.setEpg(curr)
				self["epgProgress"].show()
				self._info_part.updateLayout()
			else:
				self.hideEpgLabels()
			curr = self._worker.getNext(channel.cid)
			if curr:
				self["epgNextTime"].setText("%s - %s" % (curr.begin.strftime("%H:%M"), curr.end.strftime("%H:%M")))
				self["epgNextName"].setText(curr.name)
				self["epgNextDescription"].setText(curr.description)
				self["epgNextName"].show()
				self["epgNextTime"].show()
				self["epgNextDescription"].show()
			else:
				self.hideEpgNextLabels()

	def hideEpgLabels(self):
		self["epgName"].hide()
		self["epgTime"].hide()
		self["epgProgress"].hide()
		self["epgDescription"].hide()

	def hideEpgNextLabels(self):
		self["epgNextName"].hide()
		self["epgNextTime"].hide()
		self["epgNextDescription"].hide()

	def showGroups(self):
		self.mode = self.GROUPS
		self.fillList()

	def showAll(self):
		self.mode = self.ALL
		self.fillList()

	def showFavourites(self):
		self.mode = self.FAV
		self.fillList()

	def npGroup(self, diff):
		if self.mode == self.GROUP:
			groups = self.db.selectGroups()
			for idx, g in enumerate(groups):
				if g.gid == self.gid:
					self.gid = groups[(idx + diff) % len(groups)].gid
					self.fillList()
					break

	def nextGroup(self):
		self.npGroup(1)

	def prevGroup(self):
		self.npGroup(-1)

	def addRemoveFavourites(self):
		channel = self.getSelected()
		if not channel:
			return
		if self.mode == self.FAV:
			self.db.rmFav(channel.cid)
			self.showFavourites()
		elif self.mode != self.GROUPS:
			self.db.addFav(channel.cid)

	def showMenu(self):
		actions = []
		current = self.getSelected()
		if self.mode in [self.ALL, self.GROUP]:
			actions += [
				(_("Sort by number"), self.sortByNumber),
				(_("Sort by name"), self.sortByName),
			]
			if current:
				actions += [
					(_('Add "%s" to favourites') % current.name, self.addRemoveFavourites),
				]
				if current.has_archive:
					actions += [(_('Open archive for "%s"') % current.name, self.showEpgList)]
		if self.mode == self.FAV:
			if current:
				actions += [
					(_('Remove "%s" from favourites') % current.name, self.addRemoveFavourites),
				]
				if current.has_archive:
					actions += [(_('Open archive for "%s"') % current.name, self.showEpgList)]
				if not self.edit_mode:
					actions += [(_("Enter edit mode"), self.confirmStartEditing)]
				else:
					actions += [(_("Exit edit mode"), self.notifyFinishEditing)]
		actions += [(_("Open plugin settings"), self.openSettings)]
		if self.db.AUTH_TYPE:
			actions += [(_("Clear login data and exit"), self.clearLogin)]

		def cb(entry=None):
			if entry is not None:
				func = entry[1]
				func()
		if actions:
			self.session.openWithCallback(cb, ChoiceBox, _("Context menu"), actions)

	def sortBy(self, what):
		channel = self.getSelected()
		if channel is None:
			return
		self.order_config.setValue(what)
		self.fillList()
		index = self.findChannelIndex(channel.cid)
		if index:
			self.list.moveToIndex(index)

	def sortByNumber(self):
		self.sortBy('number')

	def sortByName(self):
		self.sortBy('name')

	def confirmStartEditing(self):
		def cb(ret):
			if ret:
				self.startEditing()
		message = _(
			"In the editing mode you can reorder your favourites list. Quick help:\n"
			"- Select channel that you want to put to a new position.\n"
			"- Press OK to start moving the channel around with arrow buttons.\n"
			"- Press OK again to fix the position of the channel.\n"
			"- Press EXIT when done.\n"
			"Start editing mode?"
		)
		self.session.openWithCallback(cb, MessageBox, _(message), MessageBox.TYPE_YESNO)

	def startEditing(self):
		"""Start reordering of channels in the favourites list"""
		self.edit_mode = True
		self["actions"].setEnabled(False)
		self["move_actions"].setEnabled(True)

	def toggleMarkForMoving(self):
		if self.marked:
			self.marked = False
			self.list.highlight(None)
		else:
			self.marked = True
			channel = self.getSelected()
			if channel:
				self.list.highlight(channel.cid)

	def moveUp(self):
		if self.marked:
			self.list.moveEntryUp()
		else:
			self.list.up()

	def moveDown(self):
		if self.marked:
			self.list.moveEntryDown()
		else:
			self.list.down()

	def notifyFinishEditing(self):
		self.session.openWithCallback(
			lambda ret: self.finishEditing(),
			MessageBox, _("Exiting edit mode"), MessageBox.TYPE_INFO, timeout=5)

	def finishEditing(self):
		self.edit_mode = False
		self["actions"].setEnabled(True)
		self["move_actions"].setEnabled(False)
		try:
			self.db.setFavourites([entry[0][0].cid for entry in self.list.list])
		except APIException as ex:
			self.session.open(
				MessageBox, "%s\n%s" % (_("Failed to save favourites list."), str(ex)), MessageBox.TYPE_ERROR)

	def showEpgList(self):
		channel = self.getSelected()
		if channel and self.modeChannels():
			self.session.openWithCallback(self.showEpgCB, IPtvDreamEpg, self.db, channel.cid, 0)

	def showEpgCB(self, cid=None, time=None):
		trace("selected program", cid, time)
		if time is not None:
			self.ok(time)

	def getSelected(self):
		entry = self.list.getCurrent()
		trace("getSelected", entry and entry[0])
		if entry:
			if self.mode == self.GROUPS:
				return entry[0]
			else:
				return entry[0][0]
		return None

	def getCurrent(self):
		return self.history.now().cid

	def modeChannels(self):
		return self.mode != self.GROUPS

	def nextChannel(self):
		self.list.down()
		self.history.append(self.createHistoryEntry())
		return self.getCurrent()

	def prevChannel(self):
		self.list.up()
		self.history.append(self.createHistoryEntry())
		return self.getCurrent()

	def historyNext(self):
		h = self.history.historyNext()
		if h is not None:
			self.recoverState(h)
			return self.getCurrent()
		else:
			return None

	def historyPrev(self):
		h = self.history.historyPrev()
		if h is not None:
			self.recoverState(h)
			return self.getCurrent()
		else:
			return None

	def findChannelIndex(self, cid):
		for i, entry in enumerate(self.list.list):
			channel = entry[0][0]
			if channel.cid == cid:
				return i
		return None

	def goToNumber(self, num):
		cid = self.db.findNumber(num)
		if cid is None:
			return None
		idx = self.findChannelIndex(cid)
		if idx is None:
			self.mode, self.gid = self.ALL, None
			self.fillList()
			idx = self.findChannelIndex(cid)
		self.list.moveToIndex(idx)
		self.history.append(self.createHistoryEntry())
		return cid

	def openSettings(self):
		self.close(None, None, 'settings')

	def clearLogin(self):
		self.close(None, None, 'clear_login')

class IPtvDreamEpg(Screen):
	def __init__(self, session, db, cid, shift):
		Screen.__init__(self, session)

		self["caption"] = Label(_("EPG List"))
		self["btn_red"] = Pixmap()
		self["key_red"] = Label(_("Archive"))
		self["key_green"] = Label(_("Details"))
		self["list"] = self.list = ListSource()

		self["epgName"] = Label()
		self["epgDescription"] = Label()
		self["epgTime"] = Label()
		self["epgDuration"] = Label()

		self["epgProgress"] = Slider(0, 100)
		self["progress"] = self._progress = EpgProgress()
		self._progress.onChanged.append(lambda value: self["epgProgress"].setValue(int(100 * value)))

		# auto resize some widgets
		self._info_part = VerticalLayoutPart(self, (self["epgTime"], self["epgProgress"], self["epgDuration"]))

		self["packetExpire"] = Label()

		self["actions"] = ActionMap(
			["OkCancelActions", "IPtvDreamEpgListActions", "ColorActions"], {
				"cancel": self.close,
				"ok": self.archive,
				"up": self.up,
				"down": self.down,
				"pageUp": self.pageUp,
				"pageDown": self.pageDown,
				"nextDay": self.nextDay,
				"prevDay": self.prevDay,
				"green": self.showInfo,
				"showInfo": self.showInfo,
				"red": self.archive
			}, -1)

		self.db = db  # type: AbstractStream
		self.cid = cid
		self.shift = shift
		self.day = 0
		self.list.onSelectionChanged.append(self.updateLabels)
		self.onShown.append(self.start)

	def start(self):
		self.onShown.remove(self.start)
		self.fillList()

	def buildEpgEntry(self, entry):
		if self.db.channels[self.cid].has_archive and entry.begin < syncTime():
			pixmap = rec_png
		else:
			pixmap = None
		return entry, pixmap, entry.begin.strftime('%a'), entry.begin.strftime('%H:%M'), entry.name

	def fillList(self):
		if self.cid is None:
			return

		time = syncTime() + secTd(self.shift)
		d = time + timedelta(self.day)

		epg_list = []
		try:
			epg_list = self.db.getDayEpg(self.cid, datetime(d.year, d.month, d.day))
		except APIException as e:
			self.session.open(MessageBox, _("Can not load EPG:") + str(e), MessageBox.TYPE_ERROR, 5)

		self.list.setList(map(self.buildEpgEntry, epg_list))
		self.setTitle("EPG / %s / %s %s" % (self.db.channels[self.cid].name, d.strftime("%d"), _(d.strftime("%b"))))
		self.list.setIndex(0)

		if self.day == 0:
			for i, program in enumerate(epg_list):
				if program.isAt(time):
					self.list.setIndex(i)
					break

	def updateLabels(self):
		entry = self.list.getCurrent()
		if not entry:
			return

		entry = entry[0]
		self["epgName"].setText(entry.name)
		self["epgTime"].setText(entry.begin.strftime("%d.%m %H:%M"))
		self["epgDescription"].setText(entry.description)
		self["epgDuration"].setText("%s min" % (entry.duration() / 60))
		if self.db.channels[self.cid].has_archive and entry.begin < syncTime():
			self["btn_red"].show()
			self["key_red"].show()
		else:
			self["btn_red"].hide()
			self["key_red"].hide()
		self._progress.setEpg(entry)
		self._info_part.updateLayout()

	def archive(self):
		entry = self.list.getCurrent()
		if not entry:
			return
		entry = entry[0]
		if self.db.channels[self.cid].has_archive and entry.begin < syncTime():
			self.close(self.cid, entry.begin)

	def showInfo(self):
		entry = self.list.getCurrent()
		if not entry:
			return
		entry = entry[0]
		self.session.openWithCallback(self.infoClosed, IPtvDreamEpgInfo, self.db.channels[self.cid], entry)

	def infoClosed(self, time=None):
		if time is not None:
			self.close(self.cid, time)

	def up(self):
		idx = self.list.getIndex()
		if idx == 0:
			self.prevDay()
			self.list.setIndex(self.list.count() - 1)
		else:
			self.list.selectPrevious()

	def down(self):
		idx = self.list.getIndex()
		if idx == self.list.count() - 1 or self.list.count() == 0:
			self.nextDay()
		else:
			self.list.selectNext()

	def pageUp(self):
		idx = self.list.getIndex()
		if idx == 0:
			self.prevDay()
			self.list.setIndex(self.list.count() - 1)
		else:
			self.list.pageUp()

	def pageDown(self):
		idx = self.list.getIndex()
		if idx == self.list.count() - 1 or self.list.count() == 0:
			self.nextDay()
		else:
			self.list.pageDown()

	def nextDay(self):
		self.day += 1
		self.fillList()

	def prevDay(self):
		self.day -= 1
		self.fillList()

class IPtvDreamEpgInfo(Screen):
	def __init__(self, session, channel, entry):
		"""
		Screen to show information for single EPG entry
		:type entry: utils.EPG
		:type channel: utils.Channel
		"""
		Screen.__init__(self, session)
		self.entry = entry
		self.channel = channel

		self.setTitle("%d. %s" % (channel.number, channel.name))

		self["epgName"] = Label(entry.name)
		self["epgDescription"] = ScrollLabel(entry.description or _("No detailed information"))
		self._main_part = VerticalLayoutPart(self, widgetsToMove=())

		self["epgTime"] = Label(entry.begin.strftime("%a %H:%M"))
		self["epgDate"] = Label(entry.begin.strftime("%d.%m.%Y"))
		self["epgDuration"] = Label()

		self["epgProgress"] = Slider(0, 100)
		self["progress"] = self._progress = EpgProgress()
		self._progress.onChanged.append(self.updateProgress)
		self.onLayoutFinish.append(self.initGui)

		self["btn_red"] = Pixmap()
		self["key_red"] = Label(_("Archive"))

		if not self.hasArchive():
			self["btn_red"].hide()
			self["key_red"].hide()

		self["actions"] = ActionMap(["OkCancelActions", "DirectionActions", "ColorActions"], {
			"cancel": self.close,
			"red": self.playArchive,
			"ok": self.close,
			"up": self["epgDescription"].pageUp,
			"down": self["epgDescription"].pageDown
		}, -1)

	def initGui(self):
		self._main_part.updateLayout()
		self._progress.setEpg(self.entry)

	def hasArchive(self):
		return self.channel.has_archive and self.entry.begin < syncTime()

	def playArchive(self):
		if self.hasArchive():
			self.close(self.entry.begin)

	def updateProgress(self, value):
		t = syncTime()
		if self.entry.isAt(t):
			self["epgDuration"].setText("+%s min" % (self.entry.timeLeft(t) / 60))
		else:
			self["epgDuration"].setText("%s min" % (self.entry.duration() / 60))
		self["epgProgress"].setValue(int(100 * value))

# gettext HACK:
# [_("Jan"), _("Feb"), _("Mar"), _("Apr"), _("May"), _("Jun"), _("Jul"), _("Aug"), _("Sep"), _("Oct"), _("Nov") ]


from pip.req import parse_requirements
from pip.download import PipSession
from setuptools import setup, find_packages

install_reqs = parse_requirements("requirements.txt", session=PipSession())
requires = [str(ir.req) for ir in install_reqs]

if __name__ == "__main__":

    setup(
        name="aioprometheus-binary-format",
        version='1.0.0',
        author="Chris Laws",
        author_email="clawsicus@gmail.com",
        description="This package provides binary metrics format support for aioprometheus",
        long_description="",
        license="MIT",
        keywords=["prometheus", "monitoring", "metrics"],
        url="https://github.com/claws/aioprometheus-binary-format",
        packages=find_packages(),
        install_requires=requires)


import logging
import os
import requests
import sys
from typing import Any
from typing import Tuple
from typing import Dict

import thoth.prescriptions_refresh
from thoth.prescriptions_refresh.prescriptions import Prescriptions
from .gh_link import iter_gh_info

_LOGGER = logging.getLogger(__name__)
_PRESCRIPTIONS_DEFAULT_REPO = Prescriptions.DEFAULT_PRESCRIPTIONS_REPO
_PRESCRIPTIONS_VERSION = thoth.prescriptions_refresh.__version__

_GH_POPULARITY_LOW = int(os.getenv("THOTH_PRESCRIPTIONS_REFRESH_GH_POPULARITY_LOW", 20))
_GH_POPULARITY_MODERATE = int(os.getenv("THOTH_PRESCRIPTIONS_REFRESH_GH_POPULARITY_MODERATE", 100))
_GH_POPULARITY_HIGH = int(os.getenv("THOTH_PRESCRIPTIONS_REFRESH_GH_POPULARITY_HIGH", 1000))
_GH_LINK_PRESCRIPTION_NAME = "gh_popularity.yaml"
_GH_LINK_PRESCRIPTION_CONTENT = """\
units:
  wraps:
  - name: {prescription_name}
    type: wrap
    should_include:
      adviser_pipeline: true
    match:
      state:
        resolved_dependencies:
        - name: {package_name}
    run:
      justification:
      - type: {message_type}
        message: Project '{package_name}' has {popularity_score} popularity on GitHub
        link: {gh_link}
        package_name: {package_name}
    metadata:
    - prescriptions_repository: {default_prescriptions_repository}
      prescriptions_version: {prescriptions_version}
"""

def _compute_popularity(github_response: Dict[str, Any]) -> Tuple[str, str]:
    """Compute popularity based on GitHub statistics."""
    # This computation is pretty simple as of now. Based on the data aggregated from the dataset on 16th
    # September, median is 108 and the distribution was very wide (hence a simple comparision). We could
    # also introduce more sophisticated computation that would involve historical data and community behavior.
    score = github_response["forks_count"] + github_response["stargazers_count"] + github_response["watchers_count"]

    if score < _GH_POPULARITY_LOW:
        return "low", "WARNING"

    if score < _GH_POPULARITY_MODERATE:
        return "moderate", "INFO"

    if score < _GH_POPULARITY_HIGH:
        return "high", "INFO"

    return "very high", "INFO"

def gh_popularity(prescriptions: "Prescriptions") -> None:
    """Check popularity of the given project."""
    for project_name, organization, repository in iter_gh_info(prescriptions):
        gh_link = f"https://github.com/{organization}/{repository}"

        response = requests.get(
            f"https://api.github.com/repos/{organization}/{repository}",
            headers={
                "Accept": "application/vnd.github.v3+json",
                "Authorization": f"token {prescriptions.get_github_token()}",
            },
        )
        if response.status_code == 404:
            _LOGGER.warning("Repository %r not found", gh_link)
            continue

        elif response.status_code == 429:
            _LOGGER.error(
                "Bad HTTP status code %s when trying to obtain information for project %r: too many requests."
                "Exiting code with status 3.",
                response.status_code,
                project_name,
            )
            sys.exit(3)

        elif response.status_code != 200:
            _LOGGER.error(
                "Bad HTTP status code %r when obtaining info using GitHub API for %r: %s",
                response.status_code,
                gh_link,
                response.text,
            )
            continue

        popularity_score, message_type = _compute_popularity(response.json())

        prescription_name = prescriptions.get_prescription_name("GitHubPopularityWrap", project_name)

        prescriptions.create_prescription(
            project_name=project_name,
            prescription_name=_GH_LINK_PRESCRIPTION_NAME,
            content=_GH_LINK_PRESCRIPTION_CONTENT.format(
                gh_link=gh_link,
                package_name=project_name,
                popularity_score=popularity_score,
                message_type=message_type,
                prescription_name=prescription_name,
                default_prescriptions_repository=_PRESCRIPTIONS_DEFAULT_REPO,
                prescriptions_version=_PRESCRIPTIONS_VERSION,
            ),
            commit_message=f"Update of GitHub popularity statistics for project {project_name!r}",
        )


# Data Visualization for Non-cyberbullying content

import matplotlib.pyplot as plot

# labels are defined
dataLabels = ['Non-Cyberbullying ', 'Cyberbullying ']

# Each label's area covered
partition = [9.59,0.41]

# Colours for labels
col = ['yellow', 'red']

# Create Pie chart
plot.pie(partition, labels = dataLabels, colors=col,
        startangle=10, shadow = True, explode = (0, 0.1),
        radius = 1.2, autopct = '%1.2f%%')

# Display the title and legend
plot.title("Non-Cyberbullying Data Visualization")
plot.legend(bbox_to_anchor=(1, 1), loc=2, borderaxespad=2)

# Display the chart
plot.show()


import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from pandas.plotting import scatter_matrix

# plt.interactive(False)

df = pd.read_csv('./data/sigite2014-difficulty-data.csv', sep=';')
print('Shape:', df.shape)
print('Columns:', df.columns)
# df.boxplot()
# scatter_matrix(df, alpha=0.2, figsize=(6, 6), diagonal='kde')
spent = df.filter(regex='SECONDS_SPENT_ON_*', axis=1)
print('Shape:', spent.shape)
print('Columns:', spent.columns)

figure = plt.gcf()  # get current figure
figure.set_size_inches(100, 100)
matplotlib.rc('font', size=4)
plt.axis('off')

spent.hist()
#plt.savefig('seconds_spent', dpi=600)
#plt.show()


# coding: utf-8
from flask import Blueprint, render_template, jsonify, abort
from flask_restful import Resource, Api
from ..models import User, Product

bp = Blueprint('api', __name__, url_prefix='/api')
api = Api(bp)

class USER(Resource):
    def get(self, id):
    	try:
    		user = User.query.get_or_404(id)
    		return jsonify({
    			'User email: ': user.email
    			})
    	except:
    		return 'User {} not found!!!'.format(id)

class PRODUCT(Resource):
    def get(self, id):
    	try:
    		product = Product.query.get_or_404(id)
    		return jsonify({
    			'Product name: ': product.name
    			})
    	except:
    		return 'Product {} not found!!!'.format(id)

api.add_resource(USER, '/user/<int:id>')
api.add_resource(PRODUCT, '/product/<int:id>')


import arcade
from CONSTANTS import *
from Frog import FrogBody
from Popcorn import Popcorn
from random import randint
from Candy import CandyFall
from Hand import HandBoss
from Table import Table
from platform_tile import PlatformTile
from filling_popcorn import RisingPopcorn
import arcade.key

class MovieTheaterFrog(arcade.Window):
    def __init__(self, x=0, y=0):
        # Initialize variables
        super().__init__(WINDOW_WIDTH, WINDOW_HEIGHT, GAME_TITLE)
        self.frogsprite_list = None
        self.popsprite_list = None
        self.handsprite_list = None
        self.body = None
        self.hand1 = None
        self.hand2 = None
        self.floor1 = None
        self.floor2 = None
        self.floor3 = None
        self.timer = 1
        self.popcorn_counter = 0
        self.score = 0
        self.popcorn = []
        self.tongue_end_x = x
        self.tongue_end_y = y
        self.tile1 = None
        self.tile2 = None
        self.tile3 = None
        self.score = 0
        self.candysprite_list = None
        self.candy_counter = 0
        self.off_counter = 0
        self.tablesprite_list = None
        self.tilesprite_list = None
        self.rising_popcorn = None
        self.popcorn_missed_bar = 500
        self.progress_end = 0
        self.level = 1
        self.hand1_counter = 0
        self.hand2_counter = 0
        self.end_game = False
        self.lost_game = False
        self.won_game = False
        self.PhysicsEngine = None
        self.PhysicsEngine2 = None
        self.up_status = False
        self.allow_jump = False
        self.instructions = []
        self.instructions.append(INSTRUCTION_SCREEN)
        self.instructions.append(LOSE_SCREEN)
        self.instructions.append(WON_SCREEN)
        self.current_state = INSTRUCTION_PAGE

    def setup(self):
        # Setup the game (or reset the game)
        arcade.set_background_color(BACKGROUND_COLOR)
        self.frogsprite_list = arcade.SpriteList()
        self.popsprite_list = arcade.SpriteList()
        self.candysprite_list = arcade.SpriteList()
        self.handsprite_list = arcade.SpriteList()
        self.tablesprite_list = arcade.SpriteList()
        self.tilesprite_list = arcade.SpriteList()
        self.rising_popcorn = arcade.SpriteList()
        self.candysprite_list.append(CandyFall())
        self.frogsprite_list.append(FrogBody())
        self.popsprite_list.append(Popcorn())
        self.handsprite_list.append(HandBoss())
        self.handsprite_list.append(HandBoss())
        self.tablesprite_list.append(Table())
        self.tablesprite_list.append(Table())
        self.tablesprite_list.append(Table())
        self.tablesprite_list.append(Table())
        self.body = self.frogsprite_list[0]
        self.hand1 = self.handsprite_list[0]
        self.hand2 = self.handsprite_list[1]
        self.floor1 = self.tablesprite_list[0]
        self.floor1.center_x = 100
        self.floor2 = self.tablesprite_list[1]
        self.floor2.center_x = 250
        self.floor3 = self.tablesprite_list[2]
        self.floor3.center_x = 400
        self.level = 1
        self.hand1_counter = 0
        self.PhysicsEngine = arcade.PhysicsEnginePlatformer(self.body, platforms=self.tablesprite_list, gravity_constant=GRAVITY)
        self.PhysicsEngine = arcade.PhysicsEnginePlatformer(self.body, platforms=self.tablesprite_list,
                                                            gravity_constant=GRAVITY)
        self.allow_jump = True

    def draw_instructions_page(self, page_number):
        # Draw an instruction page. Load the page as an image.
        page_texture = self.instructions[page_number]
        arcade.draw_texture_rectangle(WINDOW_WIDTH // 2, WINDOW_HEIGHT // 2,
                                          page_texture.width,
                                          page_texture.height, page_texture, 0)

    def on_draw(self):
        # Render the screen
            arcade.start_render()
            if self.current_state == INSTRUCTION_PAGE:
                self.draw_instructions_page(0)

            elif self.current_state == GAME_RUNNING:
                self.draw_game()
            elif self.current_state == END_GAME:
                self.draw_instructions_page(1)
            elif self.current_state == WON_GAME:
                self.draw_instructions_page(2)

    def draw_game(self):
        # Called when it is time to draw the world
        arcade.start_render()
        if not self.end_game:
            self.draw_game_elements()

    def on_update(self, delta_time):
        # update functions
        if self.current_state == GAME_RUNNING:
            if self.progress_end == 500:
                self.progress_level()
            self.frogsprite_list[0].update()
            self.popsprite_list.update()
            if self.level in CANDY_LEVELS:
                self.candysprite_list.update()
            if self.level in BOSS_BATTLES:
                self.hand_boss()
                if self.level == FINAL_BATTLE:
                    self.hand_final()
            self.spawn_popcorn()
            self.update_timer()
            self.spawn_candy()
            self.off_screen_counter()
            self.on_draw()
            self.hand_collisions()
            self.PhysicsEngine.update()
            self.control_rising_popcorn()
            self.rising_pop_collisions()
            self.check_candy_pop_collision()
            self.update_lose_game()
            self.update_win_game()
            self.popcorn_missed_game_end()

    def progress_level(self):
        # progresses  to next level when current level is completed
        if self.level in RISING_POP_LEVELS:
            self.rising_popcorn[0].center_y = -300
        self.level += 1
        self.progress_end = 0
        self.popcorn_missed_bar = 500

    def hand_boss(self):
        # directs hand chasing frog
        self.handsprite_list[0].update()
        self.hand_movement()
        self.hand1_reset()

    def hand_final(self):
        # directs hand in final boss
        self.handsprite_list[1].update()
        self.hand_movement()
        self.hand2_reset()

    def draw_game_elements(self):
        # draw basic game elements
        self.draw_instructions()
        self.floor1.draw()
        self.floor2.draw()
        self.floor3.draw()
        self.frogsprite_list.draw()
        arcade.draw_line(*self.body.tongue_args)
        self.popsprite_list.draw()
        self.draw_rising_levels()
        self.draw_candy_levels()
        self.draw_boss_levels()

    def draw_rising_levels(self):
        # draw rising popcorn and tiles
        if self.level in RISING_POP_LEVELS:
            self.tilesprite_list.draw()
            self.rising_popcorn.draw()

    def check_popcorn_candy_collision(self):
        for popcorn in self.popsprite_list:
            if popcorn.collides_with_list(self.candysprite_list):
                popcorn.center_y += 25

    def draw_candy_levels(self):
        # draw candy sprites
        if self.level in CANDY_LEVELS:
            self.candysprite_list.draw()

    def draw_boss_levels(self):
        # draw hand trying to capture frog
        if self.level in BOSS_BATTLES:
            self.hand1.draw()
            if self.level == FINAL_BATTLE:
                self.hand2.draw()
        arcade.draw_line(start_x=490, start_y=10, end_x=490, end_y=self.popcorn_missed_bar, line_width=10,
                         color=arcade.color.YELLOW)
        arcade.draw_line(start_x=10, start_y=10, end_x=10, end_y=self.progress_end, line_width=10,
                         color=[50, 205, 50])

    def control_rising_popcorn(self):
        # Creates platforms for frogs to jump on
        if self.level in RISING_POP_LEVELS:
            self.spawn_tiles()
        elif self.level not in RISING_POP_LEVELS:
            self.PhysicsEngine2 = None
        if self.level == 4:
            self.level_4_pop()
        if self.level == 7:
            self.level_7_pop()
        if self.level == 10:
            self.level_10_pop()

    def spawn_tiles(self):
        # draws tiles for rising popcorn levels
        if len(self.rising_popcorn) == 0:
            self.tilesprite_list.append(PlatformTile())
            self.tilesprite_list.append(PlatformTile())
            self.tile1 = self.tilesprite_list[0]
            self.tile1.center_x = 100
            self.tile1.center_y = 200
            self.tile2 = self.tilesprite_list[1]
            self.tile2.center_x = 400
            self.tile2.center_y = 200
            self.PhysicsEngine2 = arcade.PhysicsEnginePlatformer(self.body, platforms=self.tilesprite_list,
                                                                 gravity_constant=GRAVITY)
            self.PhysicsEngine2.update()
            self.rising_popcorn.append(RisingPopcorn())

    def level_4_pop(self):
        # directs rising popcorn in level 4
        if self.rising_popcorn[0].center_y <= -10:
            self.rising_popcorn[0].update()
        self.PhysicsEngine2.update()

    def level_7_pop(self):
        # directs rising popcorn in level 7
        if self.rising_popcorn[0].center_y <= -10:
            self.rising_popcorn[0].change_y = 1.5
            self.rising_popcorn[0].update()
        self.PhysicsEngine2 = arcade.PhysicsEnginePlatformer(self.body,platforms=self.tilesprite_list,gravity_constant=GRAVITY)
        self.PhysicsEngine2.update()

    def level_10_pop(self):
        # directs level 10
        self.tilesprite_list.append(PlatformTile())
        self.tile3 = self.tilesprite_list[2]
        self.tile3.center_x = 250
        self.tile3.center_y = 350
        self.PhysicsEngine2 = arcade.PhysicsEnginePlatformer(self.body,platforms=self.tilesprite_list,gravity_constant=GRAVITY)
        self.PhysicsEngine2.update()
        if self.rising_popcorn[0].center_y <= 250:
            self.level_10_rising()
        if self.body.center_y > 500:
            self.level_10_win_game()

    def level_10_rising(self):
        # directs rising popcorn in level 10
        self.rising_popcorn[0].change_y = 1.5
        self.rising_popcorn[0].update()
        self.PhysicsEngine2.update()

    def level_10_win_game(self):
        # changes game status to won
        self.won_game = True

    def check_candy_pop_collision(self):
        # checks and prevents candy and popcorn from colliding on top of one another
        for popcorn in self.popsprite_list:
            if popcorn.collides_with_list(self.candysprite_list):
                popcorn.center_y += 50

    def spawn_popcorn(self):
        # Creates randomly generated popcorn to fall on the screen
        if self.level in FALLING_POP_LEVELS:
            if self.timer % 100 == 0:
                self.edit_pop_list()

    def edit_pop_list(self):
        # adds popcorn sprites to list to be spawned
        self.popcorn_counter += 1
        self.popsprite_list.append(Popcorn())
        self.popsprite_list[self.popcorn_counter].center_x = randint(50, 450)
        if self.level in FASTER_FALL:
            self.popsprite_list[self.popcorn_counter].change_y = 3

    def update_lose_game(self):
        # updates current state after game is lost
        if self.lost_game:
            self.current_state = END_GAME

    def update_win_game(self):
        if self.won_game:
            self.current_state = WON_GAME

    def draw_instructions(self):
        # Draws instructions for the game
        if self.level == 1:
            arcade.draw_text(
                "Eat popcorn to fill the frog's \n hunger bar! \n If you miss a popcorn your health bar goes down.",
                start_x=75, start_y=325, color=arcade.color.WHITE_SMOKE, font_size=12, width=0, align="center")
        elif self.level == 2:
            arcade.draw_text("Don't eat the candy, you'll lose hunger points!", start_x=125, start_y=325,
                             color=arcade.color.WHITE_SMOKE, font_size=12,
                             width=0, align="center")
        elif self.level == 3:
            arcade.draw_text("The movie theatre attendant is trying to catch you! \n Dodge their hand!", start_x=125,
                             start_y=325,
                             color=arcade.color.WHITE_SMOKE, font_size=12,
                             width=0, align="center")
        elif self.level == 4:
            arcade.draw_text(
                "They're filling up the machine! \n Jump away from the rising popcorn!\n Don't forget to keep eating falling popcorn.",
                start_x=125, start_y=325,
                color=arcade.color.WHITE_SMOKE, font_size=12,
                width=0, align="center")
        elif self.level == 5:
            arcade.draw_text("They're speeding up the machine...\n Keep eating!", start_x=125,
                             start_y=325,
                             color=arcade.color.WHITE_SMOKE, font_size=12,
                             width=0, align="center")
        elif self.level == 6:
            arcade.draw_text("They're trying to catch you again!", start_x=125,
                             start_y=325,
                             color=arcade.color.WHITE_SMOKE, font_size=12,
                             width=0, align="center")
        elif self.level == 7 or self.level == 8:
            arcade.draw_text("Keep eating!", start_x=125,
                             start_y=325,
                             color=arcade.color.WHITE_SMOKE, font_size=12,
                             width=0, align="center")
        elif self.level == 9:
            arcade.draw_text("They won't give up... \n They're still trying to catch you!", start_x=125,
                             start_y=325,
                             color=arcade.color.WHITE_SMOKE, font_size=12,
                             width=0, align="center")
        elif self.level == 10:
            arcade.draw_text("They're filling up the machine all the way! \n Jump out of the machine to freedom!",
                             start_x=125,
                             start_y=250,
                             color=arcade.color.WHITE_SMOKE, font_size=12,
                             width=0, align="center")

    def spawn_candy(self):
        # Spawns candy to randomly fall from the top of the screen
        if self.level in CANDY_LEVELS and self.level not in MORE_CANDY:
            self.not_more_candy()
        elif self.level in CANDY_LEVELS and self.level in MORE_CANDY:
            self.more_candy()

    def not_more_candy(self):
        # spawns candy for levels not requiring more candy
        if self.timer % 300 == 0:
            self.candy_counter += 1
            self.candysprite_list.append(CandyFall())
            self.candysprite_list[self.candy_counter].center_x = randint(50, 450)
            if self.level in FASTER_FALL:
                self.candysprite_list[self.candy_counter].change_y = 3

    def more_candy(self):
        # spawns candy for levels requiring more candy
        if self.timer % 150 == 0:
            self.candy_counter += 1
            self.candysprite_list.append(CandyFall())
            self.candysprite_list[self.candy_counter].center_x = randint(50, 450)
            if self.level in FASTER_FALL:
                self.candysprite_list[self.candy_counter].change_y = 3

    def update_timer(self):
        # Updates timer and resets when timer hits max
        if self.timer < TIMER_MAX:
            self.timer += 1
        else:
            self.timer = 1

    def off_screen_calculator(self):
        # Counts the amount of popcorn that fall off screen
        self.off_counter += 1
        self.popcorn_counter -= 1

    def popcorn_missed_game_end(self):
        #Ends game if enough popcorn falls off screen
        if self.popcorn_missed_bar == 0:
            self.end_game = True
            self.lost_game = True

    def off_screen_counter(self):
        #Tracks the amount of popcorn the falls off screen
        copy_of_counter = self.popsprite_list[:]
        for popcorn in copy_of_counter:
            if popcorn.off_screen_test():
                self.off_screen_calculator()
                popcorn.remove_from_sprite_lists()
                if self.level in EAT_5:
                    self.eat_5()
                elif self.level in EAT_10:
                    self.eat_10()

    def eat_5(self):
        # grows progress bar when level requires eating 5 popcorn
        self.popcorn_missed_bar -= (500 * (1 / 5))
        self.popcorn_missed_game_end()

    def eat_10(self):
        # grows progress bar when level requires eating 10 popcorn
        self.popcorn_missed_bar -= (500 * (1 / 3))
        self.popcorn_missed_game_end()

    def hand_movement(self):
        # Moves hand depending on player location
        if self.level in BOSS_BATTLES:
            if self.hand1.center_y >= WINDOW_HEIGHT / 2:
                self.hand_boss_battles()
            if self.level == MIDDLE_BATTLE and self.hand1.center_y < WINDOW_HEIGHT / 2:
                self.hand1.change_y = 5
            if self.level == FINAL_BATTLE and self.hand1.center_y < WINDOW_HEIGHT / 2:
                self.hand1.change_y = 5
            if self.level == FINAL_BATTLE:
                self.hand_final_battle()

    def hand_boss_battles(self):
        # moves hand in boss battle
        if self.hand1.center_x < self.body.center_x:
            self.hand1.move_right()
        elif self.hand1.center_x > self.body.center_x:
            self.hand1.move_left()

    def hand_final_battle(self):
        # moves hand in final battle
        if self.hand2.center_y >= WINDOW_HEIGHT * (3 / 4):
            if self.hand2.center_x < self.body.center_x:
                self.hand2.move_right()
            elif self.hand2.center_x > self.body.center_x:
                self.hand2.move_left()
        else:
            self.hand2.change_y = 5

    def hand1_reset(self):
        # Resets hand 1 when it reaches the bottom of the screen and tracks amount of times
        if self.hand1.center_y <= 0:
            if self.hand1_counter == 2:
                self.level += 1
                self.hand1_counter = 0
            else:
                self.hand1_counter += 1
                self.hand1.center_y = WINDOW_HEIGHT
                self.hand1.change_y = 1

    def hand2_reset(self):
        # Resets hand 2 when it reaches the bottom of the screen
        if self.hand2.center_y <= 0:
            self.hand2.center_y = WINDOW_HEIGHT

    def hand_collisions(self):
        # Checks for collision between frog and hands
        if self.level in BOSS_BATTLES:
            for hand in self.handsprite_list:
                if hand.collides_with_sprite(self.body):
                    self.end_game = True
                    self.lost_game = True

    def rising_pop_collisions(self):
        # Checks for collisions between rising popcorn and frog
        if self.level in RISING_POP_LEVELS:
            for popcorn in self.rising_popcorn:
                if popcorn.collides_with_sprite(self.body):
                    self.end_game = True
                    self.lost_game = True

    def on_key_press(self, symbol: int, modifiers: int):
        # Moves frog left and right and jumps
        if self.current_state == GAME_RUNNING:
            if symbol == arcade.key.D or symbol == arcade.key.RIGHT:
                self.frogsprite_list[0].change_x = MOVEMENT_SPEED
            elif symbol == arcade.key.A or symbol == arcade.key.LEFT:
                self.frogsprite_list[0].change_x = -MOVEMENT_SPEED
            elif symbol == arcade.key.W or symbol == arcade.key.UP:
                if self.PhysicsEngine.can_jump() or self.PhysicsEngine2.can_jump():
                    self.body.change_y = 15

    def on_key_release(self, symbol: int, modifiers: int):
        # Stops frog from movements
        if self.current_state == GAME_RUNNING:
            if symbol == arcade.key.D or symbol == arcade.key.RIGHT:
                self.body.change_x = 0
            elif symbol == arcade.key.A or symbol == arcade.key.LEFT:
                self.body.change_x = 0
            elif symbol == arcade.key.W or symbol == arcade.key.UP:
                self.body.change_y = 0

    def on_mouse_motion(self, x: float, y: float, dx: float, dy: float):
        # Tracks mouse movement and controls frog tongue
        self.body.tongue_end_x = x
        self.body.tongue_end_y = y
        copy_of_popcorn = self.popsprite_list[:]
        for popcorn in copy_of_popcorn:
            if popcorn.collides_with_point([x, y]):
                popcorn.remove_from_sprite_lists()
                arcade.play_sound(MUNCH_SOUND)
                self.popcorn_counter -= 1
                self.score += 1
                if self.level in EAT_5:
                    self.progress_end += 100
                elif self.level in EAT_10:
                    self.progress_end += 50
        copy_of_candy = self.candysprite_list[:]
        for candy in copy_of_candy:
            if candy.collides_with_point([x, y]):
                candy.remove_from_sprite_lists()
                arcade.play_sound(YUCK_SOUND)
                self.candy_counter -= 1
                self.score -= 1
                if self.level in EAT_5:
                    self.progress_end -= 100
                elif self.level in EAT_10:
                    self.progress_end -= 50

    def on_mouse_press(self, x: float, y: float, button: int, modifiers: int):
        if self.current_state == INSTRUCTION_PAGE:
            self.current_state = GAME_RUNNING

def main():
    window = MovieTheaterFrog()
    window.setup()
    arcade.run()

if __name__ == "__main__":
    main()