using Lucene.Net.Support;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;

/*
 * dk.brics.automaton
 *
 * Copyright (c) 2001-2009 Anders Moeller
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * this SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * this SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

namespace Lucene.Net.Util.Automaton
{
    /// <summary>
    /// <see cref="Automaton"/> state.
    /// <para/>
    /// @lucene.experimental
    /// </summary>
    public class State : IComparable<State>, IEquatable<State> // LUCENENET specific: Implemented IEquatable, since this class is used in hashtables
    {
        internal bool accept;
        [WritableArray]
        [SuppressMessage("Microsoft.Performance", "CA1819", Justification = "Lucene's design requires some writable array properties")]
        public Transition[] TransitionsArray => transitionsArray;

        // LUCENENET NOTE: Setter removed because it is apparently not in use outside of this class
        private Transition[] transitionsArray = Arrays.Empty<Transition>();

        internal int numTransitions = 0;// LUCENENET NOTE: Made internal because we already have a public property for access

        internal int number;

        internal int id;
        internal static int next_id;

        /// <summary>
        /// Constructs a new state. Initially, the new state is a reject state.
        /// </summary>
        public State()
        {
            //ResetTransitions(); // LUCENENET: Let class initializer set these
            id = next_id++;
        }

        /// <summary>
        /// Resets transition set.
        /// </summary>
        internal void ResetTransitions()
        {
            transitionsArray = Arrays.Empty<Transition>();
            numTransitions = 0;
        }

        internal class TransitionsEnumerable : IEnumerable<Transition>
        {
            private readonly State outerInstance;

            public TransitionsEnumerable(State outerInstance)
            {
                this.outerInstance = outerInstance;
            }

            public virtual IEnumerator<Transition> GetEnumerator()
            {
                return new TransitionsEnumerator(this);
            }

            System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
            {
                return GetEnumerator();
            }

            private struct TransitionsEnumerator : IEnumerator<Transition>
            {
                private readonly TransitionsEnumerable outerInstance;
                private Transition current;
                private int i, upTo;

                public TransitionsEnumerator(TransitionsEnumerable outerInstance)
                {
                    this.outerInstance = outerInstance;
                    upTo = this.outerInstance.outerInstance.numTransitions;
                    i = 0;
                    current = default;
                }

                public bool MoveNext()
                {
                    if (i < upTo)
                    {
                        current = outerInstance.outerInstance.transitionsArray[i++];
                        return true;
                    }
                    return false;
                }

                public Transition Current => current;

                object System.Collections.IEnumerator.Current => Current;

                public void Reset()
                {
                    throw new NotSupportedException();
                }

                public void Dispose()
                {
                }
            }
        }

        /// <summary>
        /// Returns the set of outgoing transitions. Subsequent changes are reflected
        /// in the automaton.
        /// </summary>
        /// <returns> Transition set. </returns>
        public virtual IEnumerable<Transition> GetTransitions()
        {
            return new TransitionsEnumerable(this);
        }

        public virtual int NumTransitions => numTransitions;

        public virtual void SetTransitions(Transition[] transitions)
        {
            this.numTransitions = transitions.Length;
            this.transitionsArray = transitions;
        }

        /// <summary>
        /// Adds an outgoing transition.
        /// </summary>
        /// <param name="t"> Transition. </param>
        public virtual void AddTransition(Transition t)
        {
            if (numTransitions == transitionsArray.Length)
            {
                // LUCENENET: Resize rather than copy
                Array.Resize(ref transitionsArray, ArrayUtil.Oversize(1 + numTransitions, RamUsageEstimator.NUM_BYTES_OBJECT_REF));
            }
            transitionsArray[numTransitions++] = t;
        }

        /// <summary>
        /// Sets acceptance for this state. If <c>true</c>, this state is an accept state.
        /// </summary>
        public virtual bool Accept
        {
            get => accept;
            set => this.accept = value;
        }

        /// <summary>
        /// Performs lookup in transitions, assuming determinism.
        /// </summary>
        /// <param name="c"> Codepoint to look up. </param>
        /// <returns> Destination state, <c>null</c> if no matching outgoing transition. </returns>
        /// <seealso cref="Step(int, ICollection{State})"/>
        public virtual State Step(int c)
        {
            Debug.Assert(c >= 0);
            for (int i = 0; i < numTransitions; i++)
            {
                Transition t = transitionsArray[i];
                if (t.min <= c && c <= t.max)
                {
                    return t.to;
                }
            }
            return null;
        }

        /// <summary>
        /// Performs lookup in transitions, allowing nondeterminism.
        /// </summary>
        /// <param name="c"> Codepoint to look up. </param>
        /// <param name="dest"> Collection where destination states are stored. </param>
        /// <seealso cref="Step(int)"/>
        public virtual void Step(int c, ICollection<State> dest)
        {
            for (int i = 0; i < numTransitions; i++)
            {
                Transition t = transitionsArray[i];
                if (t.min <= c && c <= t.max)
                {
                    dest.Add(t.to);
                }
            }
        }

        /// <summary>
        /// Virtually adds an epsilon transition to the target
        /// <paramref name="to"/> state.  this is implemented by copying all
        /// transitions from <paramref name="to"/> to this state, and if 
        /// <paramref name="to"/> is an accept state then set accept for this state.
        /// </summary>
        internal virtual void AddEpsilon(State to)
        {
            if (to.accept)
            {
                accept = true;
            }
            foreach (Transition t in to.GetTransitions())
            {
                AddTransition(t);
            }
        }

        /// <summary>
        /// Downsizes transitionArray to numTransitions. </summary>
        public virtual void TrimTransitionsArray()
        {
            if (numTransitions < transitionsArray.Length)
            {
                Array.Resize(ref transitionsArray, numTransitions); // LUCENENET: Resize rather than copy
            }
        }

        /// <summary>
        /// Reduces this state. A state is "reduced" by combining overlapping
        /// and adjacent edge intervals with same destination.
        /// </summary>
        public virtual void Reduce()
        {
            if (numTransitions <= 1)
            {
                return;
            }
            SortTransitions(Transition.COMPARE_BY_DEST_THEN_MIN_MAX);
            State p = null;
            int min = -1, max = -1;
            int upto = 0;
            for (int i = 0; i < numTransitions; i++)
            {
                Transition t = transitionsArray[i];
                if (p == t.to)
                {
                    if (t.min <= max + 1)
                    {
                        if (t.max > max)
                        {
                            max = t.max;
                        }
                    }
                    else
                    {
                        if (p != null)
                        {
                            transitionsArray[upto++] = new Transition(min, max, p);
                        }
                        min = t.min;
                        max = t.max;
                    }
                }
                else
                {
                    if (p != null)
                    {
                        transitionsArray[upto++] = new Transition(min, max, p);
                    }
                    p = t.to;
                    min = t.min;
                    max = t.max;
                }
            }

            if (p != null)
            {
                transitionsArray[upto++] = new Transition(min, max, p);
            }
            numTransitions = upto;
        }

        /// <summary>
        /// Returns sorted list of outgoing transitions.
        /// </summary>
        /// <param name="comparer"> Comparer to sort with. </param>
        /// <returns> Transition list. </returns>

        /// <summary>
        /// Sorts transitions array in-place. </summary>
        public virtual void SortTransitions(IComparer<Transition> comparer)
        {
            // mergesort seems to perform better on already sorted arrays:
            if (numTransitions > 1)
            {
                ArrayUtil.TimSort(transitionsArray, 0, numTransitions, comparer);
            }
        }

        /// <summary>
        /// Return this state's number.
        /// <para/>
        /// Expert: Will be useless unless <see cref="Automaton.GetNumberedStates()"/>
        /// has been called first to number the states. </summary>
        /// <returns> The number. </returns>
        public virtual int Number => number;

        /// <summary>
        /// Returns string describing this state. Normally invoked via
        /// <see cref="Automaton.ToString()"/>.
        /// </summary>
        public override string ToString()
        {
            StringBuilder b = new StringBuilder();
            b.Append("state ").Append(number);
            if (accept)
            {
                b.Append(" [accept]");
            }
            else
            {
                b.Append(" [reject]");
            }
            b.Append(":\n");
            foreach (Transition t in GetTransitions())
            {
                b.Append("  ").Append(t.ToString()).Append("\n");
            }
            return b.ToString();
        }

        /// <summary>
        /// Compares this object with the specified object for order. States are
        /// ordered by the time of construction.
        /// </summary>
        public virtual int CompareTo(State s)
        {
            return s.id - id;
        }

        // LUCENENET specific - implemented IEquatable and changed to a struct.
        public bool Equals(State other)
        {
            if (other == null)
                return false;
            return id.Equals(other.id);
        }

        public override int GetHashCode()
        {
            return id;
        }
    }
}