// lucene version compatibility level: 4.8.1
using ICU4N.Globalization;
using ICU4N.Text;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Util;
using Lucene.Net.Support;
using Lucene.Net.Util;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Lucene.Net.Collation
{
    /*
     * 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>
    /// Factory for <see cref="ICUCollationKeyFilter"/>.
    /// </summary>
    /// <remarks>
    /// This factory can be created in two ways: 
    /// <list type="bullet">
    ///     <item><description>Based upon a system collator associated with a Locale.</description></item>
    ///     <item><description>Based upon a tailored ruleset.</description></item>
    /// </list>
    /// <para/>
    /// Using a System collator:
    /// <list type="bullet">
    ///     <item><description>locale: RFC 3066 locale ID (mandatory)</description></item>
    ///     <item><description>strength: 'primary','secondary','tertiary', 'quaternary', or 'identical' (optional)</description></item>
    ///     <item><description>decomposition: 'no', or 'canonical' (optional)</description></item>
    /// </list>
    /// <para/>
    /// Using a Tailored ruleset:
    /// <list type="bullet">
    ///     <item><description>custom: UTF-8 text file containing rules supported by RuleBasedCollator (mandatory)</description></item>
    ///     <item><description>strength: 'primary','secondary','tertiary', 'quaternary', or 'identical' (optional)</description></item>
    ///     <item><description>decomposition: 'no' or 'canonical' (optional)</description></item>
    /// </list>
    /// <para/>
    /// Expert options:
    /// <list type="bullet">
    ///     <item><description>alternate: 'shifted' or 'non-ignorable'. Can be used to ignore punctuation/whitespace.</description></item>
    ///     <item><description>caseLevel: 'true' or 'false'. Useful with strength=primary to ignore accents but not case.</description></item>
    ///     <item><description>caseFirst: 'lower' or 'upper'. Useful to control which is sorted first when case is not ignored.</description></item>
    ///     <item><description>numeric: 'true' or 'false'. Digits are sorted according to numeric value, e.g. foobar-9 sorts before foobar-10</description></item>
    /// </list>
    /// </remarks>
    /// <seealso cref="Collator"/>
    /// <seealso cref="RuleBasedCollator"/>
    [Obsolete("Use ICUCollationKeyAnalyzer instead.")]
    [ExceptionToClassNameConvention]
    public class ICUCollationKeyFilterFactory : TokenFilterFactory, IMultiTermAwareComponent, IResourceLoaderAware
    {
        private Collator collator;
        private readonly string custom;
        private readonly string localeID;
        private readonly string strength;
        private readonly string decomposition;

        private readonly string alternate;
        private readonly string caseLevel;
        private readonly string caseFirst;
        private readonly string numeric;
        private readonly string variableTop;

        public ICUCollationKeyFilterFactory(IDictionary<string, string> args)
            : base(args)
        {
            custom = Get(args, "custom");
            localeID = Get(args, "locale");
            strength = Get(args, "strength");
            decomposition = Get(args, "decomposition");

            alternate = Get(args, "alternate");
            caseLevel = Get(args, "caseLevel");
            caseFirst = Get(args, "caseFirst");
            numeric = Get(args, "numeric");
            variableTop = Get(args, "variableTop");

            if (custom == null && localeID == null)
                throw new ArgumentException("Either custom or locale is required.");

            if (custom != null && localeID != null)
                throw new ArgumentException("Cannot specify both locale and custom. "
                    + "To tailor rules for a built-in language, see the javadocs for RuleBasedCollator. "
                    + "Then save the entire customized ruleset to a file, and use with the custom parameter");

            if (args.Count != 0)
            {
                throw new ArgumentException("Unknown parameters: " + args);
            }
        }

        public virtual void Inform(IResourceLoader loader)
        {
            if (localeID != null)
            {
                // create from a system collator, based on Locale.
                collator = CreateFromLocale(localeID);
            }
            else
            {
                // create from a custom ruleset
                collator = CreateFromRules(custom, loader);
            }

            // set the strength flag, otherwise it will be the default.
            if (strength != null)
            {
                if (strength.Equals("primary", StringComparison.OrdinalIgnoreCase))
                    collator.Strength = CollationStrength.Primary;
                else if (strength.Equals("secondary", StringComparison.OrdinalIgnoreCase))
                    collator.Strength = CollationStrength.Secondary;
                else if (strength.Equals("tertiary", StringComparison.OrdinalIgnoreCase))
                    collator.Strength = CollationStrength.Tertiary;
                else if (strength.Equals("quaternary", StringComparison.OrdinalIgnoreCase))
                    collator.Strength = CollationStrength.Quaternary;
                else if (strength.Equals("identical", StringComparison.OrdinalIgnoreCase))
                    collator.Strength = CollationStrength.Identical;
                else
                    throw new ArgumentException("Invalid strength: " + strength);
            }

            // set the decomposition flag, otherwise it will be the default.
            if (decomposition != null)
            {
                if (decomposition.Equals("no", StringComparison.OrdinalIgnoreCase))
                    collator.Decomposition = NormalizationMode.NoDecomposition;  // (Collator.NO_DECOMPOSITION);
                else if (decomposition.Equals("canonical", StringComparison.OrdinalIgnoreCase))
                    collator.Decomposition = NormalizationMode.CanonicalDecomposition;     //.setDecomposition(Collator.CANONICAL_DECOMPOSITION);
                else
                    throw new ArgumentException("Invalid decomposition: " + decomposition);
            }

            // expert options: concrete subclasses are always a RuleBasedCollator
            RuleBasedCollator rbc = (RuleBasedCollator)collator;
            if (alternate != null)
            {
                if (alternate.Equals("shifted", StringComparison.OrdinalIgnoreCase))
                {
                    rbc.IsAlternateHandlingShifted = true;
                }
                else if (alternate.Equals("non-ignorable", StringComparison.OrdinalIgnoreCase))
                {
                    rbc.IsAlternateHandlingShifted = false;
                }
                else
                {
                    throw new ArgumentException("Invalid alternate: " + alternate);
                }
            }
            if (caseLevel != null)
            {
                rbc.IsCaseLevel = bool.Parse(caseLevel);
            }
            if (caseFirst != null)
            {
                if (caseFirst.Equals("lower", StringComparison.OrdinalIgnoreCase))
                {
                    rbc.IsLowerCaseFirst = true;
                }
                else if (caseFirst.Equals("upper", StringComparison.OrdinalIgnoreCase))
                {
                    rbc.IsUpperCaseFirst = true;
                }
                else
                {
                    throw new ArgumentException("Invalid caseFirst: " + caseFirst);
                }
            }
            if (numeric != null)
            {
                rbc.IsNumericCollation = bool.Parse(numeric);
            }

            if (variableTop != null)
            {
                rbc.SetVariableTop(variableTop);
            }
        }

        public override TokenStream Create(TokenStream input)
        {
            return new ICUCollationKeyFilter(input, collator);
        }

        /// <summary>
        /// Create a locale from <paramref name="localeID"/>.
        /// Then return the appropriate collator for the locale.
        /// </summary>
        /// <param name="localeID"></param>
        /// <returns>The appropriate collator for the locale.</returns>
        private Collator CreateFromLocale(string localeID)
        {
            return Collator.GetInstance(new UCultureInfo(localeID));
        }

        /// <summary>
        /// Read custom rules from a file, and create a <see cref="RuleBasedCollator"/>.
        /// The file cannot support comments, as # might be in the rules!
        /// </summary>
        private Collator CreateFromRules(string fileName, IResourceLoader loader)
        {
            Stream input = null;
            try
            {
                input = loader.OpenResource(fileName);
                string rules = ToUTF8String(input);
                return new RuleBasedCollator(rules);
            }
            catch (Exception e)
            {
                // io error or invalid rules
                throw new Exception(e.ToString(), e);
            }
            finally
            {
                IOUtils.DisposeWhileHandlingException(input);
            }
        }

        public virtual AbstractAnalysisFactory GetMultiTermComponent()
        {
            return this;
        }

        private string ToUTF8String(Stream input)
        {
            StringBuilder sb = new StringBuilder();
            char[] buffer = new char[1024];
            TextReader r = IOUtils.GetDecodingReader(input, Encoding.UTF8);
            int len = 0;
            while ((len = r.Read(buffer, 0, buffer.Length)) > 0)
            {
                sb.Append(buffer, 0, len);
            }
            return sb.ToString();
        }
    }
}
