﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using yes64Data;

namespace recommendation
{
    public class CollaborativeFilteringEngine : Engine
    {
        private int _topN;

        private List<Order> trainData = null;
        private List<Order> validateData = null;
        private List<Order> testData = null;

        private ArrayView<string> BestSellerRecommendList;
        private List<Tuple<string, int>> isbnSellCountList;

        private Dictionary<string, List<Tuple<string, double>>> simUserWeightDic = new Dictionary<string, List<Tuple<string, double>>>();
        private Dictionary<string, HashSet<string>> userISBNDic;
        private Dictionary<string, int> isbnSellCountDic;


        public void setTrainData(IEnumerable<Order> train)
        {
            this.trainData = train.ToList();
        }

        public void setTestData(IEnumerable<Order> test)
        {
            this.testData = test.ToList();
        }

        public void setValidateData(IEnumerable<Order> validate)
        {
            this.validateData = validate.ToList();
        }

        public void train(int topN)
        {
            _topN = topN;

            BestSellerRecommendList = ArrayView<string>.Of(trainData
                .GroupBy(e => e.ISBN)
                .Select(e => new { ISBN = e.Key, Count = e.Count() })
                .OrderByDescending(e => e.Count)
                .Select(e => e.ISBN)
                .Take(topN));

            isbnSellCountList = trainData
                .GroupBy(e => e.ISBN)
                .Select(e => new { ISBN = e.Key, Count = e.Count() })
                .OrderByDescending(e => e.Count)
                .Select(e => Tuple.Create(e.ISBN, e.Count))
                .ToList();

            isbnSellCountDic = isbnSellCountList.ToDictionary(e => e.Item1, e => e.Item2);

            userISBNDic = trainData
                .GroupBy(e => e.회원번호)
                .Select(e => new { UserId = e.Key, ISBNSet = new HashSet<string>(e.Select(t => t.ISBN)) })
                .ToDictionary(e => e.UserId, e => e.ISBNSet);

            foreach (var userA in userISBNDic.Keys)
            {
                simUserWeightDic.Add(userA, new List<Tuple<string, double>>());

                foreach (var userB in userISBNDic.Keys)
                {
                    var sim = simularity(userISBNDic[userA], userISBNDic[userB]);
                    if (sim == 0.0) continue;

                    simUserWeightDic[userA].Add(Tuple.Create(userB, sim));
                }
            }
        }

        public static double simularity(HashSet<string> setA, HashSet<string> setB)
        {
            double interCount = setA.Intersect(setB).Count();
            if (interCount == 0) return 0;

            double unionCount = setA.Union(setB).Count();
            return interCount / unionCount;
        }

        public ArrayView<string> recommend(string userId)
        {
            var recommendationList = new List<string>();

            if (simUserWeightDic.ContainsKey(userId))
            {
                Dictionary<string, double> isbnWeightDic = new Dictionary<string, double>();

                var simUserList = simUserWeightDic[userId];
                foreach (var elem in simUserList)
                {
                    var boughtISBNList = userISBNDic[elem.Item1];
                    foreach (var isbn in boughtISBNList)
                    {
                        if (isbnWeightDic.ContainsKey(isbn))
            
                            isbnWeightDic[isbn] += elem.Item2;
                        else
                            isbnWeightDic.Add(isbn, elem.Item2);
                    }
                }

                recommendationList = isbnWeightDic
                    .Where(e => isbnWeightDic.ContainsKey(e.Key))
                    .Where(e => userISBNDic[userId].Contains(e.Key) == false)
                    .OrderByDescending(e => e.Value)
                    .Select(e => e.Key)
                    .Take(_topN)
                    .ToList();
            }

            if (recommendationList.Count < _topN)
            {
                for (int i = 0; i < isbnSellCountList.Count; i++)
                {
                    var isbn = isbnSellCountList[i].Item1;
                    if (recommendationList.Contains(isbn)) continue;
                    if (userISBNDic.ContainsKey(userId) && userISBNDic[userId].Contains(isbn)) continue;

                    recommendationList.Add(isbn);
                    if (recommendationList.Count == _topN) break;
                }
            }

            return ArrayView<string>.Of(recommendationList);
        }
    }
}
