from PyQt5 import QtWidgets, uic
#import time
import pandas as pd
import numpy as np
from mlxtend.frequent_patterns import association_rules
import random


def eval_func(a):
    """
        This function converts a value to a string and returns it.
        @param a: Value to be converted to string
    """
    return eval(str(a))
def mul_func(a):
    """
        This function converts a value to an integer and multiplies it by 500.
        @param a: Value to be multiplied by 500
    """
    return int(a)*500
def make_list(a):
    """
        This function creates a list out of the parameter and returns it.
    """
    return list(a)
def div_func(a):
    """
        This function divides the number a by 500 and returns it.
    """
    return a/500

class Knap:
    """
        Class used for Knapsack implementation. \ Consists of two functions used for computing table, and one for evaluating which actors were chosen to create the table.
    """

    def __init__(self, weights, total, profit):
        """
            Constructor used to initialize variables used everywhere in the function.
            @param weights: Cost of each actor is stored in this list.
            @param total: This variable is used to denote the total budget.
            @param profit: This list indicates the profit of choosing an actor.
        """
        self.weights = weights
        self.total = total
        self.profit = profit
        self.n = self.profit.__len__()
        self.selected = np.zeros((self.n, self.total + 1))
        print(self.weights)

    def getItemsUsed(self):
        """
            Once the table of the Knapsack Algorithm is constructed, this function can be used to determine which actors were used to get this table.
            @return Set of actors (in 0s and 1s) that maximize profit and keep the total cost in the budget as determined by Knapsack.
        """
        self.marked = np.zeros(self.n)
        i = self.n - 1
        currentW = self.total
        while (i >= 0 and currentW >= 0):
            if (i == 0 and self.selected[i, np.int(currentW)] > 0) or (i != 0 and self.selected[i, np.int(currentW)] != self.selected[i - 1, np.int(currentW)]):
                self.marked[i] = 1
                currentW = currentW - self.weights[i]
            i = i - 1
        return self.marked

    def algorithm(self):
        """
            This function is used to compute the table in the Knapsack Algorithm. This table ```self.selected``` indicates the best profit for a set of actors.
            @return List containing the maximum profit, and the set of actors used to get this profit
        """
        for i in range(0, self.n):
            for j in range(0, self.total + 1):
                if self.weights[i] > j:
                    self.selected[i][j] = self.selected[i - 1][j]
                else:
                    self.selected[i,j] = np.maximum(self.selected[i-1,j],self.profit[i]+self.selected[i-1,j-np.int(self.weights[i])])
        return [self.selected[self.n-1,np.int(self.total)], self.getItemsUsed()]


class ReadFiles:
    """
        This class is used to read CSV files related to this module, perform preprocessing if necessary, get association rules of the top actors.\ The class then forwards the preprocessed data further to get results according to user input.
    """
    def __init__(self, apr, top, req):
        """
            Constructor that is used to read the CSV files related to the project and store them into Dataframes.
            @param apr: String which contains the path of the CSV file output after Apriori algorithm is run.
            @param topactors: String which contains the path of the CSV file containing the actors/actresses who have worked most in each Genre.
            @param req: String which contains the path of the CSV file containing relevant information of each actor separately.
        """
        self.apriori = pd.read_csv(apr)
        self.topactors = pd.read_csv(top)
        self.req = pd.read_csv(req)
        self.genre_list = ['Adventure','Action','Comedy','Crime','Drama','Family','Fantasy','Thriller','Romance','Horror','Musical']

    def apply(self):
        """
            Function that preprocesses the results from the apriori algorithm. \ It then determines the association rules between various actors/actresses and then writes it to the file rules.csv.
        """
        self.apriori['itemsets'] = self.apriori['itemsets'].apply(eval_func)
        self.apriori['support'] = self.apriori['support'].apply(eval_func)

        self.topactors = self.topactors.drop(columns = ['Unnamed: 0'])
        self.topactors['Top 10 Actors'] = self.topactors['Top 10 Actors'].apply(eval_func)

        self.req['normalizedRating'] = self.req['normalizedRating'].apply(eval_func)
        self.req['googleHits'] = self.req['googleHits'].apply(eval_func).apply(div_func)

        self.rules = association_rules(self.apriori, metric='lift', min_threshold=10)
        self.rules.to_csv('rules.csv')
        print(self.genre_list)

    def support(self, budget, input_genre):
        """
            This function determines the set of supporting actors as determined by the association rules. \ It uses these association rules to determine the supporting actors for the top actor selected randomly.
            @param budget: Total budget of the movie
            @param input_genre: The genre that the user inputs
        """
        abascus = list(self.topactors[self.topactors['Genre'] == input_genre.capitalize()]['Top 10 Actors'])
        top_actor = abascus[0][random.randint(0,9)][0]
        c = []
        a = list(self.rules[self.rules['antecedents'].apply(lambda x:set([top_actor]).issubset(x))]['consequents'])
        for each in a:
            if len(eval_func(each)) > 1:
                c.append(each)
        print("************************************************************")
        print(c[0])
        print("************************************************************")
        print(c)
        print("************************************************************")

        self.supporting_actors = list(frozenset.union(*c))
        self.supporting_actors.append(top_actor)
        self.daa = self.req.loc[self.req['actorName'].isin(self.supporting_actors)]
        


def recommendation():
    """
        This is the core function that reads files, takes user input, applies association rules, and uses knapsack to finally output a good star cast for a movie.\ It uses other classes to achieve this functionality.
    """
    apr = 'files/newresultapriori.csv'
    top = 'files/TopActors.csv'
    req = 'req.csv'

    rf = ReadFiles(apr, top, req)
    rf.apply()

    bud1 = int(dig.budgetBox.toPlainText())
    budget = bud1*100000
    input_genre = dig.genreBox.currentText()

    rf.support(budget, input_genre)

    profit = list(rf.daa['normalizedRating'].values)
    cost = list(rf.daa['googleHits'].values)
    W = np.int(div_func(budget))

    if W<0:
        print('Budget too low for the desired genre')
        return

    k = Knap(cost, W, profit)
    select = k.algorithm()
    # select = zeroOneKnapsack(profit,cost,W)
    print(select)

    name_index = []
    for i in range(0,len(select[1])):
        if select[1][i] == 1:
            name_index.append(i)
        #print(name_index)
    bud = []
    for each in name_index:
        bud.append(cost[int(each)])
    name = []
    for each in name_index:
        name.append(rf.supporting_actors[int(each)])
    #print(name)
    to = mul_func(sum(bud))    
    if to/100000>bud1:
        recommendation()
    else:
        res.show()
        res.finalBudget.setText(str(to/100000))
        res.actorList.clear()
        for i in range(len(name)):
            name[i] = name[i] + "\t\t\t\t\t\t" + str(bud[i] / 200) + " cr"
        res.actorList.addItems(name) 
    
def actorEvaluation():
    """
        Function that communicates with the user interface.
    """
    budget = float(dig.budgetBox.toPlainText())
    genre = dig.genreBox.currentText()
    # noOfActors = str(dig.noOfActors.value())
    
    res.show()
    #time.sleep(5)

    res.actorList.clear()
    res.actorList.addItems([str(budget),genre,noOfActors])
    

app = QtWidgets.QApplication([])
dig = uic.loadUi("projectUI.ui")
res = uic.loadUi("result.ui")

genreList = ['Adventure','Action','Comedy','Crime','Drama','Family','Fantasy','Thriller','Romance','Horror','Musical']
dig.genreBox.addItems(genreList)

dig.submit.clicked.connect(recommendation)
res.refreshResult.clicked.connect(recommendation)

dig.setWindowTitle("Star Cast Prediction")
res.setWindowTitle("Optimal star cast")

dig.show()
app.exec()
