﻿using System.Collections.Generic;
using System.IO;
using System.Linq;
using Gamelogic.Diagnostics;
//using Gamelogic.Experimental;
using Gamelogic.Grids;
using Gamelogic.Words;
using Gamelogic.Words.Dictionaries;
using Gamelogic.Words.Examples;
using UnityEngine;

namespace Gamelogic.Word.Examples
{
	public class WordWorm : GridBehaviour<FlatHexPoint>
	{
		public TextAsset dictionaryFile;

		private FlatHexGrid<LetterCell> letterGrid;
		private IGenerator<string> generator;
		private TrieDictionary dictionary;
		private FlatHexPoint? lastClickedPoint;
		private List<FlatHexPoint> currentWord;
		private TrieDictionary dictionary2;
		private string[] test1;
		private Dictionary<string, int>[] test2;
		private List<string> test3; 

		public override void InitGrid()
		{
			letterGrid = (FlatHexGrid<LetterCell>) Grid.CastValues<LetterCell, FlatHexPoint>();
		
			var alphabet = new LatinAlphabet();
			generator = new LetterGenerator(alphabet, LatinAlphabet.letterPairFrequencies)
				.Select(s => s.ToUpper());
			dictionary = new TrieDictionary(alphabet);
			currentWord = new List<FlatHexPoint>();

			if (dictionaryFile != null)
			{
				dictionary.LoadDictionary(new MemoryStream(dictionaryFile.bytes));
			}

			Reset();
		}

		public void Reset()
		{
			currentWord.Clear();
			lastClickedPoint = null;

			int cellCount = letterGrid.Count();
		
			var path = letterGrid.GetSpiralIterator(GridBuilder.Size).ToList();

			for (int i = 0; i < cellCount; i++)
			{
				if (i%5 == 0) path.Reverse();

				path = MakeRandomBackbite(letterGrid, path).ToList();
			}

			foreach (var point in path)
			{
				letterGrid[point].Text = generator.Next();
				letterGrid[point].SetStateOff();
				letterGrid[point].Used = false;
			}
		}

		public void Hint()
		{
			var validPoints = letterGrid.WhereCell(c => !c.Used).ToList();
			validPoints.Shuffle();

			foreach (var point in validPoints)
			{
				var solution = FindWordAt(point);

				if (solution.Any())
				{
					ClearWord();
					currentWord = solution.ToList();
					UpdateCellStatesOn(currentWord);
					CheckCurrentWord();

					return;
				}
			}
		}

		public IEnumerable<FlatHexPoint> FindWordAt(FlatHexPoint point)
		{
			var solutions = Backtrack.FindLongestValid(
				point,
				p => letterGrid.GetNeighbors(p).Where(q => !letterGrid[q].Used),
				points => dictionary.Search(WordFromPoints(points)).Any(word => word.Length >= 3),
				points => LongerWordsExist(WordFromPoints(points))
				);

			if(!solutions.Any()) return new List<FlatHexPoint>();
			return solutions.MaxBy(s => s.Count()); ;
		}

		public bool LongerWordsExist(string word)
		{
			return dictionary.Search(word + "+*").Any();
		}

		public void FindAnagram()
		{
			string word = WordFromPoints(currentWord);

			var anagrams = dictionary.FindAnagrams(word);

			if (anagrams.Any())
			{
				var anagram = anagrams.First();
			
				for (int i = 0; i < anagram.Length; i++)
				{
					letterGrid[currentWord[i]].Text = anagram[i].ToUpper();
				}
			}

			CheckCurrentWord();
		}

		private void UpdateCellStatesOff()
		{
			foreach (var point in currentWord)
			{
				if (!letterGrid[point].Used)
				{
					letterGrid[point].SetStateOff();
				}
			}
		}

		private void UpdateCellStatesOn(IList<FlatHexPoint> list)
		{
			GLDebug.Assert(list.Count > 0, "This method should only be called when the current word is not empty");

			if (list.Count == 1)
			{
				letterGrid[list[0]].SetStateOn();
				return;
			}

			int last = list.Count - 1;

			letterGrid[list[0]].SetStateOn(
				new List<FlatHexPoint> { list[1] - list[0] });

			for (int i = 1; i < last; i++)
			{
				letterGrid[list[i]].SetStateOn(
					new List<FlatHexPoint>
					{
						list[i - 1] - list[i],
						list[i + 1] - list[i]
					});
			}

			letterGrid[list[last]].SetStateOn(
				new List<FlatHexPoint> { list[last - 1] - list[last] });

		}

		public void OnClick(FlatHexPoint clickedPoint)
		{
			if (letterGrid[clickedPoint].Used) return;
		
			if (!lastClickedPoint.HasValue)
			{
				lastClickedPoint = clickedPoint;
				currentWord.Add(clickedPoint);
				UpdateCellStatesOn(currentWord);
				return;
			}

			if (clickedPoint == lastClickedPoint)
			{
				CheckCurrentWord();
				return;
			}

			if (clickedPoint.DistanceFrom(lastClickedPoint.Value) == 1)
			{
				lastClickedPoint = clickedPoint;
				currentWord.Add(clickedPoint);
				UpdateCellStatesOn(currentWord);
				return;
			}

			ClearWord();
		}

		private void CheckCurrentWord()
		{
			if (IsWord(WordFromPoints(currentWord)))
			{
				foreach (var point in currentWord)
				{
					letterGrid[point].Used = true;
				}
			}

			ClearWord();
		}

		private void ClearWord()
		{
			lastClickedPoint = null;
			UpdateCellStatesOff();
			currentWord.Clear();
		}

		private string WordFromPoints(IEnumerable<FlatHexPoint> list)
		{
			return list.Aggregate("", (current, point) => current + letterGrid[point].Text);
		}

		public bool IsWord(string word)
		{
			return dictionary.Search(word).Any();
		}

		private static IEnumerable<FlatHexPoint> MakeRandomBackbite<TCell>(IGrid<TCell, FlatHexPoint> grid, IList<FlatHexPoint> hamiltonionPath)
		{
			var neighbor = grid.GetNeighbors(hamiltonionPath.Last()).RandomItem();
			int backbiteIndex = hamiltonionPath.IndexOf(neighbor);
			var startPath = hamiltonionPath.Take(backbiteIndex+1);
			var endPath = hamiltonionPath.Skip(backbiteIndex+1).Reverse();

			return startPath.Concat(endPath);
		}
	}
}