// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2021 Torsten Schache, Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Gdk;
using Gtk;
using Hiperscan.Common.I18n;
using Hiperscan.GtkExtensions.Widgets;
using Hiperscan.Unix;

namespace Hiperscan.Extensions.ApoIdent.Widgets
{
    public class Animation
    {
        public const int LINE_WIDTH = 2;

        public Animation(Gtk.Image state_image,
                          Gtk.Label state_label,
                          Gtk.Image info_button,
                          Gtk.EventBox frame_eventbox,
                          Gtk.Alignment menu_line_alignment,
                          Cairo.ImageSurface logo,
                          object transaction)
        {
            this.Transaction = transaction;
            this.apo_ident_logo = logo;

            this.MenuLineAlign = menu_line_alignment;
            this.StateImage = state_image;
            this.StatusAlign = this.StateImage.Parent.Parent as Gtk.Alignment;
            this.InfoButton = info_button;
            this.CanvasEventbox = frame_eventbox;

            this.StateLabel = state_label;
            WidgetColor.SetFontColor(this.StateLabel, ApoIdentColors.WHITE);

            this.InitPics();
            this.InitLabels();
            this.SetInfoIcon();
            this.MakeInfoButtonClickable();
            this.ActivateMouseOver();
            this.ResetError();

            this.Update(AnimationState.Failure);
            this.CanvasEventbox.ExposeEvent += this.OnFrameExposed;

            this.OuterLineWidth = Animation.LINE_WIDTH;
            this.MenuLineWidth = Animation.LINE_WIDTH;
        }

        private void InitPics()
        {
            string[] resoures = Assembly.GetExecutingAssembly().GetManifestResourceNames();
            string resource_template = "Hiperscan.Extensions.ApoIdent.Widgets.Animation.Icons.{0}.png";
            foreach (AnimationState state in Enum.GetValues(typeof(AnimationState)))
            {
                if (state == AnimationState.None)
                    continue;

                string resource = String.Format(resource_template, state.ToString());

                Pixbuf buf = new Gdk.Pixbuf(null, resource, 150, 150);
                this.Pictures.Add(state, buf);
            }
        }

        private void InitLabels()
        {
            this.Labels.Add(AnimationState.Working, Catalog.GetString("System is\nworking"));
            this.Labels.Add(AnimationState.Failure, Catalog.GetString("Failure"));
            this.Labels.Add(AnimationState.ReadyForSubstanceInput, Catalog.GetString("Ready for\nsubstance input"));
            this.Labels.Add(AnimationState.ReadyForProtocolInput, Catalog.GetString("Ready for\nprotocol input"));
            this.Labels.Add(AnimationState.ReadyForProtocolCreation, Catalog.GetString("Ready for\nprotocol creation"));
            this.Labels.Add(AnimationState.Saved, Catalog.GetString("Protocol\nsaved"));
            this.Labels.Add(AnimationState.SavingFailed, Catalog.GetString("Saving protocol\nfailed"));

            this.Labels.Add(AnimationState.InternalRecalibration, Catalog.GetString("Internal\nreferencing"));

            this.Labels.Add(AnimationState.ReadyForTransflectance, Catalog.GetString("Ready for\ntransflectance"));
            this.Labels.Add(AnimationState.MeasuringTransflectance, Catalog.GetString("Acquiring\ntransflectance"));

            this.Labels.Add(AnimationState.ReadyForTransflectanceAu, Catalog.GetString("Ready for\ntransflectance"));
            this.Labels.Add(AnimationState.MeasuringTransflectanceAu, Catalog.GetString("Acquiring\ntransflectance"));

            this.Labels.Add(AnimationState.ReadyForMeasurementFluid, Catalog.GetString("Ready for acquiring\nfluid substance"));
            this.Labels.Add(AnimationState.MeasuringFluid, Catalog.GetString("Acquiring\nfluid substance"));

            this.Labels.Add(AnimationState.ReadyForMeasurementSolid, Catalog.GetString("Ready for acquiring\nsolid substance"));
            this.Labels.Add(AnimationState.MeasuringSolid, Catalog.GetString("Acquiring\nsolid substance"));

            this.Labels.Add(AnimationState.ReadyForMeasurementSolidInsert, Catalog.GetString("Ready for acquiring\ninsert substance"));
            this.Labels.Add(AnimationState.MeasuringSolidInsert, Catalog.GetString("Acquiring\ninsert substance"));

            this.Labels.Add(AnimationState.ReadyForWhiteReference, Catalog.GetString("Ready for acquiring\nwhite reference"));
            this.Labels.Add(AnimationState.MeasuringWhiteReference, Catalog.GetString("Acquiring\nwhite reference"));

            this.Labels.Add(AnimationState.ReadyForWhiteInsertReference, Catalog.GetString("Ready for acquiring\ninsert white reference"));
            this.Labels.Add(AnimationState.MeasuringWhiteInsertReference, Catalog.GetString("Acquiring white\ninsert reference"));

            this.Labels.Add(AnimationState.ReadyForBlackReference, Catalog.GetString("Ready for acquiring\nblack reference"));
            this.Labels.Add(AnimationState.MeasuringBlackReference, Catalog.GetString("Acquiring\nblack reference"));

            this.Labels.Add(AnimationState.NotMeasurable, Catalog.GetString("Substance\nis not measurable"));
        }

        private void SetInfoIcon()
        {
            string resource = "Hiperscan.Extensions.ApoIdent.Widgets.Animation.Info.png";
            Pixbuf buf = new Gdk.Pixbuf(null, resource, 30, 30);
            this.InfoButton.Pixbuf = buf;
        }

        private void MakeInfoButtonClickable()
        {
            Gtk.Container parent = this.InfoButton.Parent as Gtk.Container;

            parent.Remove(this.InfoButton);
            this.GtkEventBox = new Gtk.EventBox();
            this.GtkEventBox.Add(this.InfoButton);
            parent.Add(this.GtkEventBox);

            this.GtkEventBox.AddEvents((int)Gdk.EventType.ButtonRelease);
            this.GtkEventBox.ButtonReleaseEvent += this.OnInternalButtonReleaseEvent;

            this.GtkEventBox.ModifyBg(Gtk.StateType.Normal, this.GdkBackGroundColor);
            this.GtkEventBox.ModifyBg(Gtk.StateType.Insensitive, this.GdkBackGroundColor);
        }

        private void ActivateMouseOver()
        {
            this.GtkEventBox.AddEvents((int)Gdk.EventType.EnterNotify);
            this.GtkEventBox.EnterNotifyEvent += (o, args) => this.SetCursor(new Gdk.Cursor(Gdk.CursorType.Hand2), EventArgs.Empty);
            this.GtkEventBox.AddEvents((int)Gdk.EventType.LeaveNotify);
            this.GtkEventBox.LeaveNotifyEvent += (o, args) => this.SetCursor(new Gdk.Cursor(Gdk.CursorType.Arrow), EventArgs.Empty);
        }

        private void SetCursor(object sender, EventArgs e)
        {
            if (this.InfoButton.IsRealized == false || this.InfoButton.GdkWindow == null)
                return;

            this.InfoButton.GdkWindow.Cursor = sender as Gdk.Cursor;
        }

        private void OnFrameExposed(object o, Gtk.ExposeEventArgs e)
        {
            Gdk.Window win = this.CanvasEventbox.GdkWindow;

            if (win == null)
                return;

            Cairo.Context ctx = Gdk.CairoHelper.Create(win);

            ctx.Rectangle(0, 0, this.CanvasEventbox.Allocation.Width, this.CanvasEventbox.Allocation.Height);
            ctx.SetSourceColor(this.CairoBackGroundColor);
            ctx.Fill();


            int x = 0;
            int y = 0;

            this.StatusAlign.TranslateCoordinates(this.CanvasEventbox, 0, 0, out x, out y);

            y += this.StatusbarTopLineWidth;

            ctx.MoveTo(x + this.StatusbarLeftLineWidth, y);
            ctx.LineTo(x + this.StatusbarLeftLineWidth, y + 115);
            ctx.LineTo(x + 50, y + 130);
            ctx.CurveTo(new Cairo.PointD(x + 50, y + 130),
                        new Cairo.PointD(x + 80, y + 140),
                        new Cairo.PointD(x + 110, y + 130));
            ctx.LineTo(x + 200 - this.StatusbarRightLineWidth, y + 100);
            ctx.LineTo(x + 200 - this.StatusbarRightLineWidth, y + 0);
            ctx.ClosePath();
            ctx.SetSourceColor(new Cairo.Color(1, 1, 1));
            ctx.Fill();

            this.ApoIdentLogo.Show(ctx, x + 25, y + 25);

            ((IDisposable)ctx.GetTarget()).Dispose();
            ((IDisposable)ctx).Dispose();

            foreach (Widget widget in this.CanvasEventbox.Children)
            {
                if (widget.Visible == true)
                    widget.SendExpose(e.Event);
            }
        }

        public void UpdateError(AnimationErrorState state, string message)
        {
            if (state == AnimationErrorState.None)
                return;

            string error_time = String.Format(Catalog.GetString("Last message at {0} "), DateTime.Now.ToString("HH:mm:ss"));

            this.CurrentErrorState = state;
            this.ErrorMessage = message;
            this.ErrorPdf = String.Empty;
            this.ErrorTitle = String.Empty;

            switch (state)
            {
                case AnimationErrorState.ConnectionIdentModuleFailed:
                    this.ErrorTitle = Catalog.GetString("Error Connection Ident Module");
                    break;

                case AnimationErrorState.ConnectionDeviceFailed:
                    this.ErrorTitle = Catalog.GetString("Error Connection Device");
                    break;

                case AnimationErrorState.ConnectionLost:
                    this.ErrorTitle = Catalog.GetString("Device Disconnected");
                    break;

                case AnimationErrorState.MeasuringFailed:
                    this.ErrorTitle = Catalog.GetString("Error Measuring");
                    break;

                case AnimationErrorState.TransflectanceFailed:
                    this.ErrorTitle = Catalog.GetString("Error Measuring Transflectance");
                    this.ErrorPdf = this.Url + "error_transflectance_reference.pdf";
                    break;

                case AnimationErrorState.BlackFailed:
                    this.ErrorTitle = Catalog.GetString("Error Measuring Black Reference");
                    this.ErrorPdf = this.Url + "error_black_reference.pdf";
                    break;

                case AnimationErrorState.WhiteFailed:
                    this.ErrorTitle = Catalog.GetString("Error Measuring White Reference");
                    this.ErrorPdf = this.Url + "error_white_reference.pdf";
                    break;

                case AnimationErrorState.IdentificationFluidFailed:
                    this.ErrorTitle = Catalog.GetString("No Identification Fluid");
                    this.ErrorPdf = this.Url + "misidentification_liquids.pdf";

                    this.IdentficationState = state;
                    this.IdentficationMessage = message;
                    this.IdentficationTitle = this.ErrorTitle;
                    this.IdentificationPdf = this.ErrorPdf;
                    this.IdentificationTime = error_time;
                    break;

                case AnimationErrorState.IdentificationSolidFailed:
                    this.ErrorTitle = Catalog.GetString("No Identification Solid");
                    this.ErrorPdf = this.Url + "misidentification_solids.pdf";

                    this.IdentficationState = state;
                    this.IdentficationMessage = message;
                    this.IdentficationTitle = this.ErrorTitle;
                    this.IdentificationPdf = this.ErrorPdf;
                    this.IdentificationTime = error_time;
                    break;

                case AnimationErrorState.SaveFailed:
                    this.ErrorTitle = Catalog.GetString("Error Saving Protocol");
                    break;

                case AnimationErrorState.PrintingFailed:
                    this.ErrorTitle = Catalog.GetString("Error Printing Protocol");
                    break;

                case AnimationErrorState.LabelPrintingFailed:
                    this.ErrorTitle = Catalog.GetString("Error Label Printing Protocol");
                    break;

                case AnimationErrorState.PdfShowingFailed:
                    this.ErrorTitle = Catalog.GetString("Error Showing Protocol");
                    break;

                default:
                    break;
            }

            this.InfoButton.Visible = true;
            this.InfoButton.TooltipText = error_time;
        }

        private void OnInternalButtonReleaseEvent(object sender, Gtk.ButtonReleaseEventArgs e)
        {
            if (e.Event.Button == 1)
            {
                lock (this.Transaction)
                {
                    string pdf_info = String.IsNullOrEmpty(this.ErrorPdf) ?
                                            String.Empty : Catalog.GetString("More information ...");

                    EventHandler handler = null;
                    if (String.IsNullOrEmpty(this.ErrorPdf) == false)
                        handler = this.ShowHelp;


                    ExtToolTip.SetDialog(sender as Widget,
                                     Stock.DialogInfo,
                                     this.ErrorTitle,
                                     this.ErrorMessage,
                                     pdf_info,
                                         handler);


                }
            }
        }

        private void ShowHelp(object sender, EventArgs e)
        {
            try
            {
                Process.OpenUri(new Uri(this.ErrorPdf));
            }
            catch (Exception) { }

        }

        public void ResetError(bool check_after_save = false)
        {
            if (check_after_save == true)
            {
                switch(this.CurrentErrorState)
                {
                    case AnimationErrorState.SaveFailed:
                    case AnimationErrorState.PrintingFailed:
                    case AnimationErrorState.LabelPrintingFailed:
                    case AnimationErrorState.PdfShowingFailed:
                        if (this.IdentficationState != AnimationErrorState.None)
                        {
                            this.CurrentErrorState = this.IdentficationState;
                            this.ErrorMessage = this.IdentficationMessage;
                            this.ErrorTitle = this.IdentficationTitle;
                            this.ErrorPdf = this.IdentificationPdf;
                            this.InfoButton.TooltipText = this.IdentificationTime;
                            return;
                        }
                        break;

                    case AnimationErrorState.IdentificationFluidFailed:
                    case AnimationErrorState.IdentificationSolidFailed:
                        return;

                    default:
                        break;
                }
            }

            this.InfoButton.TooltipText = String.Empty;
            this.InfoButton.Visible = false;

            this.CurrentErrorState = AnimationErrorState.None;
            this.ErrorMessage = String.Empty;
            this.ErrorTitle = String.Empty;
            this.ErrorPdf = String.Empty;

            this.IdentficationState = AnimationErrorState.None;
            this.IdentficationMessage = String.Empty;
            this.IdentficationTitle = String.Empty;
            this.IdentificationPdf = String.Empty;
            this.IdentificationTime = String.Empty;
        }

        public void Update(AnimationState state)
        {
            if (state == AnimationState.None)
                return;

            if (CurrentAnimationState == state)
                return;

            this.StateImage.Pixbuf = this.Pictures[state];
            string text = this.Labels[state];
            if (text.Contains("\n") == false)
                text += "\n";
        
            this.StateLabel.Text = text;
        }

        private Cairo.ImageSurface apo_ident_logo = null;
        public Cairo.ImageSurface ApoIdentLogo
        {
            private get
            {
                return this.apo_ident_logo;
            }
            set
            {
                this.apo_ident_logo = value;
                this.CanvasEventbox.QueueDraw();
            }
        }

        private int outer_line_width = Animation.LINE_WIDTH;
        public int OuterLineWidth
        {
            get
            {
                return this.outer_line_width;
            }

            set
            {
                this.outer_line_width = value;
                ((Gtk.Table)this.CanvasEventbox.Child).BorderWidth = (uint) value;
            }
        }

        private int menu_line_width = Animation.LINE_WIDTH;
        public int MenuLineWidth
        {
            get
            {
                return this.menu_line_width;
            }

            set
            {
                this.menu_line_width = value;
                this.MenuLineAlign.HeightRequest = value;
            }
        }

        private int statusbar_top_line_width = 0;
        public int StatusbarTopLineWidth
        {
            get
            {
                return this.statusbar_top_line_width;
            }

            set
            {
                this.statusbar_top_line_width = value;
                this.CanvasEventbox.QueueDraw();
            }
        }

        private int statusbar_left_line_width = Animation.LINE_WIDTH;
        public int StatusbarLeftLineWidth
        {
            get
            {
                return this.statusbar_left_line_width;
            }

            set
            {
                this.statusbar_left_line_width = value;
                this.CanvasEventbox.QueueDraw();
            }
        }

        private int statusbar_right_line_width = 0;
        public int StatusbarRightLineWidth
        {
            get
            {
                return this.statusbar_right_line_width;
            }

            set
            {
                this.statusbar_right_line_width = value;
                this.CanvasEventbox.QueueDraw();
            }
        }

        private string Url
        {
            get
            {
                string path = Path.Combine("Resources", "Pdfs");
                path = Locale.ResourceFileRelativeToAssembly(Assembly.GetExecutingAssembly(), path, String.Empty, false);

                string url = path.Replace("\\", "/");
                if (url.EndsWith("/", StringComparison.InvariantCulture) == false)
                    url += "/";

                url = "file://" + url;

                return url;
            }
        }

        private AnimationState CurrentAnimationState { get; set; } = AnimationState.None;

        private AnimationErrorState CurrentErrorState { get; set; } = AnimationErrorState.None;
        private string ErrorMessage { get; set; } = String.Empty;
        private string ErrorTitle { get; set; } = String.Empty;
        private string ErrorPdf { get; set; } = String.Empty;

        private AnimationErrorState IdentficationState { get; set; } = AnimationErrorState.None;
        private string IdentficationMessage { get; set; } = String.Empty;
        private string IdentficationTitle { get; set; } = String.Empty;
        private string IdentificationPdf { get; set; } = String.Empty;
        private string IdentificationTime { get; set; } = String.Empty;

        private Dictionary<AnimationState, string> Labels { get; set; } = new Dictionary<AnimationState, string>();
        private Dictionary<AnimationState, Pixbuf> Pictures { get; set; } = new Dictionary<AnimationState, Pixbuf>();

        private Gtk.Label StateLabel { get; set; }
        private Gtk.Image InfoButton { get; set; }
        private Gtk.Image StateImage { get; set; }
        private Gtk.EventBox GtkEventBox { get; set; }
        private Gtk.EventBox CanvasEventbox { get; set; }
        private Gtk.Alignment StatusAlign { get; set; }
        private Gtk.Alignment MenuLineAlign { get; set; }

        private Gdk.Color GdkBackGroundColor { get; set; } = new Gdk.Color(52, 53, 63);
        private Cairo.Color CairoBackGroundColor { get; set; } = new Cairo.Color(52 / 255.0, 53 / 255.0, 63 / 255.0);


        private object Transaction { get; set; }
    }
}
