using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using icEveryCode;

namespace icDecLibAztec_Net
{
    #region Enums
    public enum AZ_DECODER_SPEED
    {
        ROBUST = 0
    };

    /// enum QUALITY_MASK bits of mask:
    public enum AZ_QUALITY_MASK
    {
        NO = 0X0000,
        ALL = 0x7FFF
    };

    public enum AZ_FILTER_MODE
    {
        NON = 0,  //!< no filter
        AUTO = 1,  //!< filter with digital auto-focus
    };

    public enum AZ_SYMBOL_MODE
    {
        NORMAL = 0,     //!<- Ordinary Aztec Code
        RUNES = 1,     //!<- Runes Aztec Code
    };

    public enum AZ_QZ_MODE
    {
        STANDARD = 0 //!< allows QZ>= 3 module sizes, decoder in average more fast and robust
       , SMALL = 1 //!< allows QZ>= 1 module size, affects speed and robustness
    };

    public enum AZ_LABEL_MODE
    {
        NORMAL = 0
    };

    public enum AZ_CELL_COLOR
    {
        BLACKONWHITE = 0,
        WHITEONBLACK = 1,
        ANY = 2,
    };

    public enum AZ_REJECTION_REASONS
    {
        AZ_OK = 0,
        AZ_NotFound_Error = -1,
        AZ_Mode_RS_Error = -7, // too many errors in Reed Solomon code in Mode Message
        AZ_Data_RS_Error = -8, // too many errors in Reed Solomon code in Data Message
        AZ_CharEncodation_Error = -9, // unexpected encodation scheme
        AZ_Geometry_Error = -13, // unallowed module size


        AZ_NOMEMORY = -99,
        AZ_UNKNOWN = -100,
        AZ_DISCONNECTED = -200,

    };
    #endregion

    public struct TAZ_Quality
    {
        public float symbol_contrast;    //!< Symbol Contrast
        public float axial_nonuniformity;    //!< Axial Non uniformity
        public float grid_nonuniformity;    //!< Grid  Non uniformity
        public float unused_error_correction;   //!< Unused Error Correction
        /// Grades:
        public float symbol_contrast_grade;   //!< Symbol Contrast Grade
        public float axial_nonuniformity_grade;   //!< Axial Non uniformity Grade
        public float grid_nonuniformity_grade;   //!< Grid  Non uniformity Grade
        public float unused_error_correction_grade;  //!< Unused Error Correction Grade
        public float modulation_grade;  //!< Modulation Grade
        public float fixed_pattern_damage_grade;  //!< Fixed Pattern Damage Grade
        public float decode_grade;   //!< Decode Grade
        public float overall_grade; //!< Scan Grade (minimum of Grades)
        internal unsafe TAZ_Quality(icAztecPro.TAztec_Info_Internal* Q)
        {
            if (Q != null)
            {
                symbol_contrast = Q->sc;
                axial_nonuniformity = Q->an;
                grid_nonuniformity = Q->gn;
                unused_error_correction = Q->uec;
                symbol_contrast_grade = Q->scG;
                axial_nonuniformity_grade = Q->anG;
                grid_nonuniformity_grade = Q->anG;
                unused_error_correction_grade = Q->uecG;
                modulation_grade = Q->modG;
                fixed_pattern_damage_grade = Q->fpdG;
                decode_grade = Q->deG;
                overall_grade = Q->scanG;
            }
            else
            {
                symbol_contrast = -1;
                axial_nonuniformity = -1;
                grid_nonuniformity = -1;
                unused_error_correction = -1;
                symbol_contrast_grade = -1;
                axial_nonuniformity_grade = -1;
                grid_nonuniformity_grade = -1;
                unused_error_correction_grade = -1;
                modulation_grade = -1;
                fixed_pattern_damage_grade = -1;
                decode_grade = -1;
                overall_grade = -1;
            }
        }

    };


    /// <summary>
    /// Класс, инкапсулирующий свойства декодированного символа Aztec
    /// </summary>
    public sealed class TAztec_Info
    {

        /// <summary>
        /// Координаты углов найденного символа PDF417
        /// </summary>
        public PointF[] Corners
        {
            get { return FCorners; }
        }
        /// <summary>
        /// Количество уровней
        /// </summary>
        public int LayersCount
        {
            get { return FLayersCount; }
        }
        /// <summary>
        /// Заданный уровень коррекции ошибок
        /// </summary>
        public int RSErr
        {
            get { return FRSErr; }
        }

        public int Dim
        {
            get { return FDim; }
        }

        public AZ_CELL_COLOR ActColor
        {
            get { return AZ_CELL_COLOR.BLACKONWHITE; }
        }
        public int Color = 0;

        public TAZ_Quality Quality
        {
            get { return FQuality; }
        }

        #region Private
        private int FLayersCount;
        private int FRSErr;
        private IntPtr pchPtr = IntPtr.Zero;
        private int pchLen = 0;
        private int FDim = 0;
        private PointF[] FCorners = new PointF[4];
        private TAZ_Quality FQuality = new TAZ_Quality();


        unsafe internal TAztec_Info(icAztecPro Decoder, icAztecPro.TAztec_Info_Internal* Info)
        {
            FLayersCount = Info->Layers;


            FCorners[0] = new PointF(Info->RowCols1, Info->RowCols0);
            FCorners[1] = new PointF(Info->RowCols3, Info->RowCols2);
            FCorners[2] = new PointF(Info->RowCols5, Info->RowCols4);
            FCorners[3] = new PointF(Info->RowCols7, Info->RowCols6);

            pchPtr = new IntPtr(Info->pch);
            pchLen = Info->pchlen;

            FDim = Info->Dim;

            FRSErr = Info->RSErrorCount;

            if (Decoder.QualityMask == AZ_QUALITY_MASK.NO)
                FQuality = new TAZ_Quality(null);
            else
                FQuality = new TAZ_Quality(Info);


        }
        #endregion
        /// <summary>
        /// Собственно декодированная строка
        /// </summary>
        public string GetDecodedString(int CodePage)
        {
            int L = pchLen * 2;
            if (L == 0) return "";
            if (CodePage == 0)
                return GetDecodedStringHex();
            Decoder D = Encoding.GetEncoding(CodePage).GetDecoder();
            unsafe
            {
                char[] C = new char[L];
                byte* pch = (byte*)pchPtr.ToPointer();
                fixed (char* PC = C)
                {
                    int bytesUsed; int charsUsed; bool completed;

                    D.Convert(pch, pchLen, PC, L, true, out bytesUsed, out charsUsed, out completed);
                    if (completed)
                        return new String(PC, 0, charsUsed);
                    else
                        return "";
                }
            }
        }

        string GetDecodedStringHex()
        {
            int L = pchLen * 2;
            StringBuilder SB = new StringBuilder(L);
            const string HEX = "0123456789ABCDEF";
            unsafe
            {
                byte* BB = (byte*)(byte*)pchPtr;
                for (int i = 0; i < L; i++)
                {
                    byte B = BB[i];
                    int B0 = B >> 4;
                    int B1 = B & 0xF;

                    SB.Append(' ').Append(HEX[B0]).Append(HEX[B1]);
                }
            }
            return SB.ToString();
        }
    }
    public class icAztecPro : IDisposable
    {
        #region Dll_Fields
        const string DLL_Name = "icDecLibAZ.dll";

        [DllImport(DLL_Name, EntryPoint = "Connect_AZ_Decoder", CallingConvention = CallingConvention.StdCall)]
        internal static extern IntPtr Connect_AZ_Decoder([In]Int32 maxRow, [In] Int32 maxCol);

        [DllImport(DLL_Name, EntryPoint = "Disconnect_AZ_Decoder", CallingConvention = CallingConvention.StdCall)]
        internal static extern void Disconnect_AZ_Decoder(ref IntPtr decoder);

        [DllImport(DLL_Name, EntryPoint = "Decode_AZ_Bits", CallingConvention = CallingConvention.StdCall)]
        internal static extern int Decode_AZ_Bits([In]IntPtr options, [In]Int32 rowcount, [In]Int32 colcount, [In] IntPtr pbits);

        [DllImport(DLL_Name, EntryPoint = "Get_AZ_DebugProp", CallingConvention = CallingConvention.StdCall)]
        internal static extern int Get_AZ_DebugProp([In]IntPtr options, [In]Int32 PropIndex, [In]Int32 Row, [In]Int32 Col);

        [DllImport(DLL_Name, EntryPoint = "Get_AZ_Info", CallingConvention = CallingConvention.StdCall)]
        internal static unsafe extern IntPtr Get_AZ_Info([In]IntPtr options, [In]Int32 InfoIndex);

        [DllImport(DLL_Name, EntryPoint = "Create_AZ_Options", CallingConvention = CallingConvention.StdCall)]
#if WIN64
        internal static unsafe extern IntPtr Create_AZ_Options([In] IntPtr pDecoder,[In] ref TAZ_OptMode optmode);
#else
        internal static unsafe extern IntPtr Create_AZ_Options([In] IntPtr pDecoder,
           [In] Int32 maxAZCount,   //!< 1..10
           [In] Int32 speedMode,    //!< Always SP_ROBUST
           [In] Int32 qualityMask,  //!< 0 – no QP, 1 – estimate QP, (0 by default)
           [In] Int32 labelMode,    //!< Always LM_NORMAL
           [In] Int32 CellColor,
           [In] Int32 timeOut,      //!< timeOut in mls. 
           [In] Int32 filterMode,   //!< AZFM_NON | AZFM_AUTO
           [In] Int32 symbology,    //!< AZ_S_NORMAL | AZ_S_RUNES 
           [In] Int32 qzMode);       //!< AZ_QZ_STANDARD | AZ_QZ_SMALL)*/
#endif
        [DllImport(DLL_Name, EntryPoint = "Delete_AZ_Options", CallingConvention = CallingConvention.StdCall)]

        internal static unsafe extern void Delete_AZ_Options(ref IntPtr pOptions);
        #endregion

        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        public struct TAZ_OptMode
        {
            public int maxAZCount;   //!< 1..10
            public int speedMode;    //!< Always SP_ROBUST
            public int qualityMask;  //!< 0 – no QP, 1 – estimate QP, (0 by default)
            public int labelMode;    //!< Always LM_NORMAL
            public int cellColor;
            public int timeOut;      //!< timeOut in mls. 
            public int filterMode;   //!< AZFM_NON | AZFM_AUTO
            public int symbology;    //!< AZ_S_NORMAL | AZ_S_RUNES 
            public int qzMode;       //!< AZ_QZ_STANDARD | AZ_QZ_SMALL
        };

        [StructLayout(LayoutKind.Sequential)]
        internal unsafe struct TAztec_Info_Internal
        { //result of decoding per each Aztec symbol
            public float RowCols0;    // symbol corners
            public float RowCols1;    // symbol corners
            public float RowCols2;    // symbol corners
            public float RowCols3;    // symbol corners
            public float RowCols4;    // symbol corners
            public float RowCols5;    // symbol corners
            public float RowCols6;    // symbol corners
            public float RowCols7;    // symbol corners
            public int pchlen;        // length of decoded byte array
            
            public byte* pch;// decoded byte array
            public int RSErrorCount;  // number of Reed Solomon errors

            public int Dim;
            public int Rune_AZS;

            public int Layers;

            //public TAZ_Quality_Internal Quality;

            public float sc;    //!< Symbol Contrast
            public float an;    //!< Axial Non uniformity
            public float gn;    //!< Grid  Non uniformity
            public float uec;   //!< Unused Error Correction
            /// Grades:
            public float scG;   //!< Symbol Contrast Grade
            public float anG;   //!< Axial Non uniformity Grade
            public float gnG;   //!< Grid  Non uniformity Grade
            public float uecG;  //!< Unused Error Correction Grade
            public float modG;  //!< Modulation Grade
            public float fpdG;  //!< Fixed Pattern Damage Grade
            public float deG;   //!< Decode Grade
            public float scanG; //!< Scan Grade (minimum of Grades)
            //end quality
            public int MSGLen;
        };

        [StructLayout(LayoutKind.Sequential, Pack = 4)]
        internal unsafe struct TAZ_Quality_Internal
        {
            float sc;    //!< Symbol Contrast
            float an;    //!< Axial Non uniformity
            float gn;    //!< Grid  Non uniformity
            float uec;   //!< Unused Error Correction
            /// Grades:
            float scG;   //!< Symbol Contrast Grade
            float anG;   //!< Axial Non uniformity Grade
            float gnG;   //!< Grid  Non uniformity Grade
            float uecG;  //!< Unused Error Correction Grade
            float modG;  //!< Modulation Grade
            float fpdG;  //!< Fixed Pattern Damage Grade
            float deG;   //!< Decode Grade
            float scanG; //!< Scan Grade (minimum of Grades)
        };


        public icAztecPro(int MaxRowQ, int MaxColQ)
        {
            D = Connect_AZ_Decoder(MaxRowQ, MaxColQ);
            optmode.maxAZCount = 1;
            optmode.maxAZCount = 1;   //!< 1..10
            optmode.speedMode = 0;    //!< Always SP_ROBUST
            optmode.cellColor = 0;
            optmode.qualityMask = 0;  //!< 0 – no QP, 1 – estimate QP, (0 by default)
            optmode.labelMode = 0;    //!< Always LM_NORMAL
            optmode.timeOut = 0;      //!< timeOut in mls. 
            optmode.filterMode = 0;   //!< AZFM_NON | AZFM_AUTO
            optmode.symbology = 0;    //!< AZ_S_NORMAL | AZ_S_RUNES 
            optmode.qzMode = 0;       //!< AZ_QZ_STANDARD | AZ_QZ_SMALL

        }
        /// <summary>
        /// удаляет неуправляемые ресурсы
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        ~icAztecPro()
        {
            Dispose(false);
        }
        /// <summary>
        /// освобождаем ресурсы
        /// </summary>
        /// <param name="disposing">
        /// <para>true - удаляем явно</para>
        /// <para>false - запущен финализатор</para>
        /// </param>
        protected virtual void Dispose(bool disposing)
        {
            DeleteOption();
            if (D != IntPtr.Zero)
            {
                Disconnect_AZ_Decoder(ref D);
                D = IntPtr.Zero;
            }
            if (disposing)
            {
            }
            //FDisposed = true;
            //FCount = 0;
            FInfo = null;
            //FRejectionReason = -100;
        }

        private void DeleteOption()
        {
            if (O != IntPtr.Zero)
            {
                Delete_AZ_Options(ref O);
            }
        }
        bool CreateOptions()
        {
            if (optmode_Changed) O = IntPtr.Zero;
            if (O != IntPtr.Zero)
                return true;
#if WIN64
            O = Create_AZ_Options(D, ref optmode);
#else
            O = Create_AZ_Options(D
                , optmode.maxAZCount
                , optmode.speedMode
                , optmode.qualityMask
                , optmode.labelMode
                , optmode.cellColor
                , optmode.timeOut
                , optmode.filterMode
                , optmode.symbology
                , optmode.qzMode);
#endif
            optmode_Changed = false;
            return true;
        }

        public int MaxAZCount
        {
            get { return optmode.maxAZCount; }
            set { optmode.maxAZCount = value; optmode_Changed = true; }
        }//!< 1..10
        public AZ_DECODER_SPEED SpeedMode
        {
            get { return (AZ_DECODER_SPEED)optmode.speedMode; }
            set { optmode.speedMode = (int)value; optmode_Changed = true; }
        }    //!< Always SP_ROBUST

        public AZ_LABEL_MODE LabelMode
        {
            get { return (AZ_LABEL_MODE)optmode.labelMode; }
            set { optmode.labelMode = (int)value; optmode_Changed = true; }
        }     //!< Always LM_NORMAL
        public AZ_CELL_COLOR CellColor
        {
            get { return (AZ_CELL_COLOR)optmode.cellColor; }
            set { optmode.cellColor = (int)value; optmode_Changed = true; }
        }
        public int timeOut;      //!< timeOut in mls. 
        public AZ_FILTER_MODE FilterMode
        {
            get { return (AZ_FILTER_MODE)optmode.filterMode; }
            set { optmode.filterMode = (int)value; optmode_Changed = true; }
        }   //!< AZFM_NON | AZFM_AUTO
        public AZ_SYMBOL_MODE Symbology
        {
            get { return (AZ_SYMBOL_MODE)optmode.symbology; }
            set { optmode.symbology = (int)value; optmode_Changed = true; }
        }
        //!< AZ_S_NORMAL | AZ_S_RUNES 
        public AZ_QUALITY_MASK QualityMask
        {
            get { return (optmode.qzMode == 0 ? AZ_QUALITY_MASK.NO : AZ_QUALITY_MASK.ALL); }
            set { optmode.qzMode = (int)value; optmode_Changed = true; }
        }       //!< AZ_QZ_STANDARD | AZ_QZ_SMALL

        public AZ_QZ_MODE QzMode
        {
            get { return (AZ_QZ_MODE)optmode.qzMode; }
            set { optmode.qzMode = (int)value; optmode_Changed = true; }
        }

        public AZ_REJECTION_REASONS Rejection_Reason
        {
            get { return FRejectionReason; }
        }

        private int FCount = 0;
        private IntPtr O = IntPtr.Zero;
        private IntPtr D = IntPtr.Zero;
        TAZ_OptMode optmode;
        bool optmode_Changed = true;
        private AZ_REJECTION_REASONS FRejectionReason = AZ_REJECTION_REASONS.AZ_DISCONNECTED;


        private int DecodeArray2(Byte[] array, int rowCount, int colCount)
        {
            if (!CreateOptions())
                return -100;
            if (array.Length <= colCount * rowCount)
            {
                new Exception("Размер массива слишком мал.");
            }
            IntPtr[] img = new IntPtr[rowCount];
            unsafe
            {
                fixed (Byte* pbuf = array)
                {
                    fixed (IntPtr* imgPtr = img)
                    {
                        for (int i = 0; i < rowCount; i++)
                        {
                            byte* row_ptr = (byte*)(pbuf + i * colCount);
                            imgPtr[i] = new IntPtr(row_ptr);
                        }
                        IntPtr ptr = new IntPtr(imgPtr);
                        // decode
                        int Result = Decode_AZ_Bits(O, rowCount, colCount, ptr);
                        if (Result > 0)
                        {
                            FRejectionReason = AZ_REJECTION_REASONS.AZ_OK;
                            FCount = Result;
                            return Result;
                        }
                        if (Result <= 0)
                        {
                            FRejectionReason = (AZ_REJECTION_REASONS)Result;
                            FCount = 0;
                            return Result;
                        }
                        FRejectionReason = (AZ_REJECTION_REASONS.AZ_UNKNOWN);
                        FCount = 0;
                    }
                }
            }
            //GetImageInfo( );
            return -1;

        }

        public int Count
        {
            get { return FCount; }
        }
        public TAztec_Info Get_AZ_Info(int N)
        {
            if (!CreateOptions())
                return null;
            unsafe
            {
                IntPtr pInfo = Get_AZ_Info(O, N);
                TAztec_Info_Internal* aInfo = (TAztec_Info_Internal*)pInfo;
                if (aInfo == null)
                    return null;
                else
                    return new TAztec_Info(this, aInfo);
            }
        }

        //private TAztec_Info[ ] FInfo = null;
        TAztec_Info FInfo = null;
        /*unsafe internal String GetDecodedString(TAztec_Info_Internal* AZ_Info)
        {
            Decoder D = Encoding.GetEncoding(1251).GetDecoder();
            return GetDecodedString(AZ_Info, D);
        }
        unsafe internal String GetDecodedString(TAztec_Info_Internal* AZ_Info,int CodePage)
        {
            Decoder D = Encoding.GetEncoding(CodePage).GetDecoder();
            return GetDecodedString(AZ_Info, D);
        }
        unsafe internal String GetDecodedString(TAztec_Info_Internal* AZ_Info, System.Text.Decoder D)
        {
            if (AZ_Info == null) return "";
            int L = AZ_Info->pchlen * 2;
            if (L == 0) return "";
            byte* resPtr = AZ_Info->pch;
            char[] C = new char[L];
            fixed (char* PC = C)
            {
                int bytesUsed; int charsUsed; bool completed;

                D.Convert(resPtr, AZ_Info->pchlen, PC, L, true, out bytesUsed, out charsUsed, out completed);
                if (completed)
                    return new String(PC, 0, charsUsed);
                else
                    return "";
            }
        }*/

        public int DecodeImage(Bitmap image)
        {
            if (!CreateOptions())
                return -100;
            int rowCount = image.Height;
            int colCount = image.Width;
            // image array
            Byte[] imgBuf = new byte[rowCount * colCount];
            // convert bitmap to imgBuf
            icUtils.BitmapToArray(image, imgBuf);

            FInfo = null;
            FCount = 0;
            FRejectionReason = AZ_REJECTION_REASONS.AZ_NotFound_Error;

            int Result = DecodeArray2(imgBuf, rowCount, colCount);
            if (Result > 0)
            {
                FInfo = Get_AZ_Info(0);
                FCount = 1;
            }
            return Result;
        }


        public int GetDebugProperty(int PropIndex, int Row, int Col)
        {
            return Get_AZ_DebugProp(O, PropIndex, Row, Col);
        }
    }//icAztecPro
}

