﻿using System;
using System.IO;
using TLSharp.Core.MTProto;
using TLSharp.Core.MTProto.Crypto;

namespace TLSharp.Core
{
	public interface ISessionStore
	{
		void Save(Session session);
		Session Load(string sessionUserId);
	}

	public class FileSessionStore : ISessionStore
	{
		public void Save(Session session)
		{
			using (var stream = new FileStream($"{session.SessionUserId}.dat", FileMode.OpenOrCreate))
			{
				var result = session.ToBytes();
				stream.Write(result, 0, result.Length);
			}
		}

		public Session Load(string sessionUserId)
		{
			using (var stream = new FileStream($"{sessionUserId}.dat", FileMode.Open))
			{
				var buffer = new byte[2048];
				stream.Read(buffer, 0, 2048);

				return Session.FromBytes(buffer, this, sessionUserId);
			}
		}
	}

	public class FakeSessionStore : ISessionStore
	{
		public void Save(Session session)
		{
			
		}

		public Session Load(string sessionUserId)
		{
			throw new NotImplementedException();
		}
	}

	public class Session
	{
		private const string defaultConnectionAddress = "91.108.56.165";
		private const int defaultConnectionPort = 443;

		public string SessionUserId { get; set; }
		public string ServerAddress { get; set; }
		public int Port { get; set; }
		public AuthKey AuthKey { get; set; }
		public ulong Id { get; set; }
		public int Sequence { get; set; }
		public ulong Salt { get; set; }
		public int TimeOffset { get; set; }
		public long LastMessageId { get; set; }
		public int SessionExpires { get; set; }
		public User User { get; set; }
		private Random random;

		private ISessionStore _store;

		private Session(ISessionStore store)
		{
			random = new Random();
			_store = store;
		}

		public byte[] ToBytes()
		{
			using (var stream = new MemoryStream())
			using (var writer = new BinaryWriter(stream))
			{
				writer.Write(Id);
				writer.Write(Sequence);
				writer.Write(Salt);
				writer.Write(LastMessageId);
				writer.Write(TimeOffset);
				Serializers.String.write(writer, ServerAddress);
				writer.Write(Port);

				if (User != null)
				{
					writer.Write(1);
					writer.Write(SessionExpires);
					User.Write(writer);
				}
				else
				{
					writer.Write(0);
				}

				Serializers.Bytes.write(writer, AuthKey.Data);

				return stream.ToArray();
			}
		}

		public static Session FromBytes(byte[] buffer, ISessionStore store, string sessionUserId)
		{
			using (var stream = new MemoryStream(buffer))
			using (var reader = new BinaryReader(stream))
			{
				var id = reader.ReadUInt64();
				var sequence = reader.ReadInt32();
				var salt = reader.ReadUInt64();
				var lastMessageId = reader.ReadInt64();
				var timeOffset = reader.ReadInt32();
				var serverAddress = Serializers.String.read(reader);
				var port = reader.ReadInt32();

				var isAuthExsist = reader.ReadInt32() == 1;
				int sessionExpires = 0;
				User user = null;
				if (isAuthExsist)
				{
					sessionExpires = reader.ReadInt32();
					user = Tl.Parse<User>(reader);
				}

				var authData = Serializers.Bytes.read(reader);

				return new Session(store)
				{
					AuthKey = new AuthKey(authData),
					Id = id,
					Salt = salt,
					Sequence = sequence,
					LastMessageId = lastMessageId,
					TimeOffset = timeOffset,
					SessionExpires = sessionExpires,
					User = user,
					SessionUserId = sessionUserId,
					ServerAddress = serverAddress,
					Port = port
				};
			}
		}

		public void Save()
		{
			_store.Save(this);
		}

		public static Session TryLoadOrCreateNew(ISessionStore store, string sessionUserId)
		{
			Session session;

			try
			{
				session = store.Load(sessionUserId);
			}
			catch
			{
				session = new Session(store)
				{
					Id = GenerateRandomUlong(),
					SessionUserId = sessionUserId,
					ServerAddress = defaultConnectionAddress,
					Port = defaultConnectionPort
				};
			}

			return session;
		}

		private static ulong GenerateRandomUlong()
		{
			var random = new Random();
			ulong rand = (((ulong)random.Next()) << 32) | ((ulong)random.Next());
			return rand;
		}

		public long GetNewMessageId()
		{
			long time = Convert.ToInt64((DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds);
			long newMessageId = ((time / 1000 + TimeOffset) << 32) |
								((time % 1000) << 22) |
								(random.Next(524288) << 2); // 2^19
															// [ unix timestamp : 32 bit] [ milliseconds : 10 bit ] [ buffer space : 1 bit ] [ random : 19 bit ] [ msg_id type : 2 bit ] = [ msg_id : 64 bit ]

			if (LastMessageId >= newMessageId)
			{
				newMessageId = LastMessageId + 4;
			}

			LastMessageId = newMessageId;
			return newMessageId;
		}
	}
}
