// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2022 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 Gtk;
using Gdk;
using Cairo;
using Hiperscan.GtkExtensions.Compiler;
using System.Linq;
using Hiperscan.Unix;
using System.Collections.Generic;
using Hiperscan.GtkExtensions.Widgets;

namespace Hiperscan.GtkExtensions.Widgets
{
    public class FlexLabel : DrawingArea
    {
        private class LineUnit
        {
            public LineUnit(string value = "", bool over_length = false)
            {
                this.Value = value;
                this.OverLength = over_length;
            }

            public string Value { get; set; } = String.Empty;
            public bool OverLength { get; set; } = false;
            public bool WordWrap { get; set; } = false;
        }

        public FlexLabel(EventBox parent)
        {
            parent.Add(this);

            this.MeasureText = MonoCairoGraphicCompiler.MeasureGraphicsText;

            this.InitMoreText();
            this.InitSensitiveShape();

            this.Parent.Events |= Gdk.EventMask.PointerMotionMask;
            this.Parent.MotionNotifyEvent += this.OnMotionNotify;
            this.Parent.ButtonReleaseEvent += this.OnButtonReleaseEvent;

            this.FontSize = 11;
            this.CalcHeight();
            this.ShowAll();
        }

        private void OnMotionNotify(object sender, MotionNotifyEventArgs args)
        {
            if (this.ShowMoreText == false)
            {
                this.HitMoreText = false;
                return;
            }

            if (args.Event.X < this.MoreText.Position.X)
            {
                this.HitMoreText = false;
                return;
            }

            if (args.Event.X > this.MoreText.Position.X + this.MoreText.MaxWidth)
            {
                this.HitMoreText = false;
                return;
            }

            if (args.Event.Y < this.MoreText.Position.Y)
            {
                this.HitMoreText = false;
                return;
            }

            if (args.Event.Y > this.MoreText.Position.Y + this.MeasureText(this.TmpText).Height)
            {
                this.HitMoreText = false;
                return;
            }

            this.HitMoreText = true;
        }

        protected virtual void OnButtonReleaseEvent(object sender, EventArgs e)
        {
            if (this.HitMoreText == true)
            {
                string title = this.TooLtipTitle;
                if (String.IsNullOrEmpty(title))
                    title = Catalog.GetString("Info");

                string tool_text = this.TooltipText;
                if (String.IsNullOrEmpty(tool_text))
                    tool_text = this.Text;

                ExtToolTip.SetDialog(sender as Widget,
                                     Stock.DialogInfo,
                                     title,
                                     this.text);
            }
        }

        private void InitMoreText()
        {
            this.MoreText.Text = Catalog.GetString("More");
            this.MoreText.Brush = new GraphicsSolidColorBrush(GraphicsColors.Blue);
            this.MoreText.Font.FontWeight = GraphicsFontWeights.Bold;
            this.MoreText.UnderLine = true;

            this.FontName = new Label().Style.FontDesc.Copy().Family;
            this.FontWeight = GraphicsFontWeights.Normal;
            this.rows = 1;
        }

        private void InitSensitiveShape()
        {
            GraphicsSolidColorBrush brush = new GraphicsSolidColorBrush();
            brush.Color = new GraphicsColor(1, 1, 1, 0.5);

            this.SensitiveShape.Brush = brush;
        }

        protected override bool OnExposeEvent(Gdk.EventExpose evnt)
        {
            GenericGraphicCompiler translator = new MonoCairoGraphicCompiler(Gdk.CairoHelper.Create(evnt.Window));

            this.BuildLines();
            this.CompleteVisualTree();
            this.SetTextPositions();

            GraphicsCompiler graphic_compiler = new GraphicsCompiler(this.VisualTreeRoot, translator);
            graphic_compiler.TraverseVisualTree();
            translator.Dispose();
            return true;
        }

        private void CalcHeight()
        {
            string tmp_text = String.Empty;
            for (int i = 0; i < this.rows - 1; i++)
                tmp_text += "\n";

            this.TmpText.Text = tmp_text;
            this.Parent.HeightRequest = (int)this.MeasureText(this.TmpText).Height + 1;
            this.HeightRequest = this.Parent.HeightRequest;
        }

        private void BuildLines()
        {
            this.Lines = this.GetLines();
            this.ShowMoreText = this.Lines.Any(x => x.OverLength == true);
            if (this.ShowMoreText == false)
                this.ShowMoreText = this.Lines.Count > this.Rows;
        }

        private List<LineUnit> GetLines()
        {
            List<LineUnit> output_lines = new List<LineUnit>();
            string[] lines = text.Split(new char[] { '\n' });

            foreach (string line in lines)
            {
                if (String.IsNullOrEmpty(line))
                {
                    output_lines.Add(new LineUnit());
                    continue;
                }

                List<LineUnit> sub_lines = this.GetSubLines(line);
                for (int i = 0; i < sub_lines.Count; i++)
                {
                    if (i == 0)
                        sub_lines[i].WordWrap = false;
                    else
                        sub_lines[i].WordWrap = true;
                }

                output_lines.AddRange(sub_lines);
            }

            return output_lines;
        }

        private List<LineUnit> GetSubLines(string line)
        {
            List<LineUnit> output_lines = new List<LineUnit>();

            string[] words = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            if (words.Length == 0)
            {
                output_lines.Add(new LineUnit());
                return output_lines;
            }

            this.TmpText.Text = String.Empty;
            string tmp_line = words[0];
            bool measure = true;

            for (int i = 1; i < words.Length; i++)
            {
                this.TmpText.Text = tmp_line;
                if (measure && this.MeasureText(this.TmpText).Width > this.Allocation.Width)
                {
                    output_lines.Add(new LineUnit(tmp_line, true));
                    tmp_line = words[i];
                    continue;
                }

                this.TmpText.Text = tmp_line + " " + words[i];
                if (this.MeasureText(this.TmpText).Width > this.Allocation.Width)
                {
                    output_lines.Add(new LineUnit(tmp_line));
                    tmp_line = words[i];
                    measure = true;
                }
                else
                {
                    tmp_line += " " + words[i];
                    measure = false;
                }
            }

            this.TmpText.Text = tmp_line;
            bool over_length = measure && (this.MeasureText(this.TmpText).Width > this.Allocation.Width);
            output_lines.Add(new LineUnit(tmp_line, over_length));

            return output_lines;
        }

        private void CompleteVisualTree()
        {
            this.VisualTreeRoot.Children.Clear();
            int min = this.Lines.Count;
            if (min > this.Rows)
                min = this.rows;

            for (int i = 0; i < min; i++)
            {
                if (i == this.rows - 1)
                {
                    GraphicsText item = new GraphicsText();
                    item.Font = this.TmpText.Font;
                    string tmp = this.Lines[i].Value;

                    for (int j = i + 1; j < this.Lines.Count; j++)
                    {
                        if (this.Lines[j].WordWrap == false)
                            break;

                        tmp += " " + this.Lines[j].Value;
                    }

                    item.Text = tmp;
                    this.VisualTreeRoot.Children.Add(item);
                }
                else
                {
                    GraphicsText item = new GraphicsText();
                    item.Font = this.TmpText.Font;
                    item.Text = this.Lines[i].Value;
                    this.VisualTreeRoot.Children.Add(item);
                }
            }

            if (this.ShowMoreText)
            {
                this.MoreText.Font = TmpText.Font;
                this.VisualTreeRoot.Children.Add(this.MoreText);
            }

            if (this.Parent.State == StateType.Insensitive)
            {
                this.SensitiveShape.Coordinates.X = 0;
                this.SensitiveShape.Coordinates.Y = 0;
                this.SensitiveShape.Coordinates.Width = this.Allocation.Width;
                this.SensitiveShape.Coordinates.Height = this.Allocation.Height;
                this.VisualTreeRoot.Children.Add(this.SensitiveShape);
            }
        }

        private void SetTextPositions()
        {
            int normal_lines = this.VisualTreeRoot.Children.Count;
            if (this.ShowMoreText)
                normal_lines -= 2;
            if (this.Parent.State == StateType.Insensitive)
                normal_lines -= 1;

            this.TmpText.Text = "";
            double height = this.MeasureText(this.TmpText).Height;
            double y_pos = 0;
            int diff = this.Rows - this.Lines.Count;
            if (diff > 0)
                y_pos = diff * height / 2;

            for (int i = 0; i < normal_lines; i++)
            {
                GraphicsText tmp = (GraphicsText)this.VisualTreeRoot.Children[i];
                tmp.MaxWidth = this.Allocation.Width;
                tmp.Position.X = 0;
                tmp.Position.Y = y_pos;
                y_pos += height;
            }

            if (this.ShowMoreText)
            {
                this.TmpText.Text = "   ";
                double space = this.MeasureText(this.TmpText).Width;
                double end_space = space;
                double more_width = this.MeasureText(this.MoreText).Width;
                double last_row_width = this.Allocation.Width - space - more_width - end_space;
                if (last_row_width < 0)
                    last_row_width = 0;
                if (this.Allocation.Width < more_width + end_space)
                    more_width = this.Allocation.Width;

                GraphicsText tmp = (GraphicsText)this.VisualTreeRoot.Children[normal_lines];
                tmp.MaxWidth = last_row_width;
                tmp.Position.X = 0;
                tmp.Position.Y = y_pos;

                this.MoreText.MaxWidth = more_width;
                this.MoreText.Position.X = this.Allocation.Width - more_width -end_space;
                this.MoreText.Position.Y = y_pos;
            }
        }

        public new bool Sensitive
        {
            set
            {
                this.Parent.Sensitive = value;
                this.CalcHeight();
                this.QueueDraw();
            }
        }

        public Gdk.Color Background
        {
            set
            {
                this.ModifyBg(StateType.Normal, value);
                this.Parent.ModifyBg(StateType.Normal, value);
            }
        }

        private bool hit_more_text = false;
        private bool HitMoreText 
        { 
            get
            {
                return this.hit_more_text;
            }

            set
            {
                if (value == this.hit_more_text)
                    return;

                this.hit_more_text = value;

                try
                {
                    if (this.hit_more_text)
                        this.Parent.GdkWindow.Cursor = new Cursor(CursorType.Hand2);
                    else
                        this.Parent.GdkWindow.Cursor = new Cursor(CursorType.Arrow);
                }
                catch(Exception)
                {
                    this.Parent.GdkWindow.Cursor = new Cursor(CursorType.Arrow);
                }
            }
        }

        private int rows = 1;
        public int Rows
        {
            private get
            {
                return this.rows;
            }

            set
            {
                if (value < 1)
                    value = 1;

                this.rows = value;
                this.CalcHeight();
                this.QueueDraw();
            }
        }

        private string text = String.Empty;
        public string Text
        {
            get
            {
                return this.text;
            }

            set
            {
                this.text = value;
                this.CalcHeight();
                this.QueueDraw();
            }
        }

        public double FontSize
        {
            get
            {
                if (Environment.OSVersion.Platform == PlatformID.Unix)
                    return this.TmpText.Font.FontSize * 11.0 / 18.0;
                               
                return this.TmpText.Font.FontSize;
            }
            set
            {
                if (Environment.OSVersion.Platform == PlatformID.Unix)
                    this.TmpText.Font.FontSize = value * 18.0 / 11.0;
                else
                    this.TmpText.Font.FontSize = value;

                this.CalcHeight();
                this.QueueDraw();
            }
        }

        public string FontName
        {
            set
            {
                this.TmpText.Font.FontName = value;
                this.CalcHeight();
                this.QueueDraw();
            }
        }

        public GraphicsFontWeights FontWeight
        {
            set
            {
                this.TmpText.Font.FontWeight = value;
                this.CalcHeight();
                this.QueueDraw();
            }
        }

        public string TooLtipTitle { get; set; }
        public new string TooltipText { get; set; }

        private MeasureTextDelegate MeasureText { get; set; }
        private GraphicsGroup VisualTreeRoot { get; set; } = new GraphicsGroup();
        private List<GraphicsText> GraphicTextLines { get; set; } = new List<GraphicsText>();
        private GraphicsText MoreText { get; set; } = new GraphicsText();
        private GraphicsText TmpText { get; set; } = new GraphicsText();
        private GraphicsShapeRectangle SensitiveShape { get; set; } = new GraphicsShapeRectangle();
        private bool ShowMoreText { get; set; } = false;
        private List<LineUnit> Lines { get; set; } = new List<LineUnit>();
    }
}
