// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.AspNetCore.Razor.Language.Syntax;
using Microsoft.Extensions.Internal;

namespace Microsoft.AspNetCore.Razor.Language
{
	public sealed class SourceChange : IEquatable<SourceChange>
	{
		public SourceChange( int absoluteIndex, int length, string newText )
		{
			if ( absoluteIndex < 0 )
			{
				throw new ArgumentOutOfRangeException( nameof( absoluteIndex ) );
			}

			if ( length < 0 )
			{
				throw new ArgumentOutOfRangeException( nameof( length ) );
			}

			if ( newText == null )
			{
				throw new ArgumentNullException( nameof( newText ) );
			}

			Span = new SourceSpan( absoluteIndex, length );
			NewText = newText;
		}

		public SourceChange( SourceSpan span, string newText )
		{
			if ( newText == null )
			{
				throw new ArgumentNullException( nameof( newText ) );
			}

			Span = span;
			NewText = newText;
		}

		public bool IsDelete => Span.Length > 0 && NewText.Length == 0;

		public bool IsInsert => Span.Length == 0 && NewText.Length > 0;

		public bool IsReplace => Span.Length > 0 && NewText.Length > 0;

		public SourceSpan Span { get; }

		public string NewText { get; }

		internal string GetEditedContent( SyntaxNode node )
		{
			if ( node == null )
			{
				throw new ArgumentNullException( nameof( node ) );
			}

			var offset = GetOffset( node );
			return GetEditedContent( node.GetContent(), offset );
		}

		internal string GetEditedContent( string text, int offset )
		{
			if ( text == null )
			{
				throw new ArgumentNullException( nameof( text ) );
			}

			return text.Remove( offset, Span.Length ).Insert( offset, NewText );
		}

		internal int GetOffset( SyntaxNode node )
		{
			if ( node == null )
			{
				throw new ArgumentNullException( nameof( node ) );
			}

			var start = Span.AbsoluteIndex;
			var end = Span.AbsoluteIndex + Span.Length;

			if ( start < node.Position ||
				start > node.EndPosition ||
				end < node.Position ||
				end > node.EndPosition )
			{
				throw new InvalidOperationException();
			}

			return start - node.Position;
		}

		internal string GetOriginalText( SyntaxNode node )
		{
			if ( node == null )
			{
				throw new ArgumentNullException( nameof( node ) );
			}

			if ( node.FullWidth == 0 )
			{
				return string.Empty;
			}

			var offset = GetOffset( node );
			return node.GetContent().Substring( offset, Span.Length );
		}

		public bool Equals( SourceChange other )
		{
			return
				other != null &&
				Span.Equals( other.Span ) &&
				string.Equals( NewText, other.NewText, StringComparison.Ordinal );
		}

		public override bool Equals( object obj )
		{
			return Equals( obj as SourceChange );
		}

		public override int GetHashCode()
		{
			var hash = new HashCodeCombiner();
			hash.Add( Span );
			hash.Add( NewText, StringComparer.Ordinal );
			return hash;
		}

		public override string ToString()
		{
			return Span.ToString() + " : " + NewText;
		}
	}
}
