using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Reflection;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace ScanDemo
{
    public partial class MainWindow : Form
    {
        private CyberCs.ScanDataCallback m_fullImageCallback;
        private CyberCs.ScanPartDataCallback m_partImageCallback;
        private CyberCs.ErrorReportCallback m_errorReportCallback;

        private const int MAXIMAGES = 20;
        private List<Bitmap> m_images = new List<Bitmap>(MAXIMAGES);
        private int m_currentImageIndex = -1;


        private CpuEater m_cputEater = new CpuEater();

        private bool m_Scanner_Needs_Reset = true;
        private string m_errorMsg = "";
        private string m_statusMsg = "  ==>  Scanner not Configured";

        private PixelFormat m_pixelFormat = PixelFormat.Format24bppRgb;
        private ColorPalette m_greyPalette;
        private int m_gammaValue;
        private int m_lightValue;
        private int m_bwThresholdValue;

        public MainWindow()
        {
            InitializeComponent();
            m_fullImageCallback = ScanDataHandler;
            m_partImageCallback = ScanPartDataHandler;
            m_errorReportCallback = ErrorMessageHandler;
        }

        // On window load initialise the scanner
        private void MainWindow_Load(object sender, EventArgs e)
        {
            InitScanner();
            // status timer 
            paperStatusTm.Enabled = true;

            // We need to create 8 bit grayscale palette for 8 bit indexed scans
            m_greyPalette = new Bitmap(1, 1, PixelFormat.Format8bppIndexed).Palette;
            for (int i = 0; i < 256; i++)
            {
                m_greyPalette.Entries[i] = Color.FromArgb(i, i, i);
            }

            // Default settings
            partScanBtn.Enabled = false;
            radioButtonFullColour.Checked = true;
            radioButton200DPI.Checked = true;
            m_gammaValue = 8;
            numericUpDownGamma.Value = m_gammaValue;
            m_lightValue = 67;
            numericUpDownLight.Value = m_lightValue;    
            m_bwThresholdValue = 173;
            numericUpDownBWThreshold.Value = m_bwThresholdValue;

        }

        private bool InitScanner()
        {
            // Initialise the scanner
            var ret = CyberCs.ScInit(m_fullImageCallback, m_errorReportCallback);
            if (ret == 0)
            {
                m_errorMsg = "  ==>  Unable to init scanner";
                MessageBox.Show(m_errorMsg);
                m_Scanner_Needs_Reset = true;
            }
            else
            {
                m_Scanner_Needs_Reset = false;
            }
            return !m_Scanner_Needs_Reset;
        }


        private void StartScnBtn_Click(object sender, EventArgs e)
        {
            if ( m_Scanner_Needs_Reset )
            {
                if (! InitScanner() ) return;
            }
            
            // Basically configure the scanner once the, "ready for scans" button is pressed
            // this is a one time configuration unless the user wants to change settings or there is a major error 
            try
            {
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_STOPSCAN, 0);

                SetColorDepth();

                SetDPI();

                SetBWThreshold();
                SetLight();       // not sure this does anything !
                SetGamma();

                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_STARTSCAN, 0);
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_FORCESCAN, 0);

                m_statusMsg = "  ==>  Scanner Ready";
                partScanBtn.Enabled = true;
            }
            catch (Exception ex)
            {
                m_statusMsg = "  ==>  Config Problem ";
                m_errorMsg = "  ==>  " + ex.Message.ToString();
                m_Scanner_Needs_Reset = true;
            }
        }

        // Gray Scale and Colour modes use gamma setting
        private void SetGamma()
        {
            
            m_gammaValue = (int)numericUpDownGamma.Value;
            CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_SET_GAMMA, m_gammaValue);
            m_errorMsg = "";
        }

        private void SetLight()
        {
            // Light setting does not appear to have much effect ? 
            m_lightValue = (int)numericUpDownLight.Value;
            CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_SET_LIGHT, m_lightValue);
            m_errorMsg = "";
        }

        // in B/W mode this provides more precise control than gamma
        // but it does not apply to grayscale / full colour modes 
        private void SetBWThreshold()
        {
            if (m_pixelFormat.Equals(PixelFormat.Format1bppIndexed))
            {
                m_bwThresholdValue = (int)numericUpDownBWThreshold.Value;
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_SET_BW, m_bwThresholdValue);
                m_errorMsg = "";
            }
            else
            {
                m_errorMsg  = "  ==>  B/W Threshold only applies in B/W mode";
            }
        }

        private void SetDPI()
        {
            if (radioButton200DPI.Checked)
            {
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_SET_DEV, (int)DeviceCode.EDEV200A4_1728);  // 200 DPI
            }
            else if (radioButton300DPI.Checked)
            {
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_SET_DEV, (int)DeviceCode.EDEV300A4_2592);  // 300 DPI
            }
        }

        private void SetColorDepth()
        {
            if (radioButtonBW.Checked)
            {
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_SET_RESO, (int)Resolutions.EACRmFULL2);  // 1 bit B/W
                m_pixelFormat = PixelFormat.Format1bppIndexed;
            }
            else if (radioButtonGrayScale.Checked)
            {
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_SET_RESO, (int)Resolutions.EACRmHALF256);  // 8 bit Gray Scale
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_SET_RESO, 0x80);
                m_pixelFormat = PixelFormat.Format8bppIndexed;
            }
            else if (radioButtonFullColour.Checked)
            {
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_SET_RESO, (int)Resolutions.EACRmFULL256);  // 24 bit Colour
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_SET_RESO, 0x60);
                m_pixelFormat = PixelFormat.Format24bppRgb;
            }
        }


        private void ReceiveImage(Bitmap image)
        {
            // keep image list size trimmed to the defined max number 
            if (m_images.Count < MAXIMAGES)
            {
                m_images.Add(image);
                m_currentImageIndex = m_images.Count - 1;
            }
            else
            {
                m_images.RemoveAt(0);
            }

            // resize image to fit the pictureBox but keep aspect ratio
            float aspectRatio = (float)image.Width / (float)image.Height;
            var scaledImage = new Bitmap(image, (int)((float)scannedImageBox.Size.Height * aspectRatio), scannedImageBox.Size.Height);
            scannedImageBox.Image = scaledImage;

            nextImageBtn.Enabled = (m_currentImageIndex < m_images.Count - 1);
            PreviousImageBtn.Enabled = (m_currentImageIndex > 0);
        }

        private void nextImageBtn_Click(object sender, EventArgs e)
        {
            if (m_currentImageIndex >= m_images.Count - 1) return;

            m_currentImageIndex++;
            var nextImage = m_images[m_currentImageIndex];

            // resize image to fit the pictureBox but keep aspect ratio
            float aspectRatio = (float)nextImage.Width / (float)nextImage.Height;
            var scaledImage = new Bitmap(nextImage, (int)((float)scannedImageBox.Size.Height * aspectRatio), scannedImageBox.Size.Height);
            scannedImageBox.Image = scaledImage;

            nextImageBtn.Enabled = (m_currentImageIndex < m_images.Count - 1);
            PreviousImageBtn.Enabled = (m_currentImageIndex > 0);
        }

        private void PreviousImageBtn_Click(object sender, EventArgs e)
        {
            if (m_currentImageIndex < 0) return;

            m_currentImageIndex--;
            var previousImage = m_images[m_currentImageIndex];

            // resize image to fit the pictureBox but keep aspect ratio
            var scaledImage = new Bitmap(previousImage, scannedImageBox.Size.Width, scannedImageBox.Size.Height);
            scannedImageBox.Image = scaledImage;

            nextImageBtn.Enabled = (m_currentImageIndex < m_images.Count - 1);
            PreviousImageBtn.Enabled = (m_currentImageIndex > 0);
        }


        private void partScanBtn_Click(object sender, EventArgs e)
        {
            int[] steps = { 100, 100, 100 };
            int numSteps = steps.Length;
            CyberCs.ScSetupPartScan(steps, numSteps, m_partImageCallback);

        }

        private void rejectBtn_Click(object sender, EventArgs e)
        {
            CyberCs.ScRejectPaper();
        }

        private void loadBtn_Click(object sender, EventArgs e)
        {
            if (m_cputEater.IsRunning)
            {
                m_cputEater.Stop();
            }
            else
            {
                m_cputEater.Run();
            }
        }


        private void paperStatusTm_Tick(object sender, EventArgs e)
        {
            var paper = CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_PAPER, 0);
            if (paper != 0)
            {
                paperLbl.Text = "Paper Detected " + m_statusMsg + m_errorMsg;
            }
            else
            {
                paperLbl.Text = "No Paper " + m_statusMsg + m_errorMsg;
            }

        }

        private void radioButtonGrayScale_CheckedChanged(object sender, EventArgs e)
        {
            SetColorDepth();
        }

        private void radioButtonBW_CheckedChanged(object sender, EventArgs e)
        {
            SetColorDepth();
        }

        private void radioButtonFullColour_CheckedChanged(object sender, EventArgs e)
        {
            SetColorDepth();
        }

        private void radioButton200DPI_CheckedChanged(object sender, EventArgs e)
        {
            SetDPI();
        }

        private void radioButton300DPI_CheckedChanged(object sender, EventArgs e)
        {
            SetDPI();
        }

        private void numericUpDownGamma_ValueChanged(object sender, EventArgs e)
        {
            SetGamma();
        }


        private void numericUpDownLight_ValueChanged(object sender, EventArgs e)
        {
            SetLight();
        }

        private void numericUpDownBWThreshold_ValueChanged(object sender, EventArgs e)
        {
            SetBWThreshold();
        }

        private void CalibrateBtn_Click(object sender, EventArgs e)
        {
            string StatusText = "";
            try
            {
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_STOPSCAN, 0);
                if (MessageBox.Show("Insert Blank A4 sheet of paper and then click OK", "ReCalibration",
                                     MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) == DialogResult.OK)
                {
                    if (CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_RECALIBRATE, 0) > 0)
                    {
                        m_statusMsg = " ==> Scanner Calibrated OK";
                        m_errorMsg = "";
                    }
                    else
                    {
                        m_statusMsg = " ==> Problem ReCalibrating scanner";
                        m_errorMsg = " ==> Please try again";
                    }
                }
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_STARTSCAN, 0);
            }
            catch (Exception ex)
            {
                m_statusMsg = " ==> Error Calibrating scanner";
                m_errorMsg = " ==> " + ex.Message;
                StatusText = m_statusMsg + " " + m_errorMsg;
                MessageBox.Show(StatusText, "ReCalibration Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                CyberCs.ScSCalibrate(ConfigSettings.CV_CAL_STARTSCAN, 0);
            }

        }

        // ----------------------------------------------------------------------------
        // From here down these are callback functions. These are not in the UI thread.
        // ---------------------------------------------------------------------------- 
        private void ScanDataHandler(ref CyberImg img)
        {
            var bitmap = new Bitmap(img.wWid, img.wHei, img.wWidBy, m_pixelFormat, img.pIm);

            // Grayscale images need a defined palette assigned
            if (m_pixelFormat == PixelFormat.Format8bppIndexed)
            {
                bitmap.Palette = m_greyPalette;
            }

            // images come upside down from the scanner so we need to flip them
            bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
            Invoke((Action)delegate () { ReceiveImage(new Bitmap(bitmap)); });
        }

        private bool ScanPartDataHandler(ref CyberImg img)
        {
            var bitmap = new Bitmap(img.wWid, img.wHei, img.wWidBy, m_pixelFormat, img.pIm);

            // Grayscale images need a defined palette assigned
            if (m_pixelFormat == PixelFormat.Format8bppIndexed)
            {
                bitmap.Palette = m_greyPalette;
            }

            // images come upside down from the scanner so we need to flip them
            bitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
            Invoke((Action)delegate () { ReceiveImage(new Bitmap(bitmap)); });
            return true;
            //return false;
        }

        // The second (dwType) parameter must be interpreted on the basis of the first parameter (dwError)
        // because for CV_BARCODE, CV_DEBUG it is actually a pointer to a string. All pointers are to memory
        // within the DLL and should not be modified.
        private void ErrorMessageHandler(int dwError, UIntPtr dwType)
        {
            switch (dwError)
            {
                case (int)cvErrors.CV_BARCODE:
                {
                    // Barcode read - dwType is a pointer to a string
                    IntPtr strPtr = (IntPtr)dwType.ToUInt64();
                    string barcodeStr = Marshal.PtrToStringAnsi(strPtr);
                    m_statusMsg = " ";
                    m_errorMsg = "  ==>  Barcode Read: " + barcodeStr;
                    break;
                }
                case (int)cvErrors.CV_DEBUG:
                {
                    // Debug message - dwType is a pointer to a string
                    IntPtr strPtr = (IntPtr)dwType.ToUInt64();
                    string debugStr = Marshal.PtrToStringAnsi(strPtr);
                    m_statusMsg = " ==> Error ";
                    m_errorMsg = "  ==>  Debug: " + debugStr;
                    m_Scanner_Needs_Reset = true;
                    break;
                }
                case (int)cvErrors.CV_SCANNER_NOT_CONNECTED:
                {
                    m_statusMsg = " ==> Error ";
                    m_errorMsg = "  ==>  Scanner Not Connected";
                    m_Scanner_Needs_Reset = true;
                    break;
                }
                case (int)cvErrors.CV_SCANNER_ERROR_CALIB:
                {
                    m_statusMsg = " ==> Error ";
                    m_errorMsg = "  ==>  Scanner Calibration Error";
                    m_Scanner_Needs_Reset = true;
                    break;
                }
                case (int)cvErrors.CV_SCAN_ABORTED:
                {
                    m_statusMsg = " ==> Scan Aborted ";
                    m_errorMsg = "  ==>  User Abort";
                    break;
                }
                case (int)cvErrors.CV_BARCODE_CHECKSUM_BAD:
                {
                    m_statusMsg = " ==> Error ";
                    m_errorMsg = "  ==> Barcode Checksum Error";
                    break;
                }
                default:
                {
                    m_statusMsg = " ==> Error ";
                    m_errorMsg = "  ==>  Scanner Unknown Error";
                    m_Scanner_Needs_Reset = true;
                    break;
                }
            }
        }
    }
}
