require "treetop"

module SearchCopGrammar
  module Nodes
    module Base
      def and(node)
        And.new self, node
      end

      def or(node)
        Or.new self, node
      end

      def not
        Not.new self
      end

      def can_flatten?
        false
      end

      def flatten!
        self
      end

      def can_group?
        false
      end

      def group!
        self
      end

      def fulltext?
        false
      end

      def can_optimize?
        can_flatten? || can_group?
      end

      def optimize!
        flatten!.group! while can_optimize?

        finalize!
      end

      def finalize!
        self
      end

      def nodes
        []
      end
    end

    class Binary
      include Base

      attr_accessor :left, :right

      def initialize(left, right)
        @left = left
        @right = right
      end
    end

    class Equality < Binary; end
    class NotEqual < Binary; end
    class GreaterThan < Binary; end
    class GreaterThanOrEqual < Binary; end
    class LessThan < Binary; end
    class LessThanOrEqual < Binary; end
    class Matches < Binary; end
    class Generator < Binary; end

    class Not
      include Base

      attr_accessor :object

      def initialize(object)
        @object = object
      end

      def finalize!
        @object.finalize!

        self
      end
    end

    class MatchesFulltext < Binary
      include Base

      def not
        MatchesFulltextNot.new left, right
      end

      def fulltext?
        true
      end

      def finalize!
        FulltextExpression.new collection, self
      end

      def collection
        left
      end
    end

    class MatchesFulltextNot < MatchesFulltext; end

    class FulltextExpression
      include Base

      attr_reader :collection, :node

      def initialize(collection, node)
        @collection = collection
        @node = node
      end
    end

    class Collection
      include Base

      attr_reader :nodes

      def initialize(*nodes)
        @nodes = nodes.flatten
      end

      def can_flatten?
        nodes.any?(&:can_flatten?) || nodes.any? { |node| node.is_a?(self.class) || node.nodes.size == 1 }
      end

      def flatten!(&block)
        @nodes = nodes.collect(&:flatten!).collect { |node| node.is_a?(self.class) || node.nodes.size == 1 ? node.nodes : node }.flatten

        self
      end

      def can_group?
        nodes.reject(&:fulltext?).any?(&:can_group?) || nodes.select(&:fulltext?).group_by(&:collection).any? { |_, group| group.size > 1 }
      end

      def group!
        @nodes = nodes.reject(&:fulltext?).collect(&:group!) + nodes.select(&:fulltext?).group_by(&:collection).collect { |collection, group| group.size > 1 ? self.class::Fulltext.new(collection, group) : group.first }

        self
      end

      def finalize!
        @nodes = nodes.collect(&:finalize!)

        self
      end
    end

    class FulltextCollection < Collection
      attr_reader :collection

      def initialize(collection, *nodes)
        @collection = collection

        super(*nodes)
      end

      def fulltext?
        true
      end

      def finalize!
        FulltextExpression.new collection, self
      end
    end

    class And < Collection
      class Fulltext < FulltextCollection; end
    end

    class Or < Collection
      class Fulltext < FulltextCollection; end
    end
  end
end
