using ICSharpCode.SharpZipLib.Zip;
using System;
using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Text;

namespace HiImRay
{
	public class Docxument
	{
		protected string Filename { get; set; }

		public int CompressionLevel { get; set; } = 9;
		public Dictionary<string, string> ReplacementWords = new Dictionary<string, string>();
		public Dictionary<string, List<Dictionary<string, string>>> ReplacementParagraphs = new Dictionary<string, List<Dictionary<string, string>>>();

		public Docxument()
		{
		}

		public Docxument(string filename)
		{
			Load(filename);
		}

		public void Load(string filename)
		{
			this.Filename = filename;
		}

		public void Save(string saveFilename)
		{
			int size = 2048;
			byte[] buffer = new byte[size];
			int bufferSize = 0;

			// read file
			ZipEntry readEntry = null;
			ZipInputStream readStream = new ZipInputStream(File.OpenRead(this.Filename));

			// write file
			ZipOutputStream writeStream = new ZipOutputStream(File.Create(saveFilename));
			writeStream.SetLevel(this.CompressionLevel);						

			// loop
			while (true)
			{
				readEntry = readStream.GetNextEntry();
				if (readEntry == null)
				{
					break;
				}

				if (string.IsNullOrWhiteSpace(readEntry.Name))
				{
					break;
				}

				if (!readEntry.IsFile)
				{
					continue;
				}

				// change document
				if (readEntry.Name.Equals("word/document.xml"))
				{
					MemoryStream ms = new MemoryStream();
					buffer = new byte[size];
					bufferSize = 0;

					do
					{
						bufferSize = readStream.Read(buffer, 0, buffer.Length);
						ms.Write(buffer, 0, bufferSize);
					} while (bufferSize > 0);

					ms.Position = 0;

					StreamReader sr = new StreamReader(ms);
					StringBuilder sb = new StringBuilder();
					sb.Append(sr.ReadToEnd());

					// make changes
					sb = replaceTokens(sb, this.ReplacementWords);
					sb = replaceParagraphs(sb, this.ReplacementParagraphs);

					// make readable
					MemoryStream ms2 = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(sb.ToString()));
					ms2.Position = 0;

					// write new document xml
					writeStream.PutNextEntry(new ZipEntry(readEntry.Name));

					buffer = new byte[size];
					bufferSize = 0;

					do
					{
						bufferSize = ms2.Read(buffer, 0, buffer.Length);
						writeStream.Write(buffer, 0, bufferSize);
					} while (bufferSize > 0);

					ms2.Close();
					ms2.Dispose();
				}
				else
				{
					writeStream.PutNextEntry(new ZipEntry(readEntry.Name));

					buffer = new byte[size];
					bufferSize = 0;

					do
					{
						bufferSize = readStream.Read(buffer, 0, buffer.Length);
						writeStream.Write(buffer, 0, bufferSize);
					} while (bufferSize > 0);
				}
			}
						
			writeStream.Finish();
			writeStream.Flush();
			writeStream.Close();
			writeStream.Dispose();

			readStream.Close();
			readStream.Dispose();
		}

		protected StringBuilder replaceTokens(StringBuilder sb, Dictionary<string, string> options)
		{
			foreach (KeyValuePair<string, string> item in options)
			{
				sb.Replace("{{" + item.Key + "}}", SecurityElement.Escape(item.Value));
			}

			return sb;
		}

		protected StringBuilder replaceParagraphs(StringBuilder sb, Dictionary<string, List<Dictionary<string, string>>> options)
		{
			foreach (KeyValuePair<string, List<Dictionary<string, string>>> item in options)
			{
				sb = replaceParagraph(sb, item);
			}

			return sb;
		}
	
		protected StringBuilder replaceParagraph(StringBuilder sb, KeyValuePair<string, List<Dictionary<string, string>>> options)
		{		
			string paragraph = sb.ToString();

			Tuple<int, int> outerCoord = getOuterParagraph(paragraph, "{{" + options.Key + "}}");
			if (outerCoord != null)
			{
				sb.Remove(outerCoord.Item1, outerCoord.Item2);

				Tuple<int, int> innerCoord = getInnerParagraph(paragraph.Substring(outerCoord.Item1, outerCoord.Item2), "{{" + options.Key + "}}");
				string innerParagraph = paragraph.Substring((innerCoord.Item1 + outerCoord.Item1), innerCoord.Item2);

				StringBuilder innerText = new StringBuilder();
				foreach (Dictionary<string, string> row in options.Value)
				{
					StringBuilder sb2 = new StringBuilder();
					sb2.Append(innerParagraph);

					sb2 = replaceTokens(sb2, row);

					innerText.Append(sb2.ToString());
				}

				sb.Insert(outerCoord.Item1, innerText.ToString());
			}

			return sb;
		}

		protected Tuple<int, int> getOuterParagraph(string fullText, string findTerm)
		{
			string headTerm = "<w:p ";
			string tailTerm = "</w:p>";

			int headIndex = fullText.IndexOf(findTerm);
			if (headIndex < 0)
			{
				return null;
			}

			int tailIndex = fullText.IndexOf(findTerm, (headIndex + findTerm.Length));
			if (tailIndex < 0)
			{
				return null;
			}

			headIndex = fullText.LastIndexOf(headTerm, headIndex);
			if (headIndex < 0)
			{
				return null;
			}

			tailIndex = fullText.IndexOf(tailTerm, (tailIndex + tailTerm.Length));
			if (tailIndex < 0)
			{
				return null;
			}

			tailIndex += tailTerm.Length;

			return new Tuple<int, int>(headIndex, (tailIndex - headIndex));
		}

		protected Tuple<int, int> getInnerParagraph(string fullText, string findTerm)
		{
			string headTerm = "<w:p ";
			string tailTerm = "</w:p>";

			int headIndex = fullText.IndexOf(findTerm);
			if (headIndex < 0)
			{
				return null;
			}

			int tailIndex = fullText.IndexOf(findTerm, (headIndex + findTerm.Length));
			if (tailIndex < 0)
			{
				return null;
			}

			headIndex = fullText.IndexOf(tailTerm, headIndex);
			if (headIndex < 0)
			{
				return null;
			}

			headIndex += tailTerm.Length;

			tailIndex = fullText.LastIndexOf(headTerm, tailIndex);
			if (tailIndex < 0)
			{
				return null;
			}

			return new Tuple<int, int>(headIndex, (tailIndex - headIndex));
		}
	}
}
