import pandas as pd
import sys
from scipy.stats import pearsonr
from ..model.Recommendation import Recommendation
from ..model.Movie import Movie
from ..model.Rating import Rating
from ..model.User import User
from ..util.Database import Database


class Recommender:
    users_ratings_similarities = None
    users = None
    ratings = None
    movies = None
    recoms = None

    def __init__(self):
        Database.initialize()
        self.users = Database.session.query(User).all()
        self.ratings = Database.session.query(Rating).all()
        self.movies = Database.session.query(Movie).all()
        self.recoms = {}

    # Calculate recommendations for each user
    def start(self):
        self.collaborative_filtering()

    def collaborative_filtering(self):
        self.sim_pearsons()
        self.recommendations()
        self.persist_recommendations()

    # https://books.google.dk/books?id=w84wDgAAQBAJ&pg=PA172&lpg=PA172&dq=sim+pearson+python&source=bl&ots=zR5gRVW8Sh&sig=swdFBvTh8oPaVl_3VfT-eFp_wxw&hl=da&sa=X&ved=0ahUKEwjVyp6TvKbTAhXIBsAKHSsyA_sQ6AEIRDAE#v=onepage&q=sim%20pearson%20python&f=false
    def sim_pearsons(self):
        print("Preparing Similarities")
        df_movies_rated = pd.DataFrame()
        for idx, user_x in enumerate(self.users):
            df_movies_rated[user_x.id] = pd.Series([rating.movie_id for rating in user_x.ratings])

        df_similarities = pd.DataFrame(index=[u.id for u in self.users], columns=[u.id for u in self.users])
        print("Calculating Similarities")
        num_rows = len(self.users)
        for idx, user_x in enumerate(self.users):
            for idy, user_y in enumerate(self.users):
                # Similarity has not been found yet
                if pd.isnull(df_similarities[user_x.id][user_y.id]):
                    # Other user, find shared movie ratings
                    if user_x.id != user_y.id:
                        intersection = pd.Series(
                            list(set(df_movies_rated[user_x.id]) & set(df_movies_rated[user_y.id])))
                        # Users share atleast 5, calc similarity
                        if len(intersection) >= 5:
                            # Convert to integers -> make comparable
                            intersection = map(int, intersection)

                            x_ratings = [rating.value for rating in user_x.ratings if
                                         int(rating.movie_id) in intersection]
                            y_ratings = [rating.value for rating in user_y.ratings if
                                         int(rating.movie_id) in intersection]
                            pearson = pearsonr(x_ratings, y_ratings)
                            df_similarities.set_value(user_x.id, user_y.id, pearson[0])
                            df_similarities.set_value(user_y.id, user_x.id, pearson[0])

            progress = (float(idx) / float(num_rows)) * 100
            sys.stdout.write("Progress: %f%%    \r" % progress)
            sys.stdout.flush()
        self.users_ratings_similarities = df_similarities

    # Programming collective chapter 2
    def recommendations(self):
        print("Finding Movie Recommendations")
        weighted_sum = {}
        similarity_sum = {}
        num_rows = len(self.users)
        for index, user in enumerate(self.users):
            # Remove users that don't share ratings
            df_similar_users = self.users_ratings_similarities[user.id]
            df_similar_users = df_similar_users.dropna()
            user_movies = [rating.movie_id for rating in user.ratings]
            # Other users ratings, positive similarity -> weight what else they have seen
            for idx, sim in df_similar_users.iteritems():
                if sim > 0:
                    target_user = self.users[idx - 1]
                    if target_user.id != idx:
                        print("User id\'s dosen\'t match up, idx: " + str(idx) + " target user: " + str(target_user.id))
                        exit(1)
                    for rating in target_user.ratings:
                        # user has not seen this movie -> calculate score
                        if rating.movie_id not in user_movies:
                            # Similarity and Score
                            weighted_sum.setdefault(rating.movie_id, 0)
                            weighted_sum[rating.movie_id] += (rating.value * sim)
                            ## Sum of similarity of people who rated it
                            similarity_sum.setdefault(rating.movie_id, 0)
                            similarity_sum[rating.movie_id] += sim

            # Normalise -> people who agree with me more, their oppinions matter most
            rankings = [(w_score / similarity_sum[item], item) for item, w_score in weighted_sum.items()]
            rankings.sort()
            rankings.reverse()
            self.recoms[user] = rankings[:100]

            progress = (float(index) / float(num_rows)) * 100
            sys.stdout.write("Progress: %f%%    \r" % progress)
            sys.stdout.flush()

    def persist_recommendations(self):
        print("Saving Recommendations")
        num_rows = len(self.recoms)
        index = 0
        estimated_rating = 0
        movie_id = 1
        recommendations = []
        for user, recoms in self.recoms.iteritems():
            # Persist top 20 ratings for each user.
            amount_recoms = 0
            for item in recoms:
                if Database.session.query(Recommendation).filter(Recommendation.user_id == user.id).filter(
                                Recommendation.movie_id == item[movie_id]).first() is None:
                    recom = Recommendation(user_id=user.id, movie_id=item[movie_id],
                                           estimated_rating=item[estimated_rating])
                    recommendations.append(recom)
                    amount_recoms += 1
                    if amount_recoms % 20 == 0:
                        break
            progress = (float(index) / float(num_rows)) * 100
            sys.stdout.write("Progress: %f%%    \r" % progress)
            sys.stdout.flush()
            index += 1

        Database.session.bulk_save_objects(recommendations)
        Database.session.commit()

import pandas as pd
import sys
from scipy.stats import pearsonr
from ..model.Recommendation import Recommendation
from ..model.Movie import Movie
from ..model.Rating import Rating
from ..model.User import User
from ..util.Database import Database


class Recommender:
    users_ratings_similarities = None
    users = None
    ratings = None
    movies = None
    recoms = None

    def __init__(self):
        Database.initialize()
        self.users = Database.session.query(User).all()
        self.ratings = Database.session.query(Rating).all()
        self.movies = Database.session.query(Movie).all()
        self.recoms = {}

    # Calculate recommendations for each user
    def start(self):
        self.collaborative_filtering()

    def collaborative_filtering(self):
        self.sim_pearsons()
        self.recommendations()
        self.persist_recommendations()

    # https://books.google.dk/books?id=w84wDgAAQBAJ&pg=PA172&lpg=PA172&dq=sim+pearson+python&source=bl&ots=zR5gRVW8Sh&sig=swdFBvTh8oPaVl_3VfT-eFp_wxw&hl=da&sa=X&ved=0ahUKEwjVyp6TvKbTAhXIBsAKHSsyA_sQ6AEIRDAE#v=onepage&q=sim%20pearson%20python&f=false
    def sim_pearsons(self):
        print("Preparing Similarities")
        df_movies_rated = pd.DataFrame()
        for idx, user_x in enumerate(self.users):
            df_movies_rated[user_x.id] = pd.Series([rating.movie_id for rating in user_x.ratings])

        df_similarities = pd.DataFrame(index=[u.id for u in self.users], columns=[u.id for u in self.users])
        print("Calculating Similarities")
        num_rows = len(self.users)
        for idx, user_x in enumerate(self.users):
            for idy, user_y in enumerate(self.users):
                # Similarity has not been found yet
                if pd.isnull(df_similarities[user_x.id][user_y.id]):
                    # Other user, find shared movie ratings
                    if user_x.id != user_y.id:
                        intersection = pd.Series(
                            list(set(df_movies_rated[user_x.id]) & set(df_movies_rated[user_y.id])))
                        # Users share atleast 5, calc similarity
                        if len(intersection) >= 5:
                            # Convert to integers -> make comparable
                            intersection = map(int, intersection)

                            x_ratings = [rating.value for rating in user_x.ratings if
                                         int(rating.movie_id) in intersection]
                            y_ratings = [rating.value for rating in user_y.ratings if
                                         int(rating.movie_id) in intersection]
                            pearson = pearsonr(x_ratings, y_ratings)
                            df_similarities.set_value(user_x.id, user_y.id, pearson[0])
                            df_similarities.set_value(user_y.id, user_x.id, pearson[0])

            progress = (float(idx) / float(num_rows)) * 100
            sys.stdout.write("Progress: %f%%    \r" % progress)
            sys.stdout.flush()
        self.users_ratings_similarities = df_similarities

    # Programming collective chapter 2
    def recommendations(self):
        print("Finding Movie Recommendations")
        weighted_sum = {}
        similarity_sum = {}
        num_rows = len(self.users)
        for index, user in enumerate(self.users):
            # Remove users that don't share ratings
            df_similar_users = self.users_ratings_similarities[user.id]
            df_similar_users = df_similar_users.dropna()
            user_movies = [rating.movie_id for rating in user.ratings]
            # Other users ratings, positive similarity -> weight what else they have seen
            for idx, sim in df_similar_users.iteritems():
                if sim > 0:
                    target_user = self.users[idx - 1]
                    if target_user.id != idx:
                        print("User id\'s dosen\'t match up, idx: " + str(idx) + " target user: " + str(target_user.id))
                        exit(1)
                    for rating in target_user.ratings:
                        # user has not seen this movie -> calculate score
                        if rating.movie_id not in user_movies:
                            # Similarity and Score
                            weighted_sum.setdefault(rating.movie_id, 0)
                            weighted_sum[rating.movie_id] += (rating.value * sim)
                            ## Sum of similarity of people who rated it
                            similarity_sum.setdefault(rating.movie_id, 0)
                            similarity_sum[rating.movie_id] += sim

            # Normalise -> people who agree with me more, their oppinions matter most
            rankings = [(w_score / similarity_sum[item], item) for item, w_score in weighted_sum.items()]
            rankings.sort()
            rankings.reverse()
            self.recoms[user] = rankings[:100]

            progress = (float(index) / float(num_rows)) * 100
            sys.stdout.write("Progress: %f%%    \r" % progress)
            sys.stdout.flush()

    def persist_recommendations(self):
        print("Saving Recommendations")
        num_rows = len(self.recoms)
        index = 0
        estimated_rating = 0
        movie_id = 1
        recommendations = []
        for user, recoms in self.recoms.iteritems():
            # Persist top 20 ratings for each user.
            amount_recoms = 0
            for item in recoms:
                if Database.session.query(Recommendation).filter(Recommendation.user_id == user.id).filter(
                                Recommendation.movie_id == item[movie_id]).first() is None:
                    recom = Recommendation(user_id=user.id, movie_id=item[movie_id],
                                           estimated_rating=item[estimated_rating])
                    recommendations.append(recom)
                    amount_recoms += 1
                    if amount_recoms % 20 == 0:
                        break
            progress = (float(index) / float(num_rows)) * 100
            sys.stdout.write("Progress: %f%%    \r" % progress)
            sys.stdout.flush()
            index += 1

        Database.session.bulk_save_objects(recommendations)
        Database.session.commit()

# Calculate recommendations for each user # https://books.google.dk/books?id=w84wDgAAQBAJ&pg=PA172&lpg=PA172&dq=sim+pearson+python&source=bl&ots=zR5gRVW8Sh&sig=swdFBvTh8oPaVl_3VfT-eFp_wxw&hl=da&sa=X&ved=0ahUKEwjVyp6TvKbTAhXIBsAKHSsyA_sQ6AEIRDAE#v=onepage&q=sim%20pearson%20python&f=false # Similarity has not been found yet # Other user, find shared movie ratings # Users share atleast 5, calc similarity # Convert to integers -> make comparable # Programming collective chapter 2 # Remove users that don't share ratings # Other users ratings, positive similarity -> weight what else they have seen # user has not seen this movie -> calculate score # Similarity and Score ## Sum of similarity of people who rated it # Normalise -> people who agree with me more, their oppinions matter most # Persist top 20 ratings for each user.