using System;
using System.Collections.Generic;

using Codice.CM.Common;
using PlasticGui;
using PlasticGui.WorkspaceWindow.Topbar.WorkingObjectInfo.BranchesList;
using Unity.PlasticSCM.Editor.UI;

using UnityEditor.IMGUI.Controls;
using UnityEngine;

#if UNITY_6000_2_OR_NEWER
using TreeView = UnityEditor.IMGUI.Controls.TreeView<int>;
using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem<int>;
using TreeViewState = UnityEditor.IMGUI.Controls.TreeViewState<int>;
#endif

namespace Unity.PlasticSCM.Editor.Toolbar.PopupWindow.BranchesList
{
    internal class BranchesTreeView : TreeView
    {
        internal interface IClickListener
        {
            void OnItemClicked(BranchTreeViewItem item);
        }

        internal BranchesTreeView(IClickListener listener) : base(new TreeViewState())
        {
            mClickListener = listener;
        }

        internal void SetWorkingBranch(Func<BranchInfo> fetchWorkingBranch)
        {
            mFetchWorkingBranch = fetchWorkingBranch;
        }

        internal void SetBranches(ClassifiedBranchesList branches)
        {
            mBranches = branches;

            if (!string.IsNullOrEmpty(mSearchString))
            {
                SetSearchString(mSearchString);
                return;
            }

            Reload();
        }

        internal void SetSearchString(string searchString)
        {
            mSearchString = searchString;
        }

        protected override bool CanMultiSelect(TreeViewItem item)
        {
            return false;
        }

        protected override float GetCustomRowHeight(int row, TreeViewItem item)
        {
            if (IsCategory(item))
                return 26;
            else
                return 38f;
        }

        public override void OnGUI(Rect rect)
        {
            if (Event.current.type == EventType.MouseMove)
            {
                Vector2 localMousePos = Event.current.mousePosition - rect.position + state.scrollPos;

                int newHoveredId = GetHoveredItemId(localMousePos);
                if (newHoveredId != mLastHoveredItemId)
                {
                    mLastHoveredItemId = newHoveredId;
                    Repaint();
                }
            }

            base.OnGUI(rect);
        }

        protected override void RowGUI(RowGUIArgs args)
        {
            Rect rowRect = args.rowRect;
            Event evt = Event.current;

            switch (evt.type)
            {
                case EventType.MouseDown:
                    if (rowRect.Contains(evt.mousePosition) &&
                        evt.button == 0)
                    {
                        mClickedItemId = args.item.id;
                        evt.Use();
                    }

                    break;

                case EventType.MouseUp:
                    if (rowRect.Contains(evt.mousePosition) &&
                        evt.button == 0 &&
                        mClickedItemId == args.item.id &&
                        !IsCategory(args.item))
                    {
                        mClickedItemId = -1;
                        evt.Use();
                        mClickListener.OnItemClicked((BranchTreeViewItem)args.item);
                    }

                    break;

                case EventType.Repaint:
                    DrawRowGUI(args);
                    break;
            }
        }

        bool IsCategory(TreeViewItem item)
        {
            return item is CategoryTreeViewItem;
        }

        void DrawRowGUI(RowGUIArgs args)
        {
            if (IsCategory(args.item))
            {
                DrawCategory(args);
                return;
            }

            DrawBranch(args);
        }

        void DrawBranch(RowGUIArgs args)
        {
            bool isHover = args.rowRect.Contains(Event.current.mousePosition);

            GUIStyle titleStyle = isHover ?
                UnityStyles.EditorToolbar.Popup.BranchesList.TitleHover :
                UnityStyles.EditorToolbar.Popup.BranchesList.Title;
            GUIStyle descriptionStyle = isHover ?
                UnityStyles.EditorToolbar.Popup.BranchesList.DescriptionHover :
                UnityStyles.EditorToolbar.Popup.BranchesList.Description;
            GUIStyle timeAgoStyle = isHover ?
                UnityStyles.EditorToolbar.Popup.BranchesList.TimeAgoHover :
                UnityStyles.EditorToolbar.Popup.BranchesList.TimeAgo;

            if (isHover)
            {
                GUI.Label(args.rowRect, GUIContent.none, UnityStyles.EditorToolbar.Popup.Hover);
            }

            BranchTreeViewItem branchItem = (BranchTreeViewItem)args.item;

            bool isWorkingBranch = branchItem.BranchInfo.Equals(mFetchWorkingBranch());
            Texture icon = isWorkingBranch ? Images.GetCurrentBranchIcon() : Images.GetBranchIcon();

            GUIContent titleContent = new GUIContent(args.item.displayName, icon);
            GUIContent descriptionContent = new GUIContent(branchItem.FormattedComment);

            string tooltip = string.Format("{0}, {1}",
                branchItem.BranchInfo.LocalTimeStamp.ToLongDateString(),
                branchItem.BranchInfo.LocalTimeStamp.ToLongTimeString());
            GUIContent timeAgoContent = new GUIContent(Codice.Client.Common.Time.GetTimeAgoString(
                branchItem.BranchInfo.LocalTimeStamp), tooltip);

            Vector2 titleContentSize = titleStyle.CalcSize(titleContent);
            Vector2 descriptionContentSize = descriptionStyle.CalcSize(descriptionContent);
            Vector2 timeAgoSize = timeAgoStyle.CalcSize(timeAgoContent);

            Rect timeAgoRect = new Rect(
                args.rowRect.x + args.rowRect.width - timeAgoSize.x,
                args.rowRect.y + (args.rowRect.height - timeAgoSize.y) / 2,
                timeAgoSize.x,
                timeAgoSize.y);

            float titleAndDescriptionHeight = titleContentSize.y + descriptionContentSize.y;

            Rect titleRect = new Rect(
                args.rowRect.x,
                args.rowRect.y + (args.rowRect.height - titleAndDescriptionHeight) / 2,
                args.rowRect.width - timeAgoSize.x,
                titleContentSize.y);

            Rect desciptionRect = new Rect(
                args.rowRect.x,
                args.rowRect.y + (args.rowRect.height - titleAndDescriptionHeight) / 2 + titleContentSize.y,
                args.rowRect.width - timeAgoSize.x,
                descriptionContentSize.y);

            // add a tooltip if title or description is not completely displayed
            if (titleContentSize.x > args.rowRect.width - timeAgoSize.x)
                titleContent.tooltip = titleContent.text;

            if (descriptionContentSize.x > args.rowRect.width - timeAgoSize.x)
                descriptionContent.tooltip = descriptionContent.text;

            DrawContentWithSearchString(
                titleRect,
                titleContent,
                mSearchString,
                titleStyle);

            DrawContentWithSearchString(
                desciptionRect,
                descriptionContent,
                mSearchString,
                descriptionStyle);

            GUI.Label(timeAgoRect, timeAgoContent, timeAgoStyle);
        }

        static void DrawCategory(RowGUIArgs args)
        {
            GUI.Label(
                args.rowRect,
                args.item.displayName,
                UnityStyles.EditorToolbar.Popup.BranchesList.Category);
        }

        static void DrawContentWithSearchString(
            Rect rect,
            GUIContent content,
            string searchString,
            GUIStyle style)
        {
            int titleSearchStringIndex = string.IsNullOrEmpty(searchString)
                ? -1
                : content.text.IndexOf(searchString, StringComparison.Ordinal);

            float iconWidth = content.image != null ? 16 : 0f;

            Rect iconRect = new Rect(
                rect.x + style.padding.left,
                rect.y,
                iconWidth,
                rect.height);

            Rect textRect = new Rect(
                rect.x + iconWidth + style.margin.left,
                rect.y,
                rect.width - iconWidth,
                rect.height);

            if (content.image != null)
            {
                GUI.DrawTexture(iconRect, content.image);
            }

            if (titleSearchStringIndex == -1)
            {
                GUI.Label(textRect, new GUIContent(content.text, content.tooltip), style);
            }
            else
            {
                style.DrawWithTextSelection(
                    textRect,
                    new GUIContent(content.text, content.tooltip),
                    -1,
                    titleSearchStringIndex,
                    titleSearchStringIndex + searchString.Length
                );
            }
        }

        int GetHoveredItemId(Vector2 mousePos)
        {
            int firstVisibleRow,
                lastVisibleRow;

            this.GetFirstAndLastVisibleRows(out firstVisibleRow, out lastVisibleRow);

            for (int i = firstVisibleRow; i <= lastVisibleRow; i++)
            {
                Rect rowRect = GetRowRect(i);

                if (rowRect.Contains(mousePos))
                {
                    return i;
                }
            }

            return -1; // No hover
        }

        protected override TreeViewItem BuildRoot()
        {
            var root = new TreeViewItem { id = -1, depth = -1 };
            root.children = new List<TreeViewItem>();

            if (mBranches == null)
                return root;

            int currentId = -1;

            if (mBranches.IsMainBranchVisible)
            {
                root.AddChild(new CategoryTreeViewItem(
                    currentId++,
                    PlasticLocalization.Name.MainBranchSection.GetString()));
                root.AddChild(new BranchTreeViewItem(currentId++, mBranches.MainBranch));
            }

            if (mBranches.AreRecentBranchesVisible)
            {
                root.AddChild(new CategoryTreeViewItem(
                    currentId++,
                    PlasticLocalization.Name.RecentBranchesSection.GetString()));
                foreach (BranchInfo branch in mBranches.RecentBranches)
                {
                    root.AddChild(new BranchTreeViewItem(currentId++, branch));
                }
            }

            if (mBranches.AreOtherBranchesVisible)
            {
                root.AddChild(new CategoryTreeViewItem(
                    currentId++,
                    PlasticLocalization.Name.OtherBranchesSection.GetString()));
                foreach (BranchInfo branch in mBranches.OtherBranches)
                {
                    root.AddChild(new BranchTreeViewItem(currentId++, branch));
                }
            }

            return root;
        }

        readonly IClickListener mClickListener;
        int mClickedItemId;
        string mSearchString;
        ClassifiedBranchesList mBranches;
        Func<BranchInfo> mFetchWorkingBranch;
        int mLastHoveredItemId = -1;
    }

    class BranchTreeViewItem : TreeViewItem
    {
        string mFormattedComment;
        internal BranchInfo BranchInfo { get; private set; }

        public string FormattedComment
        {
            get
            {
                if (string.IsNullOrEmpty(mFormattedComment))
                    mFormattedComment = GetFormattedComment(BranchInfo.Comment);

                return mFormattedComment;
            }
        }

        internal BranchTreeViewItem(int id, BranchInfo branchInfo)
        {
            this.id = id;
            this.displayName = branchInfo.Name;

            BranchInfo = branchInfo;
        }

        static string GetFormattedComment(string comment)
        {
            if (string.IsNullOrEmpty(comment))
                return PlasticLocalization.Name.NoCommentSet.GetString();
            return CommentFormatter.GetFormattedComment(comment);
        }
    }

    class CategoryTreeViewItem : TreeViewItem
    {
        internal CategoryTreeViewItem(int id, string displayName)
        {
            this.id = id;
            this.displayName = displayName;
        }
    }
}