using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Text;
using SanAndreasUnity.Utilities;

namespace SanAndreasUnity.Importing.GXT
{
	public class GXT : IDisposable
	{

		#region LoadPath
		private static string _language= "american";
		public static string Language
		{
			get => _language;
			set
			{
				var path = GetPathFromLanguage(value);
				if (File.Exists(path))
				{
					_language = value;
				}
			}
		}

		public static string GXTDir => Path.Combine(Config.GamePath, "text");
		private static string GetPathFromLanguage(string language) => Path.Combine(GXTDir, $"{language}.gxt");
		private static string LoadPath => GetPathFromLanguage(_language);

		#endregion

		#region GXTData

		private Int16 _version;
		public List<string> SubTableNames { get; } = new List<string>();
		public Dictionary<string, List<int>> TableEntryNameDict { get; } = new Dictionary<string, List<Int32>>();
		public Dictionary<int, string> EntryNameWordDict { get; } = new Dictionary<int, string>();

		private static GXT _current;
		public static GXT Current
		{
			get
			{
				return _current;
			}
		}

		private MemoryStream _rawData;

		#endregion

		public GXT(string fp)
		{
			var bytes = File.ReadAllBytes(LoadPath);
			_rawData = new MemoryStream(bytes, false);
		}

		public static void Load()
		{
			if (_current != null)
			{
				return;
			}
			_current = new GXT(LoadPath);
			_current.InternalLoad();
			_current._rawData.Dispose();
		}


		private void InternalLoad()
		{
			var encoding = LoadHeader();
			var tkeyEntryOffsets = LoadTableBlock(encoding);

			for (var i = 0; i < SubTableNames.Count; i++)
			{
				var subTableName = SubTableNames[i];
				_rawData.Seek(tkeyEntryOffsets[i], SeekOrigin.Begin);
				using (var binReader = new BinaryReader(_rawData, encoding, true))
				{
					if (i == 0) //parse tkey directly
					{
						LoadTKEY(binReader, subTableName);
					}
					else //skip tkeyname
					{
						_rawData.Seek(8, SeekOrigin.Current);
						LoadTKEY(binReader, subTableName);
					}
				}
			}
		}

		private void LoadTKEY(BinaryReader binaryReader, string tableName)
		{
			//Console.WriteLine(nameof(LoadTKEY));
			var tkey = new string(binaryReader.ReadChars(4));
			var blockSize = binaryReader.ReadInt32();

			//Console.WriteLine($"tkey:{tkey} blockSize:{blockSize}");
			if (tkey != "TKEY")
			{
				throw new ConstraintException();
			}

			var entryOffsets = new List<Int32>();
			var tdataEntryCount = blockSize / 8;
			var entryNames = new List<Int32>();
			for (int i = 0; i < tdataEntryCount; i++)
			{

				var entryOffset = binaryReader.ReadInt32();
				var crc32 = binaryReader.ReadInt32();
				entryNames.Add(crc32);
				entryOffsets.Add(entryOffset);
			}

			TableEntryNameDict[tableName] = entryNames;

			//read tdat
			var tdat = new string(binaryReader.ReadChars(4));
			if (tdat != "TDAT")
			{
				throw new ConstraintException();
			}
			var tdatBlockSize = binaryReader.ReadInt32();
			var bytes = binaryReader.ReadBytes(tdatBlockSize);
			var wordBytes = new List<byte>();
			for (int i = 0; i < entryOffsets.Count; i++)
			{
				var entryName = entryNames[i];
				//todo this should be double checked; I think it could be used a lib method.
				//but not familiar with relevant api. 
				for (int j = entryOffsets[i]; ; j++)
				{
					var c = Convert.ToChar(bytes[j]);
					if (c == '\0')
					{
						break;
					}
					wordBytes.Add(bytes[j]);
				}
				EntryNameWordDict[entryName] = Win1252ToString(wordBytes.ToArray());
				wordBytes.Clear();
			}
		}

		private string Win1252ToString(byte[] bytes)
		{
			Encoding win1252 = Encoding.GetEncoding(1252);
			var utf8bytes = Encoding.Convert(win1252, Encoding.UTF8, bytes);
			return Encoding.UTF8.GetString(utf8bytes);
		}

		private Encoding LoadHeader()
		{
			using (var binReader = new BinaryReader(_rawData, Encoding.ASCII, true))
			{
				_version = binReader.ReadInt16();
				var bitsPerChar = binReader.ReadInt16();
				if (bitsPerChar == 8)
				{
					return Encoding.ASCII;
				}
				else if (bitsPerChar == 16)
				{
					return Encoding.Unicode;
				}
			}
			return Encoding.ASCII;
		}

		private List<Int32> LoadTableBlock(Encoding encoding)
		{
			using (var binReader = new BinaryReader(_rawData, encoding, true))
			{
				var offsetList = new List<Int32>();
				var tablConst = binReader.ReadChars(4);
				if (new string(tablConst) != "TABL")
				{
					throw new ConstraintException();
				}

				var blockSize = binReader.ReadInt32();
				var entryCount = blockSize / 12;
				for (int i = 0; i < entryCount; i++)
				{
					var subtableName = new string(binReader.ReadChars(8));
					var offset = binReader.ReadInt32();

					//Console.WriteLine($"subtablename:{subtableName} offset:{offset}");
					SubTableNames.Add(subtableName);
					offsetList.Add(offset);
				}

				return offsetList;
			}
		}


		public void Dispose()
		{
			if (_rawData != null)
			{
				_rawData.Dispose();
				_rawData = null;
			}
		}
	}
}
