using System;
using System.Runtime.InteropServices;
using System.Drawing;
using GrayScaleClass;

namespace MicroPdf417Net
{
   public enum TMPDF417_SpeedMode
   {
      SP_Fast = 1,
      SP_Robust = 0
   }

   public enum TMPDF417_DirectMode
   {
      DM_LeftRight = 0,
      DM_Horizontal = 1,
      DM_All = 2,
      DM_Vertical = 3
   }

   public enum TMPDF_Rejection_Reasons
   {
      PDF417_OK = 0,
      PDF417_Rectangle_Error     = -1, // no Start and Stop Patterns were found
      MPDF417_Invalid_Argument   = -2,
      MPDF417_NotFound           = -3,
      PDF417_ColCount_Error      = -4,
      PDF417_RowCount_Error      = -5,
      PDF417_ECCodeword_Error    = -6,
      PDF417_ErasuresLimit_Error = -7, // too many codewords with improper clasters
      PDF417_RS_Error = -8, // too many errors in Reed Solomon code
      PDF417_CharEncodation_Error = -9, // unexpected encodation scheme
      PDF417_StartPattern_Error  = -10, // Stop pattern is not found
      PDF417_StopPattern_Error   = -11, // Stop pattern is not found
      PDF417_Indicators_Error    = -12, // incorrect data in row indicators
      PDF417_Geometry_Error      = -13, // unallowed module size
      PDF417_NOMEMORY            = -99,
      PDF417_UNKNOWN             = -100,
      PDF417_DISCONNECTED        = -200
   }

   public class MicroPdf417Options
   {
      public TMPDF417_SpeedMode SpeedMode;
      public TMPDF417_DirectMode DirectMode;
      
      public MicroPdf417Options()
      {
         SpeedMode = TMPDF417_SpeedMode.SP_Robust;
         DirectMode = TMPDF417_DirectMode.DM_All;
      }
   }

   public class MicroPdf417Decoder
    {
      internal unsafe struct TMPDF417_OptMode
      {
         public Int32 SpeedMode;       // RESERVED FOR FUTURE!
         public Int32 DirectMode;      // RESERVED FOR FUTURE!
         
         public TMPDF417_OptMode(MicroPdf417Options opt)
         {
            SpeedMode = (Int32)opt.SpeedMode;
            DirectMode = (Int32)opt.DirectMode;
         }
      }

#pragma warning disable 0649
      internal unsafe struct TMPDF417_ImageInfo
      {
         public Int32 RejectionReason;   // Result
      }

      internal unsafe struct TMPDF417_Info // //result of decoding per each PDF417 symbol
      {
         public Int32 rowcols0;    // symbol corners
         public Int32 rowcols1;
         public Int32 rowcols2;
         public Int32 rowcols3;
         public Int32 rowcols4;
         public Int32 rowcols5;
         public Int32 rowcols6;
         public Int32 rowcols7;

         public Int32 rcFlag;        // true if a qudrangle symbol was found
         public Int32 RSErrorCount;  // number of Reed Solomon errors
         public Int32 ColCount;      // horizontal dimension (from 1 to 30)
         public Int32 RowCount;      // vertical dimension   (from 4 to 90)
         public Int32 pchlen;        // length of decoded byte array
         public byte* pch;// decoded byte array
      }
#pragma warning restore 0649

      private const string LIB_NAME = "MicroPDF417.dll";
      [DllImport(LIB_NAME, EntryPoint = "Connect_MPDF417_Decoder", CallingConvention = CallingConvention.StdCall)]
      internal static unsafe extern IntPtr Connect_MPDF417_Decoder([In] Int32 MaxRowCount, [In] Int32 MaxColCount);

      [DllImport(LIB_NAME, EntryPoint = "Disconnect_MPDF417_Decoder", CallingConvention = CallingConvention.StdCall)]
      internal static unsafe extern void Disconnect_MPDF417_Decoder([In] IntPtr Decoder);

      [DllImport(LIB_NAME, EntryPoint = "Create_MPDF417_Options", CallingConvention = CallingConvention.StdCall)]
      internal static unsafe extern IntPtr Create_MPDF417_Options([In] IntPtr Decoder, [In]TMPDF417_OptMode opt);

      [DllImport(LIB_NAME, EntryPoint = "Delete_MPDF417_Options", CallingConvention = CallingConvention.StdCall)]
      internal static unsafe extern void Delete_MPDF417_Options([In] IntPtr Options);

      [DllImport(LIB_NAME, EntryPoint = "Get_MPDF417_ImageInfo", CallingConvention = CallingConvention.StdCall)]
      internal static unsafe extern IntPtr GetPDF417_ImageInfo([In] IntPtr DecoderO);

      [DllImport(LIB_NAME, EntryPoint = "Get_MPDF417_Info", CallingConvention = CallingConvention.StdCall)]
      internal static unsafe extern TMPDF417_Info* GetMPDF417_Info([In] IntPtr Decoder);

      [DllImport(LIB_NAME, EntryPoint = "Decode_MPDF417_Bits", CallingConvention = CallingConvention.StdCall)]
      internal static unsafe extern int Decode_MPDF417_Bits([In] IntPtr DecoderO, [In] Int32 RowCount, [In] Int32 ColCount, [In] IntPtr[] PPBits);

      [DllImport(LIB_NAME, EntryPoint = "MPDF417_GetVersion", CallingConvention = CallingConvention.StdCall)]
      internal static unsafe extern IntPtr MPDF417_Version();


      private unsafe IntPtr dec = IntPtr.Zero;
      private unsafe IntPtr opt = IntPtr.Zero;
      private bool optChanged = false;
      private MicroPdf417Options options = new MicroPdf417Options();
      private int pdfCount = 0;
      private TMPDF_Rejection_Reasons rr = TMPDF_Rejection_Reasons.MPDF417_NotFound;

      public MicroPdf417Decoder(int maxRowCount, int maxColCount)
      {
         unsafe
         {
            dec = Connect_MPDF417_Decoder(maxRowCount, maxColCount);
            if(dec == IntPtr.Zero)
            {
               throw new Exception("Connect_MPDF417_Decoder return NULL"); 
            }
         }
         optChanged = true;
      }

      ~MicroPdf417Decoder()
      {
         unsafe
         {
            if (dec != IntPtr.Zero)
            {
               Delete_MPDF417_Options(opt);
               opt = IntPtr.Zero;
               Disconnect_MPDF417_Decoder(dec);
               dec = IntPtr.Zero;
            }
         }
      }

      public void SetOptions(MicroPdf417Options newOptions)
      {
         options = newOptions;
      }

      public bool DecodeImage(Bitmap BMP)
      {
         pdfCount = 0;
         UpdateOptions();

         GrayScale G = new GrayScale(BMP);
         G.OnDecBits += this.DecodeBitsO;
         G.DecodeBits();
         unsafe
         {
            IntPtr ptr = GetPDF417_ImageInfo(opt);
            TMPDF417_ImageInfo* info = (TMPDF417_ImageInfo*)ptr.ToPointer();
            rr = (TMPDF_Rejection_Reasons)info->RejectionReason;
            if (rr == TMPDF_Rejection_Reasons.PDF417_OK)
            {
               pdfCount = 1;
               return true;
            }
            else
            {
               pdfCount = 0;
               return false;
            }
         }
      }

      private int DecodeBitsO(Int32 RowCount, Int32 ColCount, IntPtr[] PPBits)
      {
         unsafe
         {
            return Decode_MPDF417_Bits(opt, RowCount, ColCount, PPBits);
         }
      }
      private void UpdateOptions()
      {
         if (!optChanged) return;
         unsafe
         {
            if (opt != IntPtr.Zero)
               Delete_MPDF417_Options(opt);
            TMPDF417_OptMode optMode = new TMPDF417_OptMode(options);
            opt = Create_MPDF417_Options(dec, optMode);
            if (opt == IntPtr.Zero)
               throw new Exception("Create_MPDF417_Options return NULL!");
         }
         optChanged = false;
      }

      public string GetDecoderVersion()
      {
         unsafe
         {
            return Marshal.PtrToStringAnsi(MPDF417_Version());
         }
      }

      public String GetSymbolData(int codepage)
      {
         if (pdfCount == 0) return null;
         StringConvertor SC = new StringConvertor(codepage);
         unsafe
         {
            TMPDF417_Info* info = (TMPDF417_Info*)GetMPDF417_Info(opt);
            byte[] resBuf = new byte[info->pchlen];
            for (int i = 0; i < info->pchlen; i++)
               resBuf[i] = info->pch[i];
            return SC.ByteArrayToString(resBuf);
         }
      }

      public int[] GetSymbolRect()
      {
         if (pdfCount == 0) return null;
         int[] res = new int[8];
         unsafe
         {
            TMPDF417_Info* info = GetMPDF417_Info(opt);
            res[0] = info->rowcols0;
            res[1] = info->rowcols1;
            res[2] = info->rowcols2;
            res[3] = info->rowcols3;
            res[4] = info->rowcols4;
            res[5] = info->rowcols5;
            res[6] = info->rowcols6;
            res[7] = info->rowcols7;
         }
         return res;
      }


      public int SymbolCount()
      {
         return pdfCount;
      }

      public TMPDF_Rejection_Reasons RejectionReasons()
      {
         return rr;
      }

   }
}
