# frozen_string_literal: true

require "parslet"
module Plurimath
  class Latex
    class Parse < Parslet::Parser
      rule(:base)       { str("_") }
      rule(:power)      { str("^") }
      rule(:slash)      { str("\\") }
      rule(:under_over) { slash >> underover_classes }
      rule(:array_args) { (str("{") >> expression.as(:args) >> str("}")) }

      rule(:optional_args) do
        (str("[") >> intermediate_exp.maybe.as(:options) >> str("]")).maybe
      end

      rule(:begining) do
        (slash >> str("begin") >> (str("{") >> symbol_text_or_integer >> str("*").as(:asterisk).maybe >> str("}")) >> optional_args)
      end

      rule(:ending) do
        (slash >> str("end") >> (str("{") >> symbol_text_or_integer >> str("*").maybe >> str("}"))).as(:ending)
      end

      rule(:numeric_values) do
        arr_to_expression(Constants::NUMERIC_VALUES, :numeric_values)
      end

      rule(:underover_classes) do
        arr_to_expression(Constants::UNDEROVER_CLASSES, :binary)
      end

      rule(:math_operators_classes) do
        arr_to_expression(Constants::MATH_OPERATORS, :unary_functions)
      end

      rule(:lparen) do
        arr_to_expression(Constants::PARENTHESIS.keys, :lparen)
      end

      rule(:rparen) do
        arr_to_expression(Constants::PARENTHESIS.values, :rparen)
      end

      rule(:environment) do
        arr_to_expression(Constants::MATRICES.keys, :environment)
      end

      rule(:subscript) do
        intermediate_exp >> base >> intermediate_exp.as(:subscript)
      end

      rule(:supscript) do
        intermediate_exp >> power >> intermediate_exp.as(:supscript)
      end

      rule(:math_operators) do
        symbol_text_or_integer.as(:first_value) >> str("\\limits")
      end

      rule(:sqrt_arg) do
        (str("[").as(:lparen) >> intermediate_exp.repeat(1).as(:expression) >> str("]").as(:rparen)) |
          (str("[").as(:lparen) >> str("]").as(:rparen))
      end

      rule(:limits) do
        (math_operators >> base >> intermediate_exp.as(:base) >> power >> intermediate_exp.as(:power)) |
          (math_operators >> power >> intermediate_exp.as(:power) >> base >> intermediate_exp.as(:base))
      end

      rule(:symbol_class_commands) do
        (str("&#x") >> match["0-9a-fA-F"].repeat >> str(";")).as(:unicode_symbols) |
          hash_to_expression(Constants::SYMBOLS) |
          under_over |
          environment |
          numeric_values
      end

      rule(:symbol_text_or_integer) do
        str('"').as(:symbol) |
          symbol_class_commands |
          (slash >> math_operators_classes) |
          match["a-zA-Z"].as(:symbols) |
          match(/\d+(\.[0-9]+)|\d/).repeat(1).as(:number) |
          str("\\\\").as("\\\\") >> match(/\s/).repeat |
          (slash >> (lparen | rparen).as(:symbols)) |
          lparen |
          str("\\ ").as(:space)
      end

      rule(:intermediate_exp) do
        (str("{") >> expression.maybe.as(:expression) >> str("}")) |
          symbol_text_or_integer
      end

      rule(:power_base) do
        (subscript >> power >> intermediate_exp.as(:supscript)).as(:power_base) |
          (supscript >> base >> intermediate_exp.as(:subscript)).as(:power_base) |
          supscript.as(:power) |
          subscript.as(:base)
      end

      rule(:binary_functions) do
        (intermediate_exp.as(:first_value) >> under_over >> intermediate_exp.as(:second_value)).as(:under_over) |
          (slash >> str("sqrt").as(:root) >> sqrt_arg.as(:first_value) >> intermediate_exp.as(:second_value)).as(:binary) |
          (slash >> str("sqrt").as(:sqrt) >> intermediate_exp.as(:intermediate_exp)).as(:binary)
      end

      rule(:sequence) do
        limits.as(:limits) |
          (binary_functions.as(:binary_functions) >> power >> sequence.as(:supscript)).as(:power) |
          (binary_functions.as(:binary_functions) >> base >> sequence.as(:subscript)).as(:base) |
          binary_functions |
          (slash >> str("rule").as(:rule) >> sqrt_arg.maybe.as(:first_value) >> intermediate_exp.maybe.as(:second_value) >> intermediate_exp.maybe.as(:third_value)).as(:binary) |
          (over_class >> power >> intermediate_exp.as(:supscript)) |
          (over_class >> base >> intermediate_exp.as(:subscript)) |
          over_class |
          (left_right.as(:left_right) >> power >> intermediate_exp.as(:supscript)) |
          (left_right.as(:left_right) >> base >> intermediate_exp.as(:subscript)) |
          left_right.as(:left_right) |
          (slash >> str("substack").as(:substack) >> intermediate_exp) |
          (begining >> array_args >> expression.as(:table_data) >> ending).as(:environment) |
          (begining >> expression.as(:table_data) >> ending).as(:environment) |
          (slash >> environment >> intermediate_exp).as(:table_data) |
          power_base |
          (rparen >> (base >> sequence.as(:subscript)).maybe >> power >> sequence.as(:supscript)).as(:power_base) |
          (rparen >> (power >> sequence.as(:supscript)) >> base >> sequence.as(:subscript)).as(:power_base) |
          rparen |
          intermediate_exp
      end

      rule(:left_right) do
        (
         str("\\left").as(:left) >> lparen.maybe >>
         (
           (expression.repeat.as(:dividend) >> str("\\over") >> expression.repeat.as(:divisor)) |
           expression.as(:expression).maybe
         ) >>
         str("\\right").as(:right).maybe >> (rparen | str(".").maybe)
       )
      end

      rule(:over_class) do
        (
          (str("{") >> expression.repeat.as(:dividend) >> str("\\over") >> expression.repeat.as(:divisor) >> str("}")) |
          (left_right.as(:left_right).as(:power) >> power >> intermediate_exp) |
          (left_right.as(:left_right).as(:base) >> base >> intermediate_exp)
        ).as(:over)
      end

      rule(:iteration) do
        (sequence.as(:sequence) >> expression.as(:expression)) |
          sequence
      end

      rule(:expression) do
        (iteration >> expression) |
          iteration |
          ((iteration.as(:dividend) >> str("\\over") >> iteration.as(:divisor)) >> expression.repeat)
      end

      root :expression

      def arr_to_expression(array, name)
        @@new_hash ||= {}
        type = array.first.class
        @@new_hash[name] ||= array.reduce do |expression, expr_string|
          expression = str(expression).as(name) if expression.is_a?(type)
          expression | str(expr_string).as(name)
        end
      end

      def hash_to_expression(hash)
        @@expression ||= hash.reduce do |expression, (key, value)|
          expression = dynamic_rules(expression.first, expression.last) if expression.is_a?(Array)
          expression | dynamic_rules(key, value)
        end
      end

      def dynamic_rules(expr, name)
        first_value = str(expr.to_s)
        case name
        when :operant
          (first_value.as(:operant) | (slashed_value(first_value, :symbols)))
        when :symbols
          slashed_value(first_value, :symbols)
        when :unary
          unary_rules(first_value)
        when :fonts
          (slashed_value(first_value, :fonts) >> (binary_functions | intermediate_exp).as(:intermediate_exp))
        when :power_base
          (slashed_value(first_value, :binary) >> dynamic_power_base).as(:power_base) |
            (slashed_value(first_value, :binary))
        when :underover
          (slashed_value(first_value, :underover) >> dynamic_power_base) |
            (slashed_value(first_value, :underover) >> intermediate_exp.maybe.as(:first_value) >> dynamic_power_base) |
            (slashed_value(first_value, :underover))
        when :binary
          (slashed_value(first_value, :binary) >> intermediate_exp.as(:first_value) >> intermediate_exp.as(:second_value)).as(:binary)
        end
      end

      def slashed_value(first_value, name = nil)
        (slash >> first_value.as(name))
      end

      def unary_rules(first_value)
        (slashed_value(first_value, :unary_functions) >> dynamic_power_base) |
          (slashed_value(first_value, :unary) >> intermediate_exp.as(:first_value)).as(:unary_functions) |
          (slashed_value(first_value, :unary))
      end

      def dynamic_power_base
        (base >> intermediate_exp.as(:subscript) >> power >> intermediate_exp.as(:supscript)) |
          (power >> intermediate_exp.as(:supscript) >> base >> intermediate_exp.as(:subscript)) |
          (power >> intermediate_exp.as(:supscript)) |
          (base >> intermediate_exp.as(:subscript))
      end
    end
  end
end