using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using AR;
using System.Security.Cryptography;

namespace Project.Device
{
    public class KeyenceBarcode : IDisposable
    {
        public string IP { get; set; }
        public int Port { get; set; }
        private Winsock_Orcas.Winsock ws;
        public event EventHandler<RecvDataEvent> BarcodeRecv;
        public event EventHandler<RecvImageEvent> ImageRecv;
        RecvDataEvent LastData = null;
        RecvImageEvent LastImage = null;

        public Boolean IsTriggerOn { get; set; } = false;
        private DateTime LastTrigOnTime = DateTime.Now;
        private System.Threading.Thread thliveimage = null;
        public int LiveImagetDelay = 500;
        public bool disposed = false;
        bool bRunConnectionK = true;
        public string Tag { get; set; }


        public string baseZPL { get; set; }

        public class RecvDataEvent : EventArgs
        {
            public string RawData { get; set; }
            public RecvDataEvent(string rawdata)
            {
                this.RawData = rawdata;
            }
        }
        public class RecvImageEvent : EventArgs
        {
            public Image Image { get; set; }
            public RecvImageEvent(Image rawdata)
            {
                this.Image = rawdata;
            }
        }

        public KeyenceBarcode(string ip = "192.168.100.100", int port = 9004)
        {
            this.IP = ip;
            this.Port = port;
            ws = new Winsock_Orcas.Winsock();
            ws.LegacySupport = true;
            ws.DataArrival += Ws_DataArrival;
            ws.Connected += Ws_Connected;
            thliveimage = new Thread(new ThreadStart(GetLiveImage));
            thliveimage.IsBackground = false;
            thliveimage.Start();
            Console.WriteLine($"{ip} / Port:{port}");
        }

        ~KeyenceBarcode()
        {
            Dispose(disposing: false);
        }
        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        #region "Image Utility"

        /// <summary>
        /// 8bpp 이미지의  팔레트를  설정합니다
        /// </summary>
        /// <param name="bmp"></param>
        public void UpdateGrayPallete(ref Bitmap bmp)
        {
            if (bmp.PixelFormat == System.Drawing.Imaging.PixelFormat.Format8bppIndexed)
            {
                System.Drawing.Imaging.ColorPalette pal = bmp.Palette;
                for (int i = 0; i < 256; i++)
                {
                    pal.Entries[i] = Color.FromArgb(i, i, i);
                }
                bmp.Palette = pal;
            }
        }
        public void GetImagePtr(Bitmap bmp, out IntPtr ptr, out int stride)
        {
            stride = 0;
            ptr = IntPtr.Zero;
            try
            {
                BitmapData bmpdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
                ptr = bmpdata.Scan0;
                stride = bmpdata.Stride;
                bmp.UnlockBits(bmpdata);
            }
            catch (Exception ex)
            {

            }
        }

        public bool IsCompatible(Bitmap source, Bitmap target)
        {
            if (source == null) return false;
            try
            {
                if (source.Height != target.Height ||
                source.Width != target.Width ||
                source.PixelFormat != target.PixelFormat) return false;
                return true;
            }
            catch (Exception ex)
            {
                PUB.log.AddE("keyence compatiable : " + ex.Message);
                return false;
            }


        }
        public bool IsCompatible(Bitmap source, int w, int h, PixelFormat pf)
        {
            if (source == null) return false;
            if (source.Height != h ||
                source.Width != w ||
                source.PixelFormat != pf) return false;

            return true;
        }

        public Bitmap CreateBitmap(int w, int h)
        {

            Bitmap bmp = new Bitmap(w, h, PixelFormat.Format8bppIndexed);
            UpdateGrayPallete(ref bmp);
            return bmp;
        }

        byte[] memorybuffer;
        public void UpdateBitmap(Bitmap source, Bitmap target)
        {
            if (source.Width < 10 || source.Height < 10) return;
            if (target == null || target.Width < 10 || target.Height < 10)
            {
                target = CreateBitmap(source.Width, source.Height);
            }

            var bitmaperrcnt = AR.VAR.I32[eVarInt32.BitmapCompatErr];
            if (IsCompatible(source, target) == false)
            {
                PUB.log.AddE($"Keyence bitmap compatibility error Source:{source.Width}x{source.Height} => Target:{target.Width}x{target.Height}");

                if (bitmaperrcnt > 5)
                {
                    var o = target;
                    target = CreateBitmap(source.Width, source.Height);
                    if (o != null) o.Dispose();
                    bitmaperrcnt = 0;
                }
                else AR.VAR.I32[eVarInt32.BitmapCompatErr] += 1;

                return;
            }
            else
            {
                if (bitmaperrcnt > 0)
                    AR.VAR.I32[eVarInt32.BitmapCompatErr] = 0;
            }

            //get image pointer
            BitmapData bmpdatas = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, source.PixelFormat);
            var ptrs = bmpdatas.Scan0;
            var strides = bmpdatas.Stride;

            BitmapData bmpdatat = target.LockBits(new Rectangle(0, 0, target.Width, target.Height), System.Drawing.Imaging.ImageLockMode.WriteOnly, target.PixelFormat);
            var ptrt = bmpdatat.Scan0;
            var stridet = bmpdatat.Stride;

            //read source data
            var sourcesize = Math.Abs(bmpdatas.Stride) * source.Height;
            if (memorybuffer == null || memorybuffer.Length != sourcesize)
            {
                memorybuffer = new byte[sourcesize];
            }

            Marshal.Copy(ptrs, memorybuffer, 0, sourcesize);

            //write target data
            Marshal.Copy(memorybuffer, 0, ptrt, sourcesize);

            source.UnlockBits(bmpdatas);
            target.UnlockBits(bmpdatat);

        }

        public void UpdateBitmap(Bitmap target, byte[] buffer, int w, int h)
        {
            if (IsCompatible(target, w, h, target.PixelFormat) == false)
            {
                throw new Exception("cannot udate incompatible bitmap.");
            }

            //get image pointer
            BitmapData bmpdata = target.LockBits(new Rectangle(0, 0, target.Width, target.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, target.PixelFormat);
            var ptr = bmpdata.Scan0;
            var stride = bmpdata.Stride;


            if (stride == w) //image x4
            {
                Marshal.Copy(buffer, 0, ptr, stride * h);
            }
            else
            {
                for (int i = 0; i < target.Height; ++i)
                {
                    Marshal.Copy(buffer, i * w, new IntPtr(ptr.ToInt64() + i * stride), w);
                }
            }

            target.UnlockBits(bmpdata);


        }


        #endregion


        protected virtual void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    //dipose managed resources;
                    bRunConnectionK = false;
                    Trigger(false);
                    ws.DataArrival -= Ws_DataArrival;
                    ws.Connected -= Ws_Connected;
                    Disconnect();
                    if (thliveimage != null && thliveimage.IsAlive) thliveimage.Abort();
                }

                //dispose unmmanged resources;
            }

            disposed = true;
        }

        void GetLiveImage()
        {
            while (bRunConnectionK)
            {
                if (IsConnect && IsTriggerOn)
                {
                    var tagname = this.Tag;
                    var img = GetImage();
                    this.LastImage = new RecvImageEvent(img);
                    ImageRecv?.Invoke(this, this.LastImage);
                    System.Threading.Thread.Sleep(LiveImagetDelay);
                }
                else System.Threading.Thread.Sleep(1000);
            }
        }

        private void Ws_Connected(object sender, Winsock_Orcas.WinsockConnectedEventArgs e)
        {
            this.Trigger(true);
        }

        public void Connect()
        {
            if (this.ws.State == Winsock_Orcas.WinsockStates.Closed)
            {
                this.ws.RemoteHost = this.IP;
                this.ws.RemotePort = this.Port;
                this.ws.Connect();
            }

        }
        public void Disconnect()
        {
            if (this.ws.State != Winsock_Orcas.WinsockStates.Closed)
                this.ws.Close();
        }

        private void Ws_DataArrival(object sender, Winsock_Orcas.WinsockDataArrivalEventArgs e)
        {
            var data = ws.Get<string>();
            this.LastData = new RecvDataEvent(data);
            BarcodeRecv?.Invoke(this, LastData);
        }

        public bool Reset()
        {
            //if (IsTriggerOn) return;
            string cmd = "RESET";
            if (IsConnect == false)
            {
                PUB.log.AddE("Keyence Not Connected : RESET");
                return false;
            }
            try
            {
                ws.Send(cmd + "\r");
                return true;
            }
            catch (Exception ex)
            {
                PUB.log.AddE(ex.Message);
                return false;
            }
        }

        public bool BSave(int no)
        {
            var cmd = $"BSAVE,{no}";
            try
            {
                ws.Send(cmd + "\r");
                return true;
            }
            catch {
                return false;
            }
        }
        public bool BLoad(int no)
        {
            var cmd = $"BLOAD,{no}";
            try
            {
                ws.Send(cmd + "\r");
                return true;
            }
            catch {
                return false;
            }
        }
        public void Trigger(bool bOn)
        {
            //if (IsTriggerOn) return;
            string cmd = bOn ? "LON" : "LOFF";

            if (bOn) LastTrigOnTime = DateTime.Now;

            try
            {
                ws.Send(cmd + "\r");
                IsTriggerOn = bOn;
            }
            catch { }
        }
        public void ExecCommand(string cmd)
        {
            try
            {
                ws.Send(cmd + "\r");
            }
            catch { }
        }
        public bool IsConnect
        {
            get
            {
                if (ws == null) return false;
                return ws.State == Winsock_Orcas.WinsockStates.Connected;
            }
        }

        private bool readimage = false;
        ManualResetEvent mre = new ManualResetEvent(true);
        bool mreSetOK = true;
        bool mreResetOK = true;

        public Image GetImage()
        {
            Image oimg = null;
            if (mreResetOK && mre.WaitOne(1000) == false)
            {
                mreResetOK = false;
                return oimg;
            }
            else
            {
                mreResetOK = mre.Reset();
                try
                {
                    var url = $"http://{this.IP}/liveimage.jpg";
                    var data = UTIL.DownloadImagefromUrl(url);
                    var ms = new System.IO.MemoryStream(data);
                    oimg = Image.FromStream(ms);
                }
                catch (Exception ex)
                {
                    PUB.log.AddE("Keyence GetImage:" + ex.Message);
                }

                if (mreResetOK)
                {
                    mreSetOK = mre.Set();
                }
            }
            return oimg;
        }

        public bool SaveImage(string fn)
        {
            var fi = new System.IO.FileInfo(fn);
            if (fi.Directory.Exists == false)
                fi.Directory.Create();



            if (mreResetOK && mre.WaitOne(1000) == false)
            {
                mreResetOK = false;
                return false;
            }
            else
            {
                bool retval = false;
                mreResetOK = mre.Reset();
                try
                {
                    var url = $"http://{this.IP}/liveimage.jpg";
                    var data = UTIL.DownloadImagefromUrl(url);
                    using (var ms = new System.IO.MemoryStream(data))
                    {
                        using (var oimg = Image.FromStream(ms))
                            oimg.Save(fi.FullName);
                    }
                    retval = true;
                }
                catch (Exception ex)
                {

                }
                if (mreResetOK)
                    mre.Set();
                return retval;
            }
        }

        public bool UpdateKeyenceImage(ref System.Windows.Forms.PictureBox pic)
        {
            bool retval = false;
            if (readimage)
            {
                PUB.log.AddAT("Keyence is receiving image, command not processed");
            }
            else
            {
                readimage = true;
                var oimg = pic.Image;

                var newimage = GetImage();

                pic.Image = newimage;
                retval = newimage != null;
                if (oimg != null) oimg.Dispose();

            }
            readimage = false;
            return retval;
        }
    }
}
