using Lucene.Net.QueryParsers.Flexible.Core.Config;
using Lucene.Net.QueryParsers.Flexible.Core.Nodes;
using Lucene.Net.QueryParsers.Flexible.Core.Processors;
using Lucene.Net.QueryParsers.Flexible.Standard.Config;
using Lucene.Net.QueryParsers.Flexible.Standard.Nodes;
using System;
using System.Collections.Generic;
using Operator = Lucene.Net.QueryParsers.Flexible.Standard.Config.StandardQueryConfigHandler.Operator;

namespace Lucene.Net.QueryParsers.Flexible.Standard.Processors
{
    /*
     * 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>
    /// This processor is used to apply the correct <see cref="ModifierQueryNode"/> to
    /// <see cref="BooleanQueryNode"/>s children. This is a variant of
    /// <see cref="Precedence.Processors.BooleanModifiersQueryNodeProcessor"/> which ignores precedence.
    /// <para/>
    /// The <see cref="Parser.StandardSyntaxParser"/> knows the rules of precedence, but lucene
    /// does not. e.g. <code>(A AND B OR C AND D)</code> ist treated like
    /// <code>(+A +B +C +D)</code>.
    /// <para/>
    /// This processor walks through the query node tree looking for
    /// <see cref="BooleanQueryNode"/>s. If an <see cref="AndQueryNode"/> is found, every child,
    /// which is not a <see cref="ModifierQueryNode"/> or the <see cref="ModifierQueryNode"/> is
    /// <see cref="Modifier.MOD_NONE"/>, becomes a <see cref="Modifier.MOD_REQ"/>. For default
    /// <see cref="BooleanQueryNode"/>, it checks the default operator is
    /// <see cref="Operator.AND"/>, if it is, the same operation when an
    /// <see cref="AndQueryNode"/> is found is applied to it. Each <see cref="BooleanQueryNode"/>
    /// which direct parent is also a <see cref="BooleanQueryNode"/> is removed (to ignore
    /// the rules of precedence).
    /// </summary>
    /// <seealso cref="ConfigurationKeys.DEFAULT_OPERATOR"/>
    /// <seealso cref="Precedence.Processors.BooleanModifiersQueryNodeProcessor"/>
    public class BooleanQuery2ModifierNodeProcessor : IQueryNodeProcessor
    {
        internal const string TAG_REMOVE = "remove";
        internal const string TAG_MODIFIER = "wrapWithModifier";
        internal const string TAG_BOOLEAN_ROOT = "booleanRoot";

        private QueryConfigHandler queryConfigHandler;

        private readonly List<IQueryNode> childrenBuffer = new List<IQueryNode>();

        private bool usingAnd = false;

        public BooleanQuery2ModifierNodeProcessor()
        {
            // empty constructor
        }

        public virtual IQueryNode Process(IQueryNode queryTree)
        {
            Operator? op = GetQueryConfigHandler().Get(
                ConfigurationKeys.DEFAULT_OPERATOR);

            if (op == null)
            {
                throw new ArgumentException(
                    "StandardQueryConfigHandler.ConfigurationKeys.DEFAULT_OPERATOR should be set on the QueryConfigHandler");
            }

            this.usingAnd = Operator.AND == op;

            return ProcessIteration(queryTree);
        }


        protected virtual void ProcessChildren(IQueryNode queryTree)
        {
            IList<IQueryNode> children = queryTree.GetChildren();
            if (children != null && children.Count > 0)
            {
                foreach (IQueryNode child in children)
                {
                    /*child = */
                    ProcessIteration(child);
                }
            }
        }

        private IQueryNode ProcessIteration(IQueryNode queryTree)
        {
            queryTree = PreProcessNode(queryTree);

            ProcessChildren(queryTree);

            queryTree = PostProcessNode(queryTree);

            return queryTree;
        }

        protected virtual void FillChildrenBufferAndApplyModifiery(IQueryNode parent)
        {
            foreach (IQueryNode node in parent.GetChildren())
            {
                if (node.ContainsTag(TAG_REMOVE))
                {
                    FillChildrenBufferAndApplyModifiery(node);
                }
                else if (node.ContainsTag(TAG_MODIFIER))
                {
                    childrenBuffer.Add(ApplyModifier(node,
                        (Modifier)node.GetTag(TAG_MODIFIER)));
                }
                else
                {
                    childrenBuffer.Add(node);
                }
            }
        }

        protected virtual IQueryNode PostProcessNode(IQueryNode node)
        {
            if (node.ContainsTag(TAG_BOOLEAN_ROOT))
            {
                this.childrenBuffer.Clear();
                FillChildrenBufferAndApplyModifiery(node);
                node.Set(childrenBuffer);
            }
            return node;
        }

        protected virtual IQueryNode PreProcessNode(IQueryNode node)
        {
            IQueryNode parent = node.Parent;
            if (node is BooleanQueryNode)
            {
                if (parent is BooleanQueryNode)
                {
                    node.SetTag(TAG_REMOVE, true); // no precedence
                }
                else
                {
                    node.SetTag(TAG_BOOLEAN_ROOT, true);
                }
            }
            else if (parent is BooleanQueryNode)
            {
                if ((parent is AndQueryNode)
                    || (usingAnd && IsDefaultBooleanQueryNode(parent)))
                {
                    TagModifierButDoNotOverride(node, Modifier.MOD_REQ);
                }
            }
            return node;
        }

        protected virtual bool IsDefaultBooleanQueryNode(IQueryNode toTest)
        {
            return toTest != null && typeof(BooleanQueryNode).Equals(toTest.GetType());
        }

        private IQueryNode ApplyModifier(IQueryNode node, Modifier mod)
        {
            // check if modifier is not already defined and is default
            if (!(node is ModifierQueryNode modNode))
            {
                return new BooleanModifierNode(node, mod);
            }
            else
            {
                if (modNode.Modifier == Modifier.MOD_NONE)
                {
                    return new ModifierQueryNode(modNode.GetChild(), mod);
                }
            }

            return node;
        }

        protected virtual void TagModifierButDoNotOverride(IQueryNode node, Modifier mod)
        {
            if (node is ModifierQueryNode modNode)
            {
                if (modNode.Modifier == Modifier.MOD_NONE)
                {
                    node.SetTag(TAG_MODIFIER, mod);
                }
            }
            else
            {
                node.SetTag(TAG_MODIFIER, Modifier.MOD_REQ);
            }
        }

        public virtual void SetQueryConfigHandler(QueryConfigHandler queryConfigHandler)
        {
            this.queryConfigHandler = queryConfigHandler;
        }

        public virtual QueryConfigHandler GetQueryConfigHandler()
        {
            return queryConfigHandler;
        }
    }
}
