// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

using System;
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using mshtml;
using OpenLiveWriter.Api;
using OpenLiveWriter.CoreServices;
using OpenLiveWriter.HtmlEditor;
using OpenLiveWriter.HtmlParser.Parser;
using OpenLiveWriter.Localization;
using OpenLiveWriter.Mshtml;
using OpenLiveWriter.PostEditor.ContentSources;
using OpenLiveWriter.PostEditor.PostHtmlEditing;

namespace OpenLiveWriter.PostEditor
{
    class InlineEditField
    {
        private readonly IHTMLElement _element;
        private readonly ISmartContent _smartContent;
        private readonly IHtmlEditorComponentContext _editorContext;
        private readonly IHTMLElement _smartContentElement;
        private readonly IUndoRedoExecutingChecker _undoRedoCheck;

        public InlineEditField(IHTMLElement element, ISmartContent smartContent, IHtmlEditorComponentContext editorContext, IHTMLElement smartContentElement, IUndoRedoExecutingChecker undoRedoCheck)
        {
            Debug.Assert(element != null, "Making an edit field with a null element.");
            _element = element;
            _smartContent = smartContent;
            _smartContentElement = smartContentElement;
            _editorContext = editorContext;
            _undoRedoCheck = undoRedoCheck;
        }

        public string TextValue
        {
            // The innerText may contain a shortened form of the title
            // This field should only return the full title.
            get
            {
                return _element.innerText;
            }
        }

        public bool ContentEditable
        {
            get
            {
                return ((IHTMLElement3)_element).isContentEditable;
            }
            set
            {
                ((IHTMLElement3)_element).contentEditable = value ? bool.TrueString : bool.FalseString;
            }
        }

        public string PropertyPath
        {
            get
            {
                string propertyPath = _element.getAttribute("wlPropertyPath", 2) as string;
                if (!String.IsNullOrEmpty(propertyPath))
                    _smartContent.Properties.SetString("wlPropertyPath", propertyPath);

                return _smartContent.Properties.GetString("wlPropertyPath", String.Empty);
            }
        }

        public string DefaultText
        {
            get { return _element.getAttribute("defaultText", 2) as string; }
        }

        public bool IsDefaultText
        {
            get { return _element.getAttribute("isDefaultText", 2) as string != null; }
            set
            {
                if (value)
                {
                    _element.setAttribute("isDefaultText", "true", 0);
                }
                else
                {
                    _element.removeAttribute("isDefaultText", 0);
                }
            }
        }

        public string DefaultTextColor
        {
            get { return _element.getAttribute("defaultTextColor", 2) as string; }
        }

        public string TextColor
        {
            get { return _element.getAttribute("textColor", 2) as string; }
        }

        internal void ClearDefaultText()
        {
            Debug.Assert(_undoRedoCheck != null, "Clearing default text on an unmanaged inline edit field");
            if (IsDefaultText && (!_undoRedoCheck.UndoRedoExecuting()))
            {
                using (IUndoUnit undo = _editorContext.CreateInvisibleUndoUnit())
                {
                    _element.innerHTML = "";
                    if (!string.IsNullOrEmpty(TextColor))
                        _element.style.color = TextColor;
                    IsDefaultText = false;
                    undo.Commit();
                }
            }
        }

        /// <summary>
        /// This writes into the currentEditor's SelectedContent properties.
        /// Be sure that only the edit field associated with the *selected* content gets persisted there.
        /// </summary>
        public void PersistFieldValueToContent(bool persistToEditorContent)
        {
            SmartContentEditor currentEditor = ((IBlogPostHtmlEditor)_editorContext).CurrentEditor;

            if (currentEditor == null || currentEditor.SelectedContent == null || IsDefaultText)
                return;

            string propertyPath = PropertyPath;
            IProperties sidebarProperties = currentEditor.SelectedContent.Properties;
            IProperties smartContentProperties = _smartContent.Properties;

            string[] pathElements = (PropertyPath ?? "").Split('\\');
            for (int i = 0; i < pathElements.Length; i++)
            {
                if (string.IsNullOrEmpty(pathElements[i]))
                {
                    Trace.Fail("Failed to sync to malformed property path " + propertyPath);
                    return;
                }

                if (i == pathElements.Length - 1)
                {

                    if (persistToEditorContent)
                    {
                        // Save to smart content in sidebar contextual editor
                        sidebarProperties[pathElements[i]] = TextValue;
                        sidebarProperties.SetString("wlPropertyPath", pathElements[i]);
                    }

                    // Save to smart content in canvas
                    smartContentProperties[pathElements[i]] = TextValue;
                    smartContentProperties.SetString("wlPropertyPath", pathElements[i]);
                    return;
                }
                else
                {
                    sidebarProperties = sidebarProperties.GetSubProperties(pathElements[i]);
                    smartContentProperties = smartContentProperties.GetSubProperties(pathElements[i]);
                }
            }
        }

        public void SetDefaultText()
        {
            Debug.Assert(_undoRedoCheck != null, "Setting default text on an unmanaged inline edit field");
            // WinLive 210281: Don't update the default text unless really necessary. If an undo forces this function
            // to run, then creating a new undo unit will clear the redo stack.
            if (String.IsNullOrEmpty(_element.innerText) && (!_undoRedoCheck.UndoRedoExecuting()))
            {
                using (IUndoUnit undo = _editorContext.CreateInvisibleUndoUnit())
                {
                    _element.innerText = DefaultText;
                    if (!string.IsNullOrEmpty(DefaultTextColor))
                        _element.style.color = DefaultTextColor;
                    IsDefaultText = true;

                    undo.Commit();
                }
            }
        }

        public static readonly string EDIT_FIELD = "wlEditField";
        private static readonly string EditFieldRegExPattern = "\\b" + EDIT_FIELD + "\\b";

        public static bool IsEditField(IHTMLElement el)
        {
            if (el == null)
                return false;

            return Regex.IsMatch(el.className ?? "", EditFieldRegExPattern);
        }

        public static bool IsEditField(object selection)
        {
            IHtmlEditorSelection htmlSelection = selection as IHtmlEditorSelection;
            if (htmlSelection != null)
                return IsEditField(htmlSelection.SelectedMarkupRange.ParentElement());
            else if (selection is IHTMLElement)
            {
                return IsEditField((IHTMLElement)selection);
            }
            return false;
        }

        public static bool IsWithinEditField(IHTMLElement element)
        {
            while (element != null)
            {
                if (IsEditField(element))
                    return true;
                element = element.parentElement;
            }
            return false;
        }

        public static bool IsDefaultTextShowing(IHTMLElement element)
        {
            while (element != null)
            {
                if (IsEditField(element))
                {
                    InlineEditField editField = new InlineEditField(element, null, null, null, null);
                    return editField.IsDefaultText;
                }
                element = element.parentElement;
            }
            return false;
        }

        public static bool EditFieldAcceptsData()
        {

            IDataObject clipboard = Clipboard.GetDataObject();

            string[] formats = clipboard.GetFormats();

            foreach (string format in formats)
            {
                switch (format)
                {
                    case "HTML Format":
                    case "System.String":
                    case "Text":
                    case "UnicodeText":
                        return true;
                }
            }
            return false;
        }

        public static bool EditFieldAcceptsCurrentClipboardData
        {
            get
            {
                return EditFieldAcceptsData();
            }
        }
    }
}