using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using PS_SDK_Net;

namespace AutoShoot
{
    public partial class MainForm : Form
    {
        CameraManager cameraManager;
        Session session = null;
        int frameCounter = 0;
        bool isRun = false;

        const string logFileName = "log.txt";

        public MainForm()
        {
            InitializeComponent();
            InitLog();

            AddMsg("start");

            try
            {
                cameraManager = new CameraManager();

                cameraManager.CameraListChanged += 
                    cameraManager_CameraListChanged;
                cameraManager.CameraInitializationError += 
                    cameraManager_CameraInitializationError;

                UpdateTimer();
                RealizeButtons();
                CameraConnect();
            }
            catch (PSResultException exc)
            {
                if (exc.Result == PSResult.PS_PSSDK_ALREADY_RUNNING)
                {
                    string m = "PS SDK Already Running";
                    MessageBox.Show(m);
                    AddMsg(m);
                }
                else
                {
                    AddMsg("init PSResultException: {0}", exc.Result);
                }

                Shown += (object sender, EventArgs e) => { Close(); };
            }
            catch (Exception ex)
            {
                AddMsg("init exception: {0}", ex);
                Shown += (object sender, EventArgs e) => { Close(); };
            }
        }

        void cameraManager_CameraInitializationError(PSResult res, string camSystemId)
        {
            AddMsg("Camera init error: {0} {1}", res, camSystemId);
        }

        static string FormatCameraInfo(PSCameraInfo cameraInfo)
        {
            return string.Format(
                "    Id: {0}\r\n" +
                "    Name: {1}\r\n" +
                "    PSM Version: {2}\r\n" +
                "    System Id: {3}\r\n" +
                "    Serial Number: {4}",
                cameraInfo.id, cameraInfo.name,
                cameraInfo.psmVersion, cameraInfo.systemId,
                cameraInfo.serialNumber);
        }

        void cameraManager_CameraListChanged(PSCameraInfo cameraInfo, 
            PSCameraConnectionState state)
        {
            AddMsg(
                "CameraListChanged event: {0}\r\n{1}",
                state, FormatCameraInfo(cameraInfo));

            BeginInvoke(new MethodInvoker(() =>
            {
                CameraConnect();
            }));
        }

        void RealizeButtons()
        {
            bool enabled = session != null;

            btnStartStop.Enabled = enabled;
            udDelay.Enabled = enabled;
            lblDelay.Enabled = enabled;
            lblFrameCount.Enabled = enabled;
            udFrameCount.Enabled = enabled;
            cbUnlimited.Enabled = enabled;
            cbDownload.Enabled = enabled;
            cbRemove.Enabled = enabled;
            cbPreview.Enabled = enabled;
            cbPropName.Enabled = enabled;
            cbPropVal.Enabled = enabled;
            lblPropName.Enabled = enabled;
            lblPropVal.Enabled = enabled;

            btnStartStop.Text = isRun ? "Stop" : "Start";
            udFrameCount.Enabled = enabled && !cbUnlimited.Checked;
        }

        void InitLog()
        {
            File.Delete(logFileName);
        }

        void AddMsg(string format, params object[] args)
        {
            using (StreamWriter sw = File.AppendText(logFileName))
            {
                sw.Write("{0} | ", DateTime.Now.ToString("hh:mm:ss.fff"));
                sw.WriteLine(format, args);
            }
        }

        void CameraConnect()
        {
            if (session != null)
                return;

            if (cameraManager.CameraList.Count == 0)
                return;

            PSCameraInfo fci = cameraManager.CameraList[0];

            AddMsg("Open camera: \r\n{0}", FormatCameraInfo(fci));
            session = cameraManager.OpenSession(fci.id);

            session.NewPreviewFrame += session_NewPreviewFrame;
            session.CameraError += session_CameraError;
            session.PropertyListChanged += session_PropertyListChanged;
            session.PropertyChanged += session_PropertyChanged;

            RealizePreview();
            RealizeButtons();
            RealizePropertyNameList();
            cbPropName.SelectedIndex = 0;
            RealizePropertyValueList();
        }

        private void session_CameraError(PSResult err)
        {
            AddMsg("CameraError event: {0}", err);

            BeginInvoke(new MethodInvoker(() =>
            {
                CameraDisconnect();
            }));
        }

        void CameraDisconnect()
        {
            if (session == null)
                return;

            cbPropVal.Items.Clear();
            cbPropName.Items.Clear();
            session.NewPreviewFrame -= session_NewPreviewFrame;
            session.CameraError -= session_CameraError;
            session.PropertyListChanged -= session_PropertyListChanged;
            session.PropertyChanged -= session_PropertyChanged;
            session.Close();
            session = null;
            pbPreview.Image = null;
            RealizeButtons();
        }

        private void btnStartStop_Click(object sender, EventArgs e)
        {
            if (isRun)
            {
                tmrShoot.Stop();
                isRun = false;
            }
            else
            {
                UpdateTimer();
                isRun = true;
                MakeNextFrame();
            }

            RealizeButtons();
        }

        void MakeNextFrame()
        {
            if (!isRun)
                return;

            session.ShootAsync(session_ShootCompleted);
        }

        private void tmrShoot_Tick(object sender, EventArgs e)
        {
            tmrShoot.Stop();
            MakeNextFrame();
        }

        private void NextFrame()
        {
            UpdateTimer();

            if (cbUnlimited.Checked)
                tmrShoot.Start();
            else
            {
                if (udFrameCount.Value > 1)
                {
                    udFrameCount.Value--;
                    tmrShoot.Start();
                }
                else
                {
                    isRun = false;
                    RealizeButtons();
                }
            }
        }

        private void session_ShootCompleted(PSFileInfo fileInfo)
        {
            BeginInvoke(new MethodInvoker(() =>
            {
                AddMsg("Shooting completed: {0} ({1})", fileInfo.name, ++frameCounter);

                if (cbDownload.Checked)
                {
                    string resFile = Path.Combine(Directory.GetCurrentDirectory(), 
                        fileInfo.name);
                    session.DownloadFileToAsync(fileInfo.id,
                        resFile,
                        cbRemove.Checked, () =>
                        {
                            BeginInvoke(new MethodInvoker(() =>
                            {
                                AddMsg("Downloading completed: {0}", resFile);
                                NextFrame();
                            }));
                        });
                }
                else if (cbRemove.Checked)
                {
                    session.DeleteFile(fileInfo.id);
                    NextFrame();
                }
                else
                    NextFrame();
            }));
        }

        private void session_NewPreviewFrame()
        {
            BeginInvoke(new MethodInvoker(() =>
            {
                try
                {
                    if (session != null)
                        pbPreview.Image = session.GetPreviewFrame();
                }
                catch (PSException) { }
            }));            
        }

        private void udDelay_ValueChanged(object sender, EventArgs e)
        {
            UpdateTimer();
        }

        void UpdateTimer()
        {
            int i = (int)udDelay.Value * 1000;
            if ( i == 0 )
                i = 1;

            tmrShoot.Interval = i;
        }

        private void cbUnlimited_CheckedChanged(object sender, EventArgs e)
        {
            RealizeButtons();
        }

        private void cbPreview_CheckedChanged(object sender, EventArgs e)
        {
            RealizePreview();
        }

        private void RealizePreview()
        {
            if (session == null)
                return;

            session.PreviewState = cbPreview.Checked 
                ? PSPreviewState.PS_PREVIEW_ENABLED 
                : PSPreviewState.PS_PREVIEW_DISABLED;

            if (!cbPreview.Checked)
                pbPreview.Image = null;
        }

        private void RealizePropertyNameList()
        {
            if (session == null)
                return;

            cbPropName.Items.Clear();

            foreach (var v in session.Property.AvailableProperty)
                cbPropName.Items.Add(v);
        }

        private void RealizePropertyValueList()
        {
            if (session == null)
                return;

            cbPropVal.Items.Clear();

            Property ap = cbPropName.SelectedItem as Property;

            foreach (var v in ap.AvailableValues)
            {
                cbPropVal.Items.Add(v);
            }

            cbPropVal.SelectedItem = ap.Value;
            RealizePropertyValueList_Enabled();
        }

        private void RealizePropertyValueList_Enabled()
        {
            if (session == null)
                return;

            Property ap = cbPropName.SelectedItem as Property;
            if (ap != null)
                cbPropVal.Enabled = ap.isRW;
        }

        void session_PropertyChanged(PSProp prop)
        {
            BeginInvoke(new MethodInvoker(() =>
            {
                Property c = cbPropName.SelectedItem as Property;

                if (c.Prop == prop)
                    RealizePropertyValueList();
            }));
        }

        void session_PropertyListChanged()
        {
            BeginInvoke(new MethodInvoker(() =>
            {
                object prev = cbPropName.SelectedItem;
                if (prev == null)
                    return;
                RealizePropertyNameList();
                cbPropName.SelectedItem = prev;
                if (cbPropName.SelectedItem == null)
                    cbPropName.SelectedIndex = 0;
                RealizePropertyValueList();
            }));
        }

        private void cbPropName_SelectedIndexChanged(object sender, EventArgs e)
        {
            RealizePropertyValueList();
        }

        private void cbPropVal_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (session == null)
                return;

            Property c = cbPropName.SelectedItem as Property;

            if (!c.isRW)
                return;

            c.Value = cbPropVal.SelectedItem as ProperyValue;
        }
    }
}