#if FEATURE_COLLATION
using Icu;
using Icu.Collation;
using Lucene.Net.Analysis;
using Lucene.Net.Analysis.Util;
using Lucene.Net.Util;
using System;
using System.Collections.Generic;
using System.Globalization;
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="CollationKeyFilter"/>.
    /// <para>
    /// This factory can be created in two ways: 
    /// <list type="bullet">
    ///  <item><description>Based upon a system collator associated with a <see cref="System.Globalization.CultureInfo"/>.</description></item>
    ///  <item><description>Based upon a tailored ruleset.</description></item>
    /// </list>
    /// </para>
    /// <para>
    /// Using a System collator:
    /// <list type="bullet">
    ///  <item><description>language: ISO-639 language code (mandatory)</description></item>
    ///  <item><description>country: ISO-3166 country code (optional)</description></item>
    ///  <item><description>variant: vendor or browser-specific code (optional)</description></item>
    ///  <item><description>strength: 'primary','secondary','tertiary', or 'identical' (optional)</description></item>
    ///  <item><description>decomposition: 'no','canonical', or 'full' (optional)</description></item>
    /// </list>
    /// </para>
    /// <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', or 'identical' (optional)</description></item>
    ///  <item><description>decomposition: 'no','canonical', or 'full' (optional)</description></item>
    /// </list>
    /// 
    /// <code>
    /// &lt;fieldType name="text_clltnky" class="solr.TextField" positionIncrementGap="100"&gt;
    ///   &lt;analyzer&gt;
    ///     &lt;tokenizer class="solr.KeywordTokenizerFactory"/&gt;
    ///     &lt;filter class="solr.CollationKeyFilterFactory" language="ja" country="JP"/&gt;
    ///   &lt;/analyzer&gt;
    /// &lt;/fieldType&gt;</code>
    /// 
    /// </para>
    /// </summary>
    /// <seealso cref="Collator"/>
    /// <seealso cref="CultureInfo"/>
    /// <seealso cref="RuleBasedCollator"/>
    /// @since solr 3.1
    /// @deprecated use <see cref="CollationKeyAnalyzer"/> instead. 
    [Obsolete("use <seealso cref=\"CollationKeyAnalyzer\"/> instead.")]
	public class CollationKeyFilterFactory : TokenFilterFactory, IMultiTermAwareComponent, IResourceLoaderAware
	{
		private Collator collator;
        private readonly string custom;
        private readonly string language;
        private readonly string country;
        private readonly string variant;
        private readonly string strength;
        private readonly string decomposition;

		public CollationKeyFilterFactory(IDictionary<string, string> args) : base(args)
		{
			this.custom = this.RemoveFromDictionary(args, "custom");
			this.language = this.RemoveFromDictionary(args, "language");
			this.country = this.RemoveFromDictionary(args, "country");
			this.variant = this.RemoveFromDictionary(args, "variant");
			this.strength = this.RemoveFromDictionary(args, "strength");
            this.decomposition = this.RemoveFromDictionary(args, "decomposition");
			
			if (this.custom == null && this.language == null)
			{
				throw new ArgumentException("Either custom or language is required.");
			}

			if (this.custom != null && (this.language != null || this.country != null || this.variant != null))
			{
				throw new ArgumentException("Cannot specify both language 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 (this.language != null)
			{
				// create from a system collator, based on Locale.
				this.collator = this.CreateFromLocale(this.language, this.country, this.variant);
			}
			else
			{
				// create from a custom ruleset
				this.collator = this.CreateFromRules(this.custom, loader);
			}

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

            // LUCENENET TODO: Verify Decomposition > NormalizationMode mapping between the JDK and icu-dotnet

            // set the decomposition flag, otherwise it will be the default.
            if (this.decomposition != null)
            {
                if (this.decomposition.Equals("no", StringComparison.OrdinalIgnoreCase))
                {
                    this.collator.NormalizationMode = NormalizationMode.Default; // .Decomposition = Collator.NoDecomposition;
                }
                else if (this.decomposition.Equals("canonical", StringComparison.OrdinalIgnoreCase))
                {
                    this.collator.NormalizationMode = NormalizationMode.Off; //.Decomposition = Collator.CannonicalDecomposition;
                }
                else if (this.decomposition.Equals("full", StringComparison.OrdinalIgnoreCase))
                {
                    this.collator.NormalizationMode = NormalizationMode.On; //.Decomposition = Collator.FullDecomposition;
                }
                else
                {
                    throw new ArgumentException("Invalid decomposition: " + this.decomposition);
                }
            }
		}

		public override TokenStream Create(TokenStream input)
		{
			return new CollationKeyFilter(input, this.collator);
		}

		/// <summary>
		/// Create a locale from language, with optional country and variant.
		/// Then return the appropriate collator for the locale.
		/// </summary>
		private Collator CreateFromLocale(string language, string country, string variant)
		{
			CultureInfo cultureInfo;

			if (language == null)
			{
				throw new System.ArgumentException("Language is required");
			}

			if (language != null && country == null && variant != null)
			{
				throw new System.ArgumentException("To specify variant, country is required");
			}

			if (country != null && variant != null)
			{
                cultureInfo = new CultureInfo(string.Concat(language, "-", country, "-", variant));

                // LUCENENET TODO: This method won't work on .NET core - confirm the above solution works as expected.
                //cultureInfo = CultureInfo.GetCultures(CultureTypes.SpecificCultures).Single(x =>
                //{
                //	if (!x.TwoLetterISOLanguageName.Equals(language, StringComparison.OrdinalIgnoreCase) &&
                //		!x.ThreeLetterISOLanguageName.Equals(language, StringComparison.OrdinalIgnoreCase) &&
                //		!x.ThreeLetterWindowsLanguageName.Equals(language, StringComparison.OrdinalIgnoreCase))
                //	{
                //		return false;
                //	}

                //	var region = new RegionInfo(x.Name);

                //	if (!region.TwoLetterISORegionName.Equals(country, StringComparison.OrdinalIgnoreCase) &&
                //		!region.ThreeLetterISORegionName.Equals(country, StringComparison.OrdinalIgnoreCase) &&
                //		!region.ThreeLetterWindowsRegionName.Equals(country, StringComparison.OrdinalIgnoreCase)
                //                    )
                //	{
                //		return false;
                //	}

                //	return x.Name
                //		.Replace(x.TwoLetterISOLanguageName, string.Empty)
                //		.Replace(region.TwoLetterISORegionName, string.Empty)
                //		.Replace("-", string.Empty)
                //		.Equals(variant, StringComparison.OrdinalIgnoreCase);
                //});
            }
			else if (country != null)
			{
				cultureInfo = new CultureInfo(string.Concat(language, "-", country));
			}
			else
			{
				cultureInfo = new CultureInfo(language);
			}

			return Collator.Create(cultureInfo);
		}

         /// <summary>
         /// Read custom rules from a file, and create a 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);
				var rules = ToUTF8String(input);
				return new RuleBasedCollator(rules);
			}
            catch (TransliteratorParseException e)
            {
                // invalid rules
                throw new IOException("ParseException thrown while parsing rules", e);
            }
            catch (SyntaxErrorException e)
			{
                // invalid rules
                throw new IOException("ParseException thrown while parsing rules", e);
			}
			finally
			{
				IOUtils.CloseWhileHandlingException(input);
			}
		}

		public virtual AbstractAnalysisFactory GetMultiTermComponent()
        {
			return this;
		}

		private static string ToUTF8String(Stream @in)
		{
			var builder = new StringBuilder();
			var buffer = new char[1024];
			var reader = IOUtils.GetDecodingReader(@in, Encoding.UTF8);

			var index = 0;
			while ((index = reader.Read(buffer, index, 1)) > 0)
			{
				builder.Append(buffer, 0, index);
			}

			return builder.ToString();
		}

		/// <summary>
		/// Trys to gets the value of a key from a dictionary and removes the value after.
		/// This is to mimic java's Dictionary.Remove method.
		/// </summary>
		/// <returns>The value for the given key; otherwise null.</returns>
		private string RemoveFromDictionary(IDictionary<string, string> args, string key)
		{
			string value = null;
			if (args.TryGetValue(key, out value))
			{
				args.Remove(key);
			}

			return value;
		}
	}
}
#endif