using System;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace AR.MemoryMap
{
    public abstract class Core
    {
        protected ManualResetEvent mre;
        protected MemoryMappedFile mmf;
        protected Mutex mutex;
        protected bool mutexCreated;
        protected Func<bool> StartAction;
        protected Func<bool> StopAction;
        string MapFileName = string.Empty;
        public int WriteTime = 100;
        public int ReadTime = 100;
        int MapSize = 100;
        public bool Init { get; set; } = false;
        Task loop;
        protected bool brun = false;
        public int LoopDelay { get; set; }
        public string ErrorMessage { get; set; }
        public Core(string mapFileName, int mapSize, int loopDelay = 50)
        {
            LoopDelay = loopDelay;
            this.MapFileName = mapFileName;
            this.MapSize = mapSize;
            this.monitorvalue = new byte[mapSize];
            mre = new ManualResetEvent(false);
        }
        ~Core()
        {
            brun = false;
        }

        public bool Start()
        {
            Init = StartAction.Invoke();
            return Init;
        }
        public void StartMonitor()
        {
            if (Init == false) return;
            if (IsMonitorRun) return;


            brun = true;
            loop = Task.Factory.StartNew(() =>
            {
                Monitor_Loop();
            });
        }
        public void StopMonitor()
        {
            brun = false;
        }
        public bool IsMonitorRun
        {
            get
            {
                if (loop == null) return false;
                if (loop.IsCompleted) return false;
                if (loop.IsCanceled) return false;
                return true;
            }
        }

        public bool SetMonitorTarget(bool All)
        {
            if (mre.WaitOne(100) == false) return false;


            mre.Set();
            return true;
        }

        byte[] monitorvalue;

        public void Stop()
        {
            StopAction.Invoke();
        }
        private void Monitor_Loop()
        {
            while (brun)
            {
                //작업가능확인
                bool readok = false;
                byte[] value = null;

                try
                {
                    if (mre.WaitOne(100))
                    {
                        try
                        {
                            //메모리접근 권한 확인
                            if (MutexWaitOne(100))
                            {
                                //값을 읽은 경우
                                if (ReadBytes(0, this.MapSize, out value))
                                {
                                    readok = true;
                                }

                            }
                        }
                        finally { mutex.ReleaseMutex(); }
                    }
                }
                finally { mre.Set(); }


                if (readok)
                {
                    //값의 변화가 있다면 이벤트 발생
                    List<int> changeindex = new List<int>();
                    for (int i = 0; i < value.Length; i++)
                    {
                        if (value[i] != monitorvalue[i]) changeindex.Add(i);
                    }
                    if (changeindex.Any())
                    {
                        ValueChanged.Invoke(this, new monitorvalueargs(changeindex.ToArray(), monitorvalue, value));
                    }
                    //신규값을 업데이트
                    Array.Copy(value, monitorvalue, value.Length);
                }

                //write
                System.Threading.Thread.Sleep(LoopDelay);
            }
        }
        public bool MutexWaitOne(int milli)
        {
            var MapFileSync = $"{MapFileName}{MapSize}";
            if (mutexCreated == false)
            {
                mutex = new Mutex(false, MapFileSync, out mutexCreated);
                return mutex.WaitOne(milli);
            }
            else
            {
                //이미생성된 경우에는 에러처리를 해야한다
                try
                {
                    return mutex.WaitOne(milli);
                }
                catch
                {
                    //오류가있으니 다시 작성한다
                    mutex = new Mutex(false, MapFileSync, out mutexCreated);
                    return false;
                }
            }
        }

        public event EventHandler<monitorvalueargs> ValueChanged;
        public class monitorvalueargs : EventArgs
        {
            public byte[] newdata;
            public byte[] olddata;
            public int[] idxlist;
            public monitorvalueargs(int[] idxs, byte[] beforedata, byte[] afterdata)
            {
                idxlist = new int[idxs.Length];
                Array.Copy(idxs, this.idxlist, idxs.Length);
                newdata = new byte[afterdata.Length];
                Array.Copy(afterdata, newdata, afterdata.Length);
                olddata = new byte[beforedata.Length];
                Array.Copy(beforedata, olddata, beforedata.Length);
            }
        }

        #region "WRITE"
        public bool Write(int address, Boolean value)
        {
            if (Init == false) return false;
            if (MutexWaitOne(WriteTime) == false) return false;
            Type type = value.GetType();
            var size = Marshal.SizeOf(value.GetType());
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            using (var writer = new BinaryWriter(stream))
                writer.Write(value);
            mutex.ReleaseMutex();
            return true;
        }

        public bool Write(int address, byte[] value, int startIndex = 0, int size = 0)
        {
            if (Init == false) return false;
            if (MutexWaitOne(WriteTime) == false) return false;
            Type type = value.GetType();
            if (size == 0) size = Marshal.SizeOf(value.GetType());
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            using (var writer = new BinaryWriter(stream))
                writer.Write(value,startIndex,size);
            mutex.ReleaseMutex();
            return true;
        }
        public bool Write(int address, byte value)
        {
            if (Init == false) return false;
            if (MutexWaitOne(WriteTime) == false) return false;
            Type type = value.GetType();
            var size = Marshal.SizeOf(value.GetType());
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            using (var writer = new BinaryWriter(stream))
                writer.Write(value);
            mutex.ReleaseMutex();
            return true;
        }
        public bool Write(int address, string value)
        {
            if (Init == false) return false;
            if (MutexWaitOne(WriteTime) == false) return false;
            Type type = value.GetType();
            var size = Marshal.SizeOf(type);
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            using (var writer = new BinaryWriter(stream))
                writer.Write(value);
            mutex.ReleaseMutex();
            return true;
        }
        public bool Write(int address, Int32 value)
        {
            if (Init == false) return false;
            if (MutexWaitOne(WriteTime) == false) return false;
            Type type = value.GetType();
            var size = Marshal.SizeOf(type);
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            using (var writer = new BinaryWriter(stream))
                writer.Write(value);
            mutex.ReleaseMutex();
            return true;
        }
        public bool Write(int address, Int16 value)
        {
            if (Init == false) return false;
            if (MutexWaitOne(WriteTime) == false) return false;
            Type type = value.GetType();
            var size = Marshal.SizeOf(type);
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            using (var writer = new BinaryWriter(stream))
                writer.Write(value);
            mutex.ReleaseMutex();
            return true;
        }
        public bool Write(int address, UInt32 value)
        {
            if (Init == false) return false;
            if (MutexWaitOne(WriteTime) == false) return false;
            Type type = value.GetType();
            var size = Marshal.SizeOf(type);
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            using (var writer = new BinaryWriter(stream))
                writer.Write(value);
            mutex.ReleaseMutex();
            return true;
        }
        public bool Write(int address, UInt16 value)
        {
            if (Init == false) return false;
            if (MutexWaitOne(WriteTime) == false) return false;
            Type type = value.GetType();
            var size = Marshal.SizeOf(type);
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            using (var writer = new BinaryWriter(stream))
                writer.Write(value);
            mutex.ReleaseMutex();
            return true;
        }
        public bool Write(int address, Single value)
        {
            if (Init == false) return false;
            if (MutexWaitOne(WriteTime) == false) return false;
            Type type = value.GetType();
            var size = Marshal.SizeOf(type);
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            using (var writer = new BinaryWriter(stream))
                writer.Write(value);
            mutex.ReleaseMutex();
            return true;
        }
        public bool Write(int address, Double value)
        {
            if (Init == false) return false;
            if (MutexWaitOne(WriteTime) == false) return false;
            Type type = value.GetType();
            var size = Marshal.SizeOf(type);
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            using (var writer = new BinaryWriter(stream))
                writer.Write(value);
            mutex.ReleaseMutex();
            return true;
        }

        //public bool Write<T>(int address, T value)
        //{
        //    if (checktype(value.GetType()) == false) return false;
        //    if (MutexWaitOne(3000) == false) return false;
        //    var size = Marshal.SizeOf(typeof(T));
        //    using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
        //    {
        //        using (var reader = new BinaryWriter(stream))
        //        {
        //            var a = (byte[])Convert.ChangeType(value, typeof(byte[]));
        //            reader.Write(a, 0, a.Length);
        //        }
        //    }
        //    mutex.ReleaseMutex();
        //    return true;
        //}
        #endregion
        #region "READ"
        public bool ReadSingle(int address, out Single value)
        {
            value = 0;
            if (Init == false) return false;
            var retval = true;
            var type = value.GetType();
            if (MutexWaitOne(ReadTime) == false) return false;
            var size = 1;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            {
                using (var reader = new BinaryReader(stream))
                {
                    byte[] buffer = new byte[size];
                    reader.Read(buffer, 0, size);
                    value = BitConverter.ToSingle(buffer, 0);
                }
            }
            mutex.ReleaseMutex();
            return retval;
        }
        public bool ReadDouble(int address, out double value)
        {
            var retval = true;
            value = 0;
            if (Init == false) return false;
            var type = value.GetType();
            if (MutexWaitOne(ReadTime) == false) return false;
            var size = 1;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            {
                using (var reader = new BinaryReader(stream))
                {
                    byte[] buffer = new byte[size];
                    reader.Read(buffer, 0, size);
                    value = BitConverter.ToDouble(buffer, 0);
                }
            }
            mutex.ReleaseMutex();
            return retval;
        }
        public bool ReadBytes(int address, int size, out byte[] value)
        {
            var retval = true;
            value = new byte[size];
            if (Init == false) return false;
            var type = value.GetType();
            if (MutexWaitOne(ReadTime) == false) return false;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            {
                using (var reader = new BinaryReader(stream))
                {
                    reader.Read(value, 0, size);
                }
            }
            mutex.ReleaseMutex();
            return retval;
        }
        public bool ReadByte(int address, out byte value)
        {
            var retval = true;
            value = 0;
            if (Init == false) return false;
            var type = value.GetType();
            if (MutexWaitOne(ReadTime) == false) return false;
            var size = 1;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            {
                using (var reader = new BinaryReader(stream))
                {
                    byte[] buffer = new byte[size];
                    reader.Read(buffer, 0, size);
                    value = buffer.First();
                }
            }
            mutex.ReleaseMutex();
            return retval;
        }
        public bool ReadInt16(int address, out Int16 value)
        {
            var retval = true;
            value = 0;
            if (Init == false) return false;
            var type = value.GetType();
            if (MutexWaitOne(ReadTime) == false) return false;
            var size = 4;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            {
                using (var reader = new BinaryReader(stream))
                {
                    byte[] buffer = new byte[size];
                    reader.Read(buffer, 0, size);
                    value = BitConverter.ToInt16(buffer, 0);
                }
            }
            mutex.ReleaseMutex();
            return retval;
        }
        public bool ReadInt32(int address, out Int32 value)
        {
            var retval = true;
            value = 0;
            if (Init == false) return false;
            var type = value.GetType();
            if (MutexWaitOne(ReadTime) == false) return false;
            var size = 4;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            {
                using (var reader = new BinaryReader(stream))
                {
                    byte[] buffer = new byte[size];
                    reader.Read(buffer, 0, size);
                    value = BitConverter.ToInt32(buffer, 0);
                }
            }
            mutex.ReleaseMutex();
            return retval;
        }
        public bool ReadInt64(int address, out Int64 value)
        {
            var retval = true;
            value = 0;
            if (Init == false) return false;
            var type = value.GetType();
            if (MutexWaitOne(ReadTime) == false) return false;
            var size = 4;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            {
                using (var reader = new BinaryReader(stream))
                {
                    byte[] buffer = new byte[size];
                    reader.Read(buffer, 0, size);
                    value = BitConverter.ToInt64(buffer, 0);
                }
            }
            mutex.ReleaseMutex();
            return retval;
        }
        public bool ReadUInt16(int address, out UInt16 value)
        {
            var retval = true;
            value = 0;
            if (Init == false) return false;
            var type = value.GetType();
            if (MutexWaitOne(ReadTime) == false) return false;
            var size = 4;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            {
                using (var reader = new BinaryReader(stream))
                {
                    byte[] buffer = new byte[size];
                    reader.Read(buffer, 0, size);
                    value = BitConverter.ToUInt16(buffer, 0);
                }
            }
            mutex.ReleaseMutex();
            return retval;
        }
        public bool ReadUInt32(int address, out UInt32 value)
        {
            var retval = true;
            value = 0;
            if (Init == false) return false;
            var type = value.GetType();
            if (MutexWaitOne(ReadTime) == false) return false;
            var size = 4;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            {
                using (var reader = new BinaryReader(stream))
                {
                    byte[] buffer = new byte[size];
                    reader.Read(buffer, 0, size);
                    value = BitConverter.ToUInt32(buffer, 0);
                }
            }
            mutex.ReleaseMutex();
            return retval;
        }
        public bool ReadUInt64(int address, out UInt64 value)
        {
            var retval = true;
            value = 0;
            if (Init == false) return false;
            var type = value.GetType();
            if (MutexWaitOne(ReadTime) == false) return false;
            var size = 4;
            using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
            {
                using (var reader = new BinaryReader(stream))
                {
                    byte[] buffer = new byte[size];
                    reader.Read(buffer, 0, size);
                    value = BitConverter.ToUInt64(buffer, 0);
                }
            }
            mutex.ReleaseMutex();
            return retval;
        }

        //public bool Read<T>(int address, out T value) where T : IConvertible
        //{
        //    var retval = true;
        //    value = default(T);
        //    var type = value.GetType();
        //    if (checktype(type) == false) return false;

        //    if (MutexWaitOne(3000) == false) return false;
        //    var size = Marshal.SizeOf(typeof(T));
        //    using (MemoryMappedViewStream stream = mmf.CreateViewStream(address, size))
        //    {
        //        using (var reader = new BinaryReader(stream))
        //        {
        //            byte[] buffer = new byte[size];
        //            reader.Read(buffer, 0, size);
        //            if (type == typeof(Int32))
        //                value = (T)Convert.ChangeType(BitConverter.ToInt32(buffer, 0), typeof(T));
        //            else if (type == typeof(UInt32))
        //                value = (T)Convert.ChangeType(BitConverter.ToUInt32(buffer, 0), typeof(T));
        //            else if (type == typeof(Int16))
        //                value = (T)Convert.ChangeType(BitConverter.ToInt16(buffer, 0), typeof(T));
        //            else if (type == typeof(UInt16))
        //                value = (T)Convert.ChangeType(BitConverter.ToUInt16(buffer, 0), typeof(T));
        //            else if (type == typeof(byte))
        //                value = (T)Convert.ChangeType(buffer[0], typeof(T));
        //            else if (type == typeof(string))
        //                value = (T)Convert.ChangeType(System.Text.Encoding.Default.GetString(buffer), typeof(T));
        //            else retval = false;
        //        }
        //    }
        //    mutex.ReleaseMutex();
        //    return retval;
        //}
        #endregion


        /// <summary>
        /// 지정한 타입이 호환되는 타입인가?
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        //bool checktype(Type value)
        //{
        //    if (value == typeof(Int32)) return true;
        //    else if (value == typeof(UInt32)) return true;
        //    else if (value == typeof(Int16)) return true;
        //    else if (value == typeof(UInt16)) return true;
        //    else if (value == typeof(byte)) return true;
        //    else if (value == typeof(string)) return true;
        //    else return false;
        //}

    }
}
