// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

using System;

namespace Unity.CodeEditor.Document
{
    /// <summary>
    /// Describes a change of the document text.
    /// This class is thread-safe.
    /// </summary>
    internal class DocumentChangeEventArgs : TextChangeEventArgs
    {

        /// <summary>
        /// Gets the OffsetChangeMap associated with this document change.
        /// </summary>
        /// <remarks>The OffsetChangeMap instance is guaranteed to be frozen and thus thread-safe.</remarks>
        internal OffsetChangeMap OffsetChangeMap
        {
            get
            {
                OffsetChangeMap map = OffsetChangeMapOrNull;
                if (map == null)
                {
                    // create OffsetChangeMap on demand
                    map = OffsetChangeMap.FromSingleElement(CreateSingleChangeMapEntry());
                    OffsetChangeMapOrNull = map;
                }
                return map;
            }
        }

        internal OffsetChangeMapEntry CreateSingleChangeMapEntry()
        {
            return new OffsetChangeMapEntry(Offset, RemovalLength, InsertionLength);
        }

        /// <summary>
        /// Gets the OffsetChangeMap, or null if the default offset map (=single replacement) is being used.
        /// </summary>
        internal OffsetChangeMap OffsetChangeMapOrNull { get; private set; }

        /// <summary>
        /// Gets the new offset where the specified offset moves after this document change.
        /// </summary>
        internal override int GetNewOffset(int offset, AnchorMovementType movementType = AnchorMovementType.Default)
        {
            if (OffsetChangeMapOrNull != null)
                return OffsetChangeMapOrNull.GetNewOffset(offset, movementType);
            return CreateSingleChangeMapEntry().GetNewOffset(offset, movementType);
        }

        /// <summary>
        /// Creates a new DocumentChangeEventArgs object.
        /// </summary>
        internal DocumentChangeEventArgs(int offset, string removedText, string insertedText)
            : this(offset, removedText, insertedText, null)
        {
        }

        /// <summary>
        /// Creates a new DocumentChangeEventArgs object.
        /// </summary>
        internal DocumentChangeEventArgs(int offset, string removedText, string insertedText, OffsetChangeMap offsetChangeMap)
            : base(offset, removedText, insertedText)
        {
            SetOffsetChangeMap(offsetChangeMap);
        }

        /// <summary>
        /// Creates a new DocumentChangeEventArgs object.
        /// </summary>
        internal DocumentChangeEventArgs(int offset, ITextSource removedText, ITextSource insertedText, OffsetChangeMap offsetChangeMap)
            : base(offset, removedText, insertedText)
        {
            SetOffsetChangeMap(offsetChangeMap);
        }

        private void SetOffsetChangeMap(OffsetChangeMap offsetChangeMap)
        {
            if (offsetChangeMap != null)
            {
                if (!offsetChangeMap.IsFrozen)
                    throw new ArgumentException("The OffsetChangeMap must be frozen before it can be used in DocumentChangeEventArgs");
                if (!offsetChangeMap.IsValidForDocumentChange(Offset, RemovalLength, InsertionLength))
                    throw new ArgumentException("OffsetChangeMap is not valid for this document change", nameof(offsetChangeMap));
                OffsetChangeMapOrNull = offsetChangeMap;
            }
        }

        /// <inheritdoc/>
        internal override TextChangeEventArgs Invert()
        {
            OffsetChangeMap map = OffsetChangeMapOrNull;
            if (map != null)
            {
                map = map.Invert();
                map.Freeze();
            }
            return new DocumentChangeEventArgs(Offset, InsertedText, RemovedText, map);
        }
    }
}