using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using PPScanControl;

namespace WPF_ScanControl_Sample
{
    /// <summary>
    /// MainWindow.xaml 的互動邏輯
    /// </summary>
    public partial class MainWindow : Window
    {
        private ScannerType scannerType;
        private ScanControl scanControl;
        private string outputDirctory;
        private readonly List<int> dpiList = new List<int> { 300, 400, 500, 600 };
        private readonly List<ImageType> extList = new List<ImageType> { ImageType.BMP, ImageType.JPG, ImageType.PNG };
        private CancellationTokenSource scanCompleteCTS;
        private ScanCompletedEventArgs tempResult;

        public MainWindow()
        {
            InitializeComponent();
        }

        private void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
        {
            scanControl = new ScanControl();
            //scanControl.ScanCompleted += ScanControl_ScanCompleted;
            scanControl.ButtonPressed += ScanControl_ButtonPressed;
            lbApiVersion.Content = scanControl.GetApiVersion();
            cbDpi.ItemsSource = dpiList;
            cbDpi.SelectedIndex = 0;
            cbFormat.ItemsSource = extList;
            cbFormat.SelectedIndex = 1;
            toggle1.IsChecked = true;

            // auto detect at app open
            GetScanners();
            
            // create output directory
            outputDirctory = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures) + @"\PPScanner\";
            lbOutputPath.Content = outputDirctory;
            if (!Directory.Exists(outputDirctory))
            {
                try
                {
                    Directory.CreateDirectory(outputDirctory);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(this, Properties.Resources.MsgCreateTempFailed + ex.Message, null, MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
        }

        private void GetScanners()
        {
            scannerType = scanControl.GetConnectedScanner();
            lbCurrentScanner.Content = scannerType;
            if (scannerType == ScannerType.None)
            {
                lbCurrentScanner.Foreground = Brushes.Red;
                SetButtonEnable(false);
            }
            else
            {
                lbCurrentScanner.Foreground = Brushes.Green;
                btnOpen.IsEnabled = true;
            }
        }

        private void BtnGetScanners_OnClick(object sender, RoutedEventArgs e)
        {
            if (scanControl == null)
                scanControl = new ScanControl();
            GetScanners();
            if (scannerType == ScannerType.None)
            {
                MessageBox.Show(this, Properties.Resources.MsgNoScanner, "", MessageBoxButton.OK, MessageBoxImage.Information);
            }
            else
            {
                MessageBox.Show(this, Properties.Resources.MsgCurrentScanner + scannerType, "", MessageBoxButton.OK, MessageBoxImage.Information);
            }
        }

        private void BtnOpen_OnClick(object sender, RoutedEventArgs e)
        {
            Cursor = Cursors.Wait;
            ReturnCode code = scanControl.Open(scannerType);
            Cursor = Cursors.Arrow;
            if (code != ReturnCode.Success)
            {
                ShowErrorMessage(this, code);
                SetButtonEnable(false);
                if (code == ReturnCode.ScannerNotFound)
                {
                    lbOpenStatus.Content = "Closed";
                    lbOpenStatus.Foreground = Brushes.Black;
                }
                else
                {
                    lbOpenStatus.Content = "Error";
                    lbOpenStatus.Foreground = Brushes.Red;
                    btnOpen.IsEnabled = true;
                }
            }
            else
            {
                SetButtonEnable(true);
                btnOpen.IsEnabled = false;
            }
        }

        private void BtnNeedCalibrate_OnClick(object sender, RoutedEventArgs e)
        {
            ReturnCode code = scanControl.IsCalibrateNeeded();
            if (code == ReturnCode.Success)
                MessageBox.Show(this, Properties.Resources.MsgNoNeedCalibrate, "", MessageBoxButton.OK, MessageBoxImage.Information);
            else
                ShowErrorMessage(this, code);
        }

        private void BtnCalibrate_OnClick(object sender, RoutedEventArgs e)
        {
            A6_CalibrateWizard wizard = new A6_CalibrateWizard(scanControl);
            wizard.Owner = this;
            wizard.ShowDialog();
        }

        private void BtnStartScan_OnClick(object sender, RoutedEventArgs e)
        {
            string outputFileName = outputDirctory + "A6_" + DateTime.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + "." + GetExt().ToString().ToLower();
            ReturnCode code = scanControl.StartScan(GetDpi(), GetExt(), outputFileName);
            if (code != ReturnCode.Success)
            {
                ShowErrorMessage(this, code);
            }
            else
            {
                lbOpenStatus.Content = "Scanning";
                lbOpenStatus.Foreground = Brushes.Red;
            }
        }

        private void BtnStartScanMemory_OnClick(object sender, RoutedEventArgs e)
        {
            ReturnCode code = scanControl.StartScan(GetDpi(), GetExt(), null);
            if (code != ReturnCode.Success)
            {
                ShowErrorMessage(this, code);
            }
            else
            {
                lbOpenStatus.Content = "Scanning";
                lbOpenStatus.Foreground = Brushes.Red;
            }
        }

        private void ScanControl_ScanCompleted(object sender, ScanCompletedEventArgs e)
        {
            HandleScanComplete(e);

            lbOpenStatus.Content = "Opened";
            lbOpenStatus.Foreground = Brushes.Green;
        }

        private void HandleScanComplete(ScanCompletedEventArgs result)
        {
            if (result.Result == ReturnCode.Success)
            {
                if (!string.IsNullOrEmpty(result.FileName))
                {
                    using (FileStream fileStream = new FileStream(result.FileName, FileMode.Open))
                    {
                        ShowResultImage(fileStream);
                    }
                    MessageBox.Show(this, Properties.Resources.MsgScanReturnSuccess + result.FileName, "", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                else if (!string.IsNullOrEmpty(result.ImageData))
                {
                    byte[] imageBytes = Convert.FromBase64String(result.ImageData);
                    using (MemoryStream memoryStream = new MemoryStream(imageBytes))
                    {
                        ShowResultImage(memoryStream);
                    }
                    MessageBox.Show(this, Properties.Resources.MsgScanReturnSuccess, "", MessageBoxButton.OK, MessageBoxImage.Information);
                }
                else
                {
                    ShowErrorMessage(this, result.Result, Properties.Resources.MsgScanReturnError);
                }
            }
            else
            {
                ShowErrorMessage(this, result.Result, Properties.Resources.MsgScanReturnError);
            }
        }

        private void ShowResultImage(Stream stream)
        {
            try
            {
                BitmapImage bitmapImage = new BitmapImage();
                bitmapImage.BeginInit();
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.StreamSource = stream;
                bitmapImage.EndInit();
                bitmapImage.Freeze();
                imagePreview.Source = bitmapImage;
            }
            catch (Exception ex)
            {
                MessageBox.Show(this, ex.ToString(), "", MessageBoxButton.OK, MessageBoxImage.Error);
            }
            finally
            {
                stream.Close();
            }
        }

        private void BtnClose_OnClick(object sender, RoutedEventArgs e)
        {
            Cursor = Cursors.Wait;
            scanControl.ScanCompleted -= ScanControl_ScanCompleted;
            scanControl.ScanCompleted -= ScanControl_ScanCompleted_v2;
            scanControl.ButtonPressed -= ScanControl_ButtonPressed;
            scanControl.Close();
            scanControl = null;
            scannerType = ScannerType.None;
            lbCurrentScanner.Content = scannerType;
            SetButtonEnable(false);
            Cursor = Cursors.Arrow;

            if (scanCompleteCTS != null)
            {
                scanCompleteCTS.Cancel();
                scanCompleteCTS.Dispose();
                scanCompleteCTS = null;
            }

            // auto detect again
            scanControl = new ScanControl();
            if (toggle1.IsChecked == true)
                scanControl.ScanCompleted += ScanControl_ScanCompleted;
            else
                scanControl.ScanCompleted += ScanControl_ScanCompleted_v2;
            scanControl.ButtonPressed += ScanControl_ButtonPressed;
            GetScanners();
        }

        private void SetButtonEnable(bool isEnabled)
        {
            btnOpen.IsEnabled = isEnabled;
            btnNeedCalibrate.IsEnabled = isEnabled;
            btnCalibrate.IsEnabled = isEnabled;
            btnStartScan.IsEnabled = isEnabled && toggle1.IsChecked == true;
            btnStartScanMemory.IsEnabled = isEnabled && toggle1.IsChecked == true;
            btnStartScanV2.IsEnabled = isEnabled && toggle2.IsChecked == true;
            btnClose.IsEnabled = isEnabled;
            if (!isEnabled)
            {
                lbOpenStatus.Content = "Closed";
                lbOpenStatus.Foreground = Brushes.Black;
            }
            else
            {
                lbOpenStatus.Content = "Opened";
                lbOpenStatus.Foreground = Brushes.Green;
            }
        }

        private void BtnOpenFolder_OnClick(object sender, RoutedEventArgs e)
        {
            if (!string.IsNullOrEmpty(outputDirctory))
            {
                try
                {
                    Process.Start(outputDirctory);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(this, "Failed to open directory: " + ex.Message, null, MessageBoxButton.OK, MessageBoxImage.Warning);
                }
            }
        }

        public static void ShowErrorMessage(Window owner, ReturnCode returnCode, string appendText = null)
        {
            string msg = appendText;
            MessageBoxImage image = MessageBoxImage.Warning;
            switch (returnCode)
            {
                case ReturnCode.Success:
                    msg += Properties.Resources.MsgApiSuccess;
                    image = MessageBoxImage.Information;
                    break;
                case ReturnCode.ScannerNotFound:
                    msg += Properties.Resources.MsgNoScanner + returnCode + " (" + (int)returnCode + ")";
                    break;
                case ReturnCode.NoPaper:
                    msg += Properties.Resources.MsgNoPaper + returnCode + " (" + (int)returnCode + ")";
                    break;
                case ReturnCode.NeedCalibrate:
                    msg += Properties.Resources.MsgNeedCalibrate + " (" + (int)returnCode + ")";
                    break;
                default:
                    msg += Properties.Resources.MsgApiError + returnCode + " (" + (int)returnCode + ")";
                    break;
            }
            MessageBox.Show(owner, msg, "", MessageBoxButton.OK, image);
        }

        private void MainWindow_OnClosing(object sender, CancelEventArgs e)
        {
            if (scanControl != null)
            {
                scanControl.Close();
                scanControl = null;
            }
            if (scanCompleteCTS != null)
            {
                scanCompleteCTS.Cancel();
                scanCompleteCTS.Dispose();
                scanCompleteCTS = null;
            }
        }

        private void ScanControl_ButtonPressed(object sender, ButtonEventArgs e)
        {
            MessageBox.Show(this, Properties.Resources.MsgButtonPressed + e.ButtonId, "", MessageBoxButton.OK, MessageBoxImage.Information);
        }

        private int GetDpi()
        {
            int index = cbDpi.SelectedIndex;
            return dpiList[index];
        }

        private ImageType GetExt()
        {
            int index = cbFormat.SelectedIndex;
            return extList[index];
        }

        private async void BtnStartScanV2_OnClick(object sender, RoutedEventArgs e)
        {
            // prepare scan options
            ImageType imageType = GetExt();
            int dpi = GetDpi();
            string outputFileName = outputDirctory + "A6_" + DateTime.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture) + "." + imageType.ToString().ToLower();

            // we must start and wait TWAIN scanning on the same thread
            ReturnCode code = scanControl.StartScan(dpi, imageType, outputFileName);
            if (code != ReturnCode.Success)
            {
                ShowErrorMessage(this, code);
            }
            else
            {
                lbOpenStatus.Content = "Scanning";
                lbOpenStatus.Foreground = Brushes.Red;

                scanCompleteCTS = new CancellationTokenSource();
                try
                {
                    // wait scanning completed event forever on same thread
                    await Task.Delay(-1, scanCompleteCTS.Token);
                }
                catch (TaskCanceledException ex)
                {
                    // wait task is cancelled, looking for result
                    HandleScanComplete(tempResult);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(this, ex.ToString(), "", MessageBoxButton.OK, MessageBoxImage.Error);
                }

                lbOpenStatus.Content = "Opened";
                lbOpenStatus.Foreground = Brushes.Green;
            }
        }

        private void ScanControl_ScanCompleted_v2(object sender, ScanCompletedEventArgs e)
        {
            // store this event argument for future use
            tempResult = e;
            // cancel the active waiting task
            scanCompleteCTS?.Cancel();
        }

        private void ToggleNonBlocking_OnChecked(object sender, RoutedEventArgs e)
        {
            // switch to old event handler
            scanControl.ScanCompleted -= ScanControl_ScanCompleted_v2;
            scanControl.ScanCompleted += ScanControl_ScanCompleted;
            btnStartScan.IsEnabled = btnStartScanMemory.IsEnabled = btnCalibrate.IsEnabled;
            btnStartScanV2.IsEnabled = false;

            if (scanCompleteCTS != null)
            {
                scanCompleteCTS.Cancel();
                scanCompleteCTS.Dispose();
                scanCompleteCTS = null;
            }
        }

        private void ToggleBlocking_OnChecked(object sender, RoutedEventArgs e)
        {
            // switch to new event handler
            scanControl.ScanCompleted -= ScanControl_ScanCompleted;
            scanControl.ScanCompleted += ScanControl_ScanCompleted_v2;
            btnStartScan.IsEnabled = btnStartScanMemory.IsEnabled = false;
            btnStartScanV2.IsEnabled = btnCalibrate.IsEnabled;
        }
    }
}
