using LDA.LdaModel;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LDA.LdaModel
{
	public class LDAGibbsSampling : LDA
	{

		protected int[][] nw;
		protected int[][] nd;
		protected int[] nwsum;
		protected int[] ndsum;
		protected double[] p;

		protected int savestep;
		protected int niters;

		protected string outputfile;
		protected int twords;

		Corpora cor;

		public LDAGibbsSampling()
		{
			M = 0;
			V = 0;
			K = 10;
			alpha = 0.1;
			beta = 0.1;
		}

		public void InitOption(CommandLineOption opt)
		{
			try
			{
				K = opt.topics;
				alpha = opt.alpha;
				beta = opt.beta;
				niters = opt.niters;
				savestep = niters;
				
				//outputfile = opt.outputfile;
				twords = opt.twords;
			}
			catch (Exception ex)
			{
				throw ex;
			}

		}

		private void InitModel(Corpora cor)
		{
			this.cor = cor;

			M = cor.totalDocuments;
			V = cor.MaxWordID();

			p = new double[K];
			Random rnd = new Random();

			nw = new int[V][];
			nd = new int[M][];
			for (int w = 0; w < V; w++)
			{
				nw[w] = new int[K];
			}
			for (int m = 0; m < M; m++)
			{
				nd[m] = new int[K];
			}

			nwsum = new int[K];
			ndsum = new int[M];

			words = new int[cor.totalWords];
			doc = new int[cor.totalWords];
			z = new int[cor.totalWords];
			wn = 0;
			for (int i = 0; i < M; i++)
			{
				int l = cor.Docs[i].Length;
				for (int j = 0; j < l; j++)
				{
					words[wn] = cor.Docs[i].Words[j];
					doc[wn] = i;
					wn++;
				}
				ndsum[i] = l;
			}
			for (int i = 0; i < wn; i++)
			{

				int topic = rnd.Next(K);
				nw[words[i]][topic] += 1;
				nd[doc[i]][topic] += 1;
				nwsum[topic] += 1;
				z[i] = topic;
			}

			theta = new double[M][];
			for (int m = 0; m < M; m++)
			{
				theta[m] = new double[K];
			}
			phi = new double[K][];
			for (int k = 0; k < K; k++)
			{
				phi[k] = new double[V];
			}

		}

		public LDA TrainNewModel(Corpora cor, CommandLineOption opt)
		{
			var getLda = new LDA();
			InitOption(opt);
			InitModel(cor);
			PrintModelInfo();
			GibbsSampling(niters);
			getLda.alpha = alpha;
			getLda.beta = beta;
			getLda.K = K;
			getLda.V = V;
			getLda.M = M;
			getLda.phi = phi;
			getLda.theta = theta;
			getLda.logLikelihood = LogLikelihood;
			return getLda;
		}

		public void PrintModelInfo()
		{
			//Console.WriteLine("Aplha: " + alpha.ToString());
			//Console.WriteLine("Beta: " + beta.ToString());
			//Console.WriteLine("M: " + M);
			//Console.WriteLine("K: " + K);
			//Console.WriteLine("V: " + V);
			//Console.WriteLine("Total iterations:" + niters);
			//Console.WriteLine("Save at: " + savestep);
			//Console.WriteLine();
		}

		private void GibbsSampling(int totalIter)
		{
			for (int iter = 1; iter <= totalIter; iter++)
			{
				//Console.Write("Iteration " + iter + ":");
				var stopWatch = new Stopwatch();
				stopWatch.Start();
				for (int i = 0; i < wn; i++)
				{
					int topic = DoSampling(i);
					z[i] = topic;
				}

				stopWatch.Stop();
				//Console.WriteLine(stopWatch.ElapsedMilliseconds / 1000.0 + " seconds");
				if (iter % savestep == 0)
				{
					//保存参数、文档分布等
					CalcParameter();
					LDA GetLda = new LDA();
					int getM = GetLda.M;
					int getV = GetLda.V;
					int getK = GetLda.K;
					double getalpha = GetLda.alpha;
					double getbeta = GetLda.beta;
					double getLogLikelihood = GetLda.LogLikelihood;
					double[][] gettheta = GetLda.theta;

					int tw = twords > V ? V : twords;
					//保存主题词分布
					for (int k = 0; k < K; k++)
					{
						var wordsProbsList = new Dictionary<int, double>();

						for (int w = 0; w < V; w++)
						{
							wordsProbsList.Add(w, phi[k][w]);
						}

						double ans = 0;
						for (int w = 0; w < V; w++)
						{
							ans += phi[k][w];
						}
						if (Math.Abs(ans - 1.00) > 0.1)
						{
							throw (new Exception("Phi Calculation Error"));
						}
						//主题编号
						//sw.Write("Topic " + k + "th:\n");
						//Console.WriteLine("Topic " + k + "th:\n");
						//主题词
						var wordsProbsListOrdered = wordsProbsList.OrderBy(e => -e.Value).ToList();

						for (int i = 0; i < tw; i++)
						{
							string word = cor.GetStringByID(wordsProbsListOrdered[i].Key);
							//	sw.WriteLine("\t" + word + " " + wordsProbsListOrdered[i].Value);
							//Console.WriteLine("\t" + word + " " + wordsProbsListOrdered[i].Value);
						}

					}
					//Console.WriteLine("LogLikelihood= " + LogLikelihood);
				}
			}
		}

		private int DoSampling(int i)
		{
			int oldZ = z[i];
			int w = words[i];
			int m = doc[i];

			nw[w][oldZ] -= 1;
			nd[m][oldZ] -= 1;
			nwsum[oldZ] -= 1;
			ndsum[m] -= 1;

			double Vbeta = V * beta;
			double Kalpha = K * alpha;
			for (int k = 0; k < K; k++)
			{
				p[k] = (nw[w][k] + beta) / (nwsum[k] + Vbeta) * (nd[m][k] + alpha) / (ndsum[m] + Kalpha);
			}
			for (int k = 1; k < K; k++)
			{
				p[k] += p[k - 1];
			}
			Random rnd = new Random();
			double cp = rnd.NextDouble() * p[K - 1];

			int newZ;
			for (newZ = 0; newZ < K; newZ++)
			{
				if (p[newZ] > cp)
				{
					break;
				}
			}
			if (newZ == K) newZ--;
			nw[w][newZ] += 1;
			nd[m][newZ] += 1;
			nwsum[newZ] += 1;
			ndsum[m] += 1;
			return newZ;
		}

		//存数据库
		public void SaveModel()
		{	
			//string jstr = GetJSONString();
			//using (System.IO.StreamWriter sw = new System.IO.StreamWriter(modelpath))
			//{
			//	sw.WriteLine(jstr);
			//}

		}

		public void LoadModel(string modelpath)
		{

		}
		//存数据库
		public void SaveTopWords()
		{
			
		}

		protected void CalcParameter()
		{
			for (int m = 0; m < M; m++)
			{
				for (int k = 0; k < K; k++)
				{
					theta[m][k] = (nd[m][k] + alpha) / (ndsum[m] + K * alpha);
				}
			}

			for (int k = 0; k < K; k++)
			{
				for (int w = 0; w < V; w++)
				{
					phi[k][w] = (nw[w][k] + beta) / (nwsum[k] + V * beta);
				}
			}
		}
	}
}
