using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace DataClients
{
	public class NetClient
	{
		private string ServerStr = "10.10.45.151";
		private int PortStr = 1070;
		private IPEndPoint IpPoint;

		public enum Cmd : uint
		{
			check_command = 4294967295,
			version = 0,
			pasp_download = 4,
			download_nh = 21,
			dir_browse = 23,
			user_flags = 26
		}

		//constructor
		public NetClient()
		{
			IpPoint = new IPEndPoint(IPAddress.Parse(ServerStr), PortStr);
		}
		public NetClient(string ipAddr, int port = 1070)
		{
			ServerStr = ipAddr;
			PortStr = port;
			IpPoint = new IPEndPoint(IPAddress.Parse(ServerStr), PortStr);
		}

		//main functions
		public byte[] CreateCommand(Cmd cmd, string val = null)
		{
			List<byte> result = new List<byte>();
			result.AddRange(BitConverter.GetBytes((uint)cmd));
			if (cmd == Cmd.dir_browse) result.Add(0x00);
			if (cmd == Cmd.dir_browse && String.IsNullOrEmpty(val))
				result.AddRange(new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00 });
			if (!String.IsNullOrEmpty(val))
			{
				result.AddRange(BitConverter.GetBytes((uint)val.Length));
				result.AddRange(Encoding.ASCII.GetBytes(val));
				result.Add(0x00);
			}

			return result.ToArray();
		}
		public byte[][] SocketWork(Cmd code, string val = "")
		{
			var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
			{
				ReceiveTimeout = 5000,
				SendTimeout = 5000
			};
			var result = new List<byte[]>();

			//Install Params
			var check_start_mark = true;
			var check_end_mark = true;
			uint static_size = 0;

			switch (code)
			{
				case Cmd.version:
					check_start_mark = false;
					break;
				case Cmd.check_command:
					check_end_mark = false;
					static_size = 4;
					break;
				case Cmd.pasp_download:
					check_end_mark = false;
					static_size = 1528;
					break;
				case Cmd.dir_browse:
				case Cmd.download_nh:
				case Cmd.user_flags:
					check_end_mark = true;
					static_size = 0;
					break;
			}

			try
			{
				//Connect
				socket.Connect(IpPoint);
				//SendRequest
				Thread.Sleep(5);
				socket.Send(CreateCommand(code, val));
				
				uint? fullSize = null;
				byte mark = 0x01;

				if (check_start_mark) mark = SocketReadByte(ref socket);
				while(mark != 0xff)
				{
					switch (mark)
					{
						case 0x01:
							var tmpL = new List<byte>();
							if (code == Cmd.pasp_download)
							{
								tmpL.AddRange(SocketReadBytes(ref socket, 16));
								if (tmpL.Last() == 0x01)
									tmpL.AddRange(SocketReadBytes(ref socket, 1512));
								mark = 0xff;
								result.Add(tmpL.ToArray());
								break;
							}

							{
								uint size = (static_size > 0) ?
								static_size : BitConverter.ToUInt32(SocketReadBytes(ref socket, 4), 0);
								tmpL.AddRange(SocketReadBytes(ref socket, size));
								mark = 0xff;
								result.Add(tmpL.ToArray());
								break;
							}
						case 0x02:
							var currSize = BitConverter.ToUInt32(SocketReadBytes(ref socket, 4), 0);
							fullSize = fullSize.HasValue ? fullSize + currSize : currSize;
							break;
						case 0x03:
							Thread.Sleep(1000);
							if (check_start_mark) mark = SocketReadByte(ref socket);
							continue;
						case 0x00:
							if (!check_end_mark)
								throw new Exception("Can't catch all packeges.");
							if (result.Count == 0 || code == Cmd.version)
								return result.ToArray();
							mark = 0xff;
							break;
						case 0xff:
							if (!check_end_mark || !fullSize.HasValue || fullSize.Value != result.Count)
								throw new Exception("Can't catch all packeges.");
							return result.ToArray();
						default:
							throw new Exception("Can't get mark for download.");
					}
					if (check_end_mark) mark = SocketReadByte(ref socket);
				}
				return result.ToArray();
			}
			catch (Exception e)
			{
				Console.WriteLine(e.Message);
				Console.WriteLine(e.StackTrace);
				return result.ToArray();
			}
			finally
			{
				socket.Disconnect(false);
			}
		}

		private byte SocketReadByte(ref Socket socket)
		{
			try
			{
				byte[] t = new byte[1];
				t[0] = 0xff;
				Thread.Sleep(5);
				int countbyte = socket.Receive(t);
				if (countbyte != 1)
					throw new Exception("Error get Byte.");
				return t[0];
			}
			catch (Exception e)
			{
				Console.WriteLine(e.Message);
				Console.WriteLine(e.StackTrace);
				return 0xff;
			}
		}
		private byte[] SocketReadBytes(ref Socket socket, uint length)
		{
			try
			{
				var result = new List<byte>();
				var count = 5;
				do
				{
					byte[] t = new byte[length];
					Thread.Sleep(5);
					int countbyte = socket.Receive(t);
					if (countbyte != length)
					{
						if (countbyte > 0)
						{
							var c = new byte[countbyte];
							Array.Copy(t, c, countbyte);
							result.AddRange(t);
							length = length - (uint)countbyte;
							count = 5;
							continue;
						}
						else
						{
							count--;
							Thread.Sleep(5);
						}
					}
					result.AddRange(t);						
					return result.ToArray();
				} while (count >= 0);
				throw new Exception("Error get Bytes.");
			}
			catch (Exception e)
			{
				Console.WriteLine(e.Message);
				Console.WriteLine(e.StackTrace);
				return new byte[0];
			}
		}
	}
}
