// lucene version compatibility level: 4.8.1
using Lucene.Net.Analysis.Phonetic.Language.Bm;
using Lucene.Net.Analysis.TokenAttributes;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace Lucene.Net.Analysis.Phonetic
{
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    /// <summary>
    /// TokenFilter for Beider-Morse phonetic encoding.
    /// <para/>
    /// @lucene.experimental
    /// </summary>
    /// <seealso cref="BeiderMorseEncoder"/>
    public sealed class BeiderMorseFilter : TokenFilter
    {
        private readonly PhoneticEngine engine;
        private readonly LanguageSet languages;

        // output is a string such as ab|ac|...
        // in complex cases like d'angelo its (anZelo|andZelo|...)-(danZelo|...)
        // if there are multiple 's, it starts to nest...
        private static readonly Regex pattern = new Regex("([^()|-]+)", RegexOptions.Compiled);

        private bool isReset = false;
        // matcher over any buffered output
        private Match matcher = pattern.Match("");
        // encoded representation
        private string encoded;
        // preserves all attributes for any buffered outputs
        private State state;

        private readonly ICharTermAttribute termAtt;
        private readonly IPositionIncrementAttribute posIncAtt;

        /// <summary>
        /// Calls <see cref="BeiderMorseFilter(TokenStream, PhoneticEngine, LanguageSet)"/>
        /// </summary>
        /// <param name="input"><see cref="TokenStream"/> to filter</param>
        /// <param name="engine">Configured <see cref="PhoneticEngine"/> with BM settings.</param>
        public BeiderMorseFilter(TokenStream input, PhoneticEngine engine)
            : this(input, engine, null)
        {
        }

        /// <summary>
        /// Create a new <see cref="BeiderMorseFilter"/>
        /// </summary>
        /// <param name="input"><see cref="TokenStream"/> to filter</param>
        /// <param name="engine">Configured <see cref="PhoneticEngine"/> with BM settings.</param>
        /// <param name="languages">Optional Set of original languages. Can be <c>null</c> (which means it will be guessed).</param>
        public BeiderMorseFilter(TokenStream input, PhoneticEngine engine, LanguageSet languages)
            : base(input)
        {
            this.engine = engine;
            this.languages = languages;
            this.termAtt = AddAttribute<ICharTermAttribute>();
            this.posIncAtt = AddAttribute<IPositionIncrementAttribute>();
        }

        public override bool IncrementToken()
        {
            if (!isReset)
            {
                matcher = matcher.NextMatch();
            }
            isReset = false;

            if (matcher.Success)
            {
                Debug.Assert(state != null && encoded != null);
                RestoreState(state);

                int start = matcher.Index;
                int end = start + matcher.Length;
                termAtt.SetEmpty().Append(encoded, start, end);
                posIncAtt.PositionIncrement = 0;
                return true;
            }

            if (m_input.IncrementToken())
            {
                encoded = (languages == null)
                    ? engine.Encode(termAtt.ToString())
                    : engine.Encode(termAtt.ToString(), languages);
                state = CaptureState();

                matcher = pattern.Match(encoded);
                if (matcher.Success)
                {
                    int start = matcher.Index;
                    int end = start + matcher.Length;
                    termAtt.SetEmpty().Append(encoded, start, end);
                }
                return true;
            }
            else
            {
                return false;
            }
        }

        public override void Reset()
        {
            base.Reset();

            // LUCENENET: Since we need to "reset" the Match
            // object, we also need an "isReset" flag to indicate
            // whether we are at the head of the match and to 
            // take the appropriate measures to ensure we don't 
            // overwrite our matcher variable with 
            // matcher = matcher.NextMatch();
            // before it is time. A string could potentially
            // match on index 0, so we need another variable to
            // manage this state.
            matcher = pattern.Match("");
            isReset = true;
        }
    }
}
