using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.IO;
using System.Security.Cryptography;
using System.Data;
using System.Text.RegularExpressions;
using System.Net.Sockets;
using System.Globalization;
using System.Threading;
using System.Diagnostics;
using System.Net;

using SystemX.Net.BaseProtocol;

namespace SystemX.Net.Middleware.Commons
{

    public static class COMMON
    {
        [DllImport("winmm.dll")]
        public static extern uint timeGetTime();

        public const string Png = "PNG Portable Network Graphics (*.png)|" + "*.png";
        public const string Jpg = "JPEG File Interchange Format (*.jpg *.jpeg *jfif)|" + "*.jpg;*.jpeg;*.jfif";
        public const string Bmp = "BMP Windows Bitmap (*.bmp)|" + "*.bmp";
        public const string Tif = "TIF Tagged Imaged File Format (*.tif *.tiff)|" + "*.tif;*.tiff";
        public const string Gif = "GIF Graphics Interchange Format (*.gif)|" + "*.gif";
        public const string AllImages = "Image file|" + "*.png; *.jpg; *.jpeg; *.jfif; *.bmp;*.tif; *.tiff; *.gif";
        public const string AllFiles = "All files (*.*)" + "|*.*";

        public static string[] mediaExtensions = {
        ".PNG", ".JPG", ".JPEG", ".BMP", ".GIF", //etc
        ".WAV", ".MID", ".MIDI", ".WMA", ".MP3", ".OGG", ".RMA", //etc
        ".AVI", ".MP4", ".DIVX", ".WMV", //etc 
        };

        public static DataTable ConvertCSVtoDataTable(string strFilePath, string strSetHeader)
        {
            DataTable dt = null;
            try
            {
                using (StreamReader sr = new StreamReader(strFilePath))
                {
                    string[] headers = null;

                    if(strSetHeader.Length > 0)
                        headers = strSetHeader.Split(',');
                    else
                        headers = sr.ReadLine().Split(',');

                    dt = new DataTable();
                    foreach (string header in headers)
                    {
                        dt.Columns.Add(header);
                    }
                    while (!sr.EndOfStream)
                    {
                        string strReadData = sr.ReadLine().Trim();
                        if (strReadData.Length <= 0)
                            break;

                        string[] rows = Regex.Split(strReadData, ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
                        DataRow dr = dt.NewRow();
                        for (int i = 0; i < headers.Length; i++)
                        {
                            dr[i] = rows[i];
                        }
                        dt.Rows.Add(dr);
                    }
                }
            }
            catch (Exception e)
            {
                dt = null;
            }

            return dt;
        }

        public static T ConvertTextToTryValue<T>(string strText, object objFailValue)
        {
            object obj;
            obj = typeof(T);

            int iGetValue = 0;
            uint uiGetValue = 0;
            double dGetValue = 0;

            if (obj.ToString().IndexOf("Int") >= 0)
            {
                if (!int.TryParse(strText, out iGetValue))
                    obj = objFailValue;
                else
                    obj = iGetValue;
            }
            if (obj.ToString().IndexOf("UInt") >= 0)
            {
                if (!uint.TryParse(strText, out uiGetValue))
                    obj = objFailValue;
                else
                    obj = uiGetValue;
            }
            else if (obj.ToString().IndexOf("Double") >= 0)
            {
                if (!double.TryParse(strText, out dGetValue))
                    obj = objFailValue;
                else
                    obj = dGetValue;
            }

            return (T)Convert.ChangeType(obj, typeof(T));
        }

        public static T FindByName<T>(this object targetClass, string name) where T : class
        {
            System.Reflection.FieldInfo fi = targetClass.GetType().GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);

            return fi.GetValue(targetClass) as T;
        }
        public static T FindByName<T>(this string name, object targetClass) where T : class
        {
            System.Reflection.FieldInfo fi = targetClass.GetType().GetField(name, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);

            return fi.GetValue(targetClass) as T;
        }

        [DllImport("KERNEL32.DLL", EntryPoint = "RtlZeroMemory")]
        public static extern bool ZeroMemory(IntPtr Destination, int Length);

        public static string GenerateKey()
        {
            // Create an instance of Symetric Algorithm. Key and IV is generated automatically.
            DESCryptoServiceProvider desCrypto = (DESCryptoServiceProvider)DESCryptoServiceProvider.Create();

            // Use the Automatically generated key for Encryption. 
            return ASCIIEncoding.ASCII.GetString(desCrypto.Key);
        }
        public static void EncryptFile(string sInputFilename,
         string sOutputFilename,
         string sKey)
        {
            FileStream fsInput = new FileStream(sInputFilename,
               FileMode.Open,
               FileAccess.Read);

            FileStream fsEncrypted = new FileStream(sOutputFilename,
               FileMode.Create,
               FileAccess.Write);
            DESCryptoServiceProvider DES = new DESCryptoServiceProvider();
            DES.Key = ASCIIEncoding.ASCII.GetBytes(sKey);
            DES.IV = ASCIIEncoding.ASCII.GetBytes(sKey);
            ICryptoTransform desencrypt = DES.CreateEncryptor();
            CryptoStream cryptostream = new CryptoStream(fsEncrypted,
               desencrypt,
               CryptoStreamMode.Write);

            byte[] bytearrayinput = new byte[fsInput.Length];
            fsInput.Read(bytearrayinput, 0, bytearrayinput.Length);
            cryptostream.Write(bytearrayinput, 0, bytearrayinput.Length);
            cryptostream.Close();
            fsInput.Close();
            fsEncrypted.Close();
        }

        public static void DecryptFile(string sInputFilename,
           string sOutputFilename,
           string sKey)
        {
            DESCryptoServiceProvider DES = new DESCryptoServiceProvider();
            //A 64 bit key and IV is required for this provider.
            //Set secret key For DES algorithm.
            DES.Key = ASCIIEncoding.ASCII.GetBytes(sKey);
            //Set initialization vector.
            DES.IV = ASCIIEncoding.ASCII.GetBytes(sKey);

            //Create a file stream to read the encrypted file back.
            FileStream fsread = new FileStream(sInputFilename,
               FileMode.Open,
               FileAccess.Read);
            //Create a DES decryptor from the DES instance.
            ICryptoTransform desdecrypt = DES.CreateDecryptor();
            //Create crypto stream set to read and do a 
            //DES decryption transform on incoming bytes.
            CryptoStream cryptostreamDecr = new CryptoStream(fsread,
               desdecrypt,
               CryptoStreamMode.Read);
            //Print the contents of the decrypted file.
            StreamWriter fsDecrypted = new StreamWriter(sOutputFilename);
            fsDecrypted.Write(new StreamReader(cryptostreamDecr).ReadToEnd());
            fsDecrypted.Flush();
            fsDecrypted.Close();
        }
    }

    public class MngConnectionPool
    {
        public int nNumber { set; get; }
        public bool bUseState { set; get; }
        public bool bLastConnState { set; get; }
        public int nUseCommandPort { set; get; }
        public int nUseStreamPort { set; get; }
		public bool bRequiredPortReset { set; get; }
        
        public bool bRequiredPortResetReady { set; get; }

        public Stopwatch stPortTimer { set; get; }       
        public Stopwatch stConnWaitTimer { set; get; }

        public MngConnectionPool(int nPos)
        {
            nNumber = nPos;

            bUseState = false;

            bLastConnState = false;

            nUseCommandPort = 0;

            nUseStreamPort = 0;

			bRequiredPortReset = false;

            bRequiredPortResetReady = false;

            stPortTimer = new Stopwatch();
            stPortTimer.Start();

            stConnWaitTimer = new Stopwatch();
        }

        public void Initialize()
        {
            bUseState = false;

            bLastConnState = false;

            PortTImerRestart();
        }

        public void PortTImerRestart()
        {
            stPortTimer.Restart();
        }

        public void ConnWaitTimerStart()
        {
            if (stConnWaitTimer.IsRunning)
                stConnWaitTimer.Restart();
            else
                stConnWaitTimer.Start();
        }

        public void ConnWaitTimerStop()
        {
            stConnWaitTimer.Stop();
        }

        public void ConnWaitTimerReset()
        {
            stConnWaitTimer.Reset();
        }

        public long ConnWaitTime()
        {
            return stConnWaitTimer.ElapsedMilliseconds;
        }
    }

    public delegate void stCallBackDelegate();

    public static class TimeControl
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool SetSystemTime(ref SYSTEMTIME st);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool SetLocalTime(ref SYSTEMTIME st);

        private static SYSTEMTIME dtTimeSystem = new SYSTEMTIME();

        private static bool bSuccessSyncTime;

        public static bool SyncServerTimeResult { get { return bSuccessSyncTime; } }

        //ScheduledTimer
        private static Timer _Timer;

        private static ServerInfo ServerLoadInfo;

        public static void _WorkTimer()
        {
            //TimeSync 시도
            DoSyncTime(ServerLoadInfo);
        }

        private static TimeSpan GetDueTime(TimeSpan A, TimeSpan B)
        {
            if (A < B)
            {
                return B.Subtract(A);
            }
            else
            {
                //return new TimeSpan(24, 0, 0).Subtract(B.Subtract(A));
                return new TimeSpan(24, 0, 0).Subtract(A.Subtract(B));
            }
        }

        private static void SetTime(TimeSpan _time, stCallBackDelegate callback)
        {
            if (_Timer != null)
            {
                // Change 매서드 사용 가능.  
                _Timer.Dispose();
            }

            TimeSpan Now = DateTime.Now.TimeOfDay;
            TimeSpan DueTime = GetDueTime(Now, _time);

            _Timer = new Timer(new TimerCallback(delegate (object _callback)
            {
                ((stCallBackDelegate)_callback)();
            }), callback, DueTime, new TimeSpan(24, 0, 0));
        }

        public static bool GetSyncTime(ref SYSTEMTIME refSyncTime)
        {
            var localDateTime = DateTime.Now.ToLocalTime();

            refSyncTime = new SYSTEMTIME();
            refSyncTime.wYear = (short)localDateTime.Year;
            refSyncTime.wMonth = (short)localDateTime.Month;
            refSyncTime.wDay = (short)localDateTime.Day;
            refSyncTime.wHour = (short)localDateTime.Hour;
            refSyncTime.wMinute = (short)localDateTime.Minute;
            refSyncTime.wSecond = (short)localDateTime.Second;
            refSyncTime.wMilliseconds = (short)localDateTime.Millisecond;

            return bSuccessSyncTime;
        }

        public static void DoSyncTime(ServerInfo GetLoadInfo)
        {
            ServerLoadInfo = GetLoadInfo;

            Task.Run(() => Work());
        }

        private static async void Work()
        {
            await GetTimeServerDateTime();
        }

        private static async Task<bool> GetTimeServerDateTime()
        {
            var currentProcess = Process.GetCurrentProcess();
            //Console.WriteLine(currentProcess.Id);

            /* 
            Win32.TokenPrivileges
            ; https://github.com/trondr/Win32.TokenPrivileges
            */

            int nCnt = 0;

            bSuccessSyncTime = false;

            SetTime(new TimeSpan(ServerLoadInfo.TimeServerSyncTimeHour, 
                ServerLoadInfo.TimeServerSyncTimeMinute, 
                ServerLoadInfo.TimeServerSyncTimeSecond), _WorkTimer);

            /*SetTime(new TimeSpan(16,
                37,
                0), _WorkTimer);*/

            while (true)
            {
                string responseText = string.Empty;

                if (nCnt > 3)
                    break;

                try
                {
                    string strHostName = string.Empty;
                    int nPortNumber = int.MinValue;

                    strHostName = ServerLoadInfo.SyncTimeServerAddress;
                    nPortNumber = ServerLoadInfo.SyncTimeServerPort;

                    if (nPortNumber == int.MinValue)
                        nPortNumber = 123;

                    if (strHostName.Length <= 0)
                        throw new Exception("Time server access address not specified.");

                    //default Windows time server
                    //{52.231.114.183}
                    //const string ntpServer = "time.windows.com";
 
                    // NTP message size - 16 bytes of the digest (RFC 2030)
                    var ntpData = new byte[48];
 
                    //Setting the Leap Indicator, Version Number and Mode values
                    ntpData[0] = 0x1B; //LI = 0 (no warning), VN = 3 (IPv4 only), Mode = 3 (Client Mode)

                    //var addresses = Dns.GetHostEntry(ntpServer).AddressList;

                    IPAddress SetAddress;
                    IPAddress.TryParse(strHostName, out SetAddress);

                    //The UDP port number assigned to NTP is 123
                    var ipEndPoint = new IPEndPoint(SetAddress, 123);
                    //var ipEndPoint = new IPEndPoint(addresses[1], 123);
                    //NTP uses UDP

                    await Task.Delay(5000);

                    using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
                    {
                        socket.Connect(ipEndPoint);
 
                        //Stops code hang if NTP is blocked
                        socket.ReceiveTimeout = 5000;
 
                        socket.Send(ntpData);
                        socket.Receive(ntpData);
                        socket.Close();
                    }
 
                    ulong intPart = (ulong)ntpData[40] << 24 | (ulong)ntpData[41] << 16 | (ulong)ntpData[42] << 8 | (ulong)ntpData[43];
                    ulong fractPart = (ulong)ntpData[44] << 24 | (ulong)ntpData[45] << 16 | (ulong)ntpData[46] << 8 | (ulong)ntpData[47];
 
                    var milliseconds = (intPart * 1000) + ((fractPart * 1000) / 0x100000000L);
                    var networkDateTime = (new DateTime(1900, 1, 1)).AddMilliseconds((long)milliseconds);

                    var localDateTime = networkDateTime.ToLocalTime();

                    //pc에 적용하기
                    dtTimeSystem = new SYSTEMTIME();
                    dtTimeSystem.wYear = (short)localDateTime.Year;
                    dtTimeSystem.wMonth = (short)localDateTime.Month;
                    dtTimeSystem.wDay = (short)localDateTime.Day;
                    dtTimeSystem.wHour = (short)localDateTime.Hour;
                    dtTimeSystem.wMinute = (short)localDateTime.Minute;
                    dtTimeSystem.wSecond = (short)localDateTime.Second;
                    dtTimeSystem.wMilliseconds = (short)localDateTime.Millisecond;

                    bool SetResult = false;
                    int lastError = 0;

                    try
                    {
                        SetResult = SetLocalTime(ref dtTimeSystem);
                        if (SetResult == false)
                        {
                            lastError = Marshal.GetLastWin32Error();

                            throw new Exception();
                        }
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Exception: " + e.Message + " (" + lastError + ")");

                        throw new Exception();
                    }

                    if (SetResult)
                    {
                        Console.WriteLine($"Response: {responseText}, DateTime: {localDateTime}");

                        bSuccessSyncTime = true;

                        return true;
                    }
                    else
                        return false;

                    /*IPAddress SetAddress;
                    IPAddress.TryParse(strHostName, out SetAddress);
                    IPEndPoint SetIpEndPoint = new IPEndPoint(SetAddress, nPortNumber);

                    //37 //13 //123
                    //using (var client = new TcpClient(strHostName, nPortNumber))
                    //using (var client = new TcpClient(SetIpEndPoint))
                    using (var streamReader = new StreamReader(client.GetStream()))
                    {
                        await Task.Delay(5000);

                        //시간 불러오기
                        responseText = streamReader.ReadToEnd(); // "59442 21-08-16 14:28:19 50 0 0 585.3 UTC(NIST) *"
                        var utcDateTimeString = responseText.Substring(7, 17);

                        if (DateTime.TryParseExact(utcDateTimeString, "yy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out DateTime utcDateTime) == false)
                        {
                            Console.WriteLine("[ " + nCnt.ToString("D10") + " ] " + responseText);

                            //continue;

                            throw new Exception();
                        }

                        var localDateTime = utcDateTime.ToLocalTime();
                        //var systemDateTime = utcDateTime.SetSystemTime();

                        //pc에 적용하기
                        dtTimeSystem = new SYSTEMTIME();
                        dtTimeSystem.wYear = (short)localDateTime.Year;
                        dtTimeSystem.wMonth = (short)localDateTime.Month;
                        dtTimeSystem.wDay = (short)localDateTime.Day;
                        dtTimeSystem.wHour = (short)localDateTime.Hour;
                        dtTimeSystem.wMinute = (short)localDateTime.Minute;
                        dtTimeSystem.wSecond = (short)localDateTime.Second;
                        dtTimeSystem.wMilliseconds = (short)localDateTime.Millisecond;

                        bool SetResult = false;
                        int lastError = 0;

                        try
                        {
                            SetResult = SetLocalTime(ref dtTimeSystem);
                            if (SetResult == false)
                            {
                                lastError = Marshal.GetLastWin32Error();

                                throw new Exception();
                            }
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine("Exception: " + e.Message + " (" + lastError + ")");

                            throw new Exception();
                        }

                        if (SetResult)
                        {
                            Console.WriteLine($"Response: {responseText}, DateTime: {localDateTime}");

                            bSuccessSyncTime = true;

                            return true;
                        }
                        else
                            return false;
                    }*/
                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception: " + e.Message + " (" + responseText + ")");

                    nCnt++;
                }
            }

            return false;
        }
    }
}
