# frozen_string_literal: true

require "parslet"
module Plurimath
  class Asciimath
    class Parse < Parslet::Parser
      rule(:td)     { expression.as(:td) }
      rule(:base)   { str("_") }
      rule(:power)  { str("^") }
      rule(:space)  { match(/\s+/) }
      rule(:comma)  { (str(",") >> space.maybe) }
      rule(:number) do
        (match("[0-9]").repeat(1) >> str(".") >> match("[0-9]").repeat(1)).as(:number) |
          match("[0-9]").repeat(1).as(:number) |
          str(".").as(:symbol)
      end

      rule(:controversial_symbols)   { power_base | expression }
      rule(:left_right_open_paren)   { str("(") | str("[") }
      rule(:left_right_close_paren)  { str(")") | str("]") }
      rule(:color_left_parenthesis)  { str("(") | str("[") | str("{") }
      rule(:color_right_parenthesis) { str(")") | str("]") | str("}") }

      rule(:binary_classes) do
        arr_to_expression(Constants::BINARY_CLASSES, :binary_class)
      end

      rule(:sub_sup_classes) do
        arr_to_expression(Constants::SUB_SUP_CLASSES, :binary_class)
      end

      rule(:open_table) do
        arr_to_expression(Constants::TABLE_PARENTHESIS.keys, :table_left)
      end

      rule(:close_table) do
        arr_to_expression(Constants::TABLE_PARENTHESIS.values, :table_right)
      end

      rule(:lparen) do
        Constants::PARENTHESIS.keys.reduce do |expression, parenthesis|
          expression = str(expression) if expression.is_a?(Symbol)
          expression | str(parenthesis)
        end
      end

      rule(:rparen) do
        Constants::PARENTHESIS.values.reduce do |expression, parenthesis|
          expression = str(expression) if expression.is_a?(String)
          expression | str(parenthesis)
        end
      end

      rule(:left_right) do
        (str("left") >> left_right_open_paren.as(:left) >> iteration.as(:left_right_value) >> str("right") >> left_right_close_paren.as(:right)) |
          ((table.as(:numerator) >> space.maybe >> match(/(?<!\/)\/(?!\/)/) >> space.maybe >> iteration.as(:denominator)).as(:frac) >> expression) |
          (table.as(:table) >> expression.maybe)
      end

      rule(:quoted_text) do
        str('"') >> match("[^\"]").repeat.as(:text) >> str('"') |
          str('"') >> str("").as(:text)
      end

      rule(:symbol_text_or_integer) do
        sub_sup_classes |
          binary_classes |
          hash_to_expression(Constants.precompile_constants) |
          (match(/[0-9]/).as(:number) >> comma.as(:comma)).repeat(1).as(:comma_separated) |
          quoted_text |
          (str("d").as(:d) >> str("x").as(:x)).as(:intermediate_exp) |
          match["a-zA-Z"].as(:symbol) |
          match(/[^\[{(\\\/@;:.,'"|\]})0-9a-zA-Z\-><$%^&*_=+!`~\s?]/).as(:symbol) |
          number
      end

      rule(:power_base) do
        (base >> space.maybe >> sequence.as(:base_value) >> power >> space.maybe >> sequence.as(:power_value)) |
          (space.maybe >> base >> space.maybe >> sequence.as(:base_value)).as(:base) |
          (space.maybe >> power >> space.maybe >> sequence.as(:power_value)).as(:power) |
          (space.maybe >> base >> space.maybe >> power.as(:symbol).as(:base_value)).as(:base) |
          (space.maybe >> power >> space.maybe >> base.as(:symbol).as(:power_value)).as(:power)
      end

      rule(:power_base_rules) do
        (sub_sup_classes >> power_base).as(:power_base) |
          (binary_classes >> space.maybe >> sequence.as(:base_value).maybe >> space.maybe >> sequence.as(:power_value).maybe).as(:power_base) |
          (sequence.as(:power_base) >> power_base).as(:power_base)
      end

      rule(:table) do
        (open_table.as(:table_left) >> tr >> close_table.as(:table_right)) |
          (open_table.as(:table_left) >> tr >> str("}").as(:table_right)) |
          (str("norm").as(:norm) >> open_table.as(:table_left) >> tr >> close_table.as(:table_right)) |
          (str("{").as(:table_left) >> tr >> close_table.as(:table_right)) |
          (str("|").as(:table_left) >> tr >> str("|").as(:table_right)) |
          (str("left") >> left_right_open_paren.as(:left) >> tr >> str("right") >> left_right_close_paren.as(:right))
      end

      rule(:tr) do
        ((left_right_open_paren.as(:open_tr) >> td.as(:tds_list) >> left_right_close_paren).as(:table_row) >> comma >> tr.as(:expr)) |
          (left_right_open_paren.as(:open_tr) >> td.as(:tds_list) >> left_right_close_paren).as(:table_row)
      end

      rule(:color_value) do
        (color_left_parenthesis.capture(:paren).as(:lparen) >> expression.as(:rgb_color) >> color_right_parenthesis.maybe.as(:rparen)).as(:intermediate_exp) |
          iteration
      end

      rule(:sequence) do
        (lparen.as(:lparen) >> space.maybe >> expression.maybe.as(:expr) >> space.maybe >> rparen.maybe.as(:rparen)).as(:intermediate_exp) |
          (str("text") >> lparen.capture(:paren) >> read_text.as(:text) >> rparen.maybe).as(:intermediate_exp) |
          symbol_text_or_integer
      end

      rule(:frac) do
        (sequence.as(:numerator) >> space.maybe >> match(/(?<!\/)\/(?!\/)/) >> space.maybe >> iteration.as(:denominator)).as(:frac) |
          ((power_base_rules | power_base).as(:numerator) >> match(/(?<!\/)\/(?!\/)/) >> iteration.as(:denominator)).as(:frac)
      end

      rule(:mod) do
        (sequence.as(:dividend) >> space.maybe >> str("mod").as(:mod) >> space.maybe >> iteration.as(:divisor)).as(:mod) |
          ((power_base_rules >> power_base).as(:dividend) >> space.maybe >> str("mod").as(:mod) >> space.maybe >> iteration.as(:divisor)).as(:mod) |
          (power_base_rules.as(:dividend) >> space.maybe >> str("mod").as(:mod) >> space.maybe >> iteration.as(:divisor)).as(:mod)
      end

      rule(:iteration) do
        table.as(:table) |
          comma.as(:comma) |
          mod |
          (sequence.as(:sequence) >> space.maybe >> str("//").as(:symbol)) |
          (str("color") >> color_value.as(:color) >> sequence.as(:color_value)) |
          frac |
          (power_base_rules >> power_base) |
          power_base_rules |
          sequence.as(:sequence) |
          space
      end

      rule(:expression) do
        left_right.as(:left_right) |
          (iteration >> space.maybe >> expression).as(:expr) |
          (base.as(:symbol) >> expression.maybe).as(:expr) |
          (power.as(:symbol) >> expression.maybe).as(:expr) |
          str("") |
          (rparen.as(:rparen) >> space.maybe >> controversial_symbols >> comma.as(:comma).maybe >> expression).repeat(1).as(:expr) |
          (power.as(:symbol) >> space.maybe >> expression).as(:expr) |
          comma.as(:comma).maybe
      end

      root :expression

      def arr_to_expression(arr, name = nil)
        type = arr.first.class
        arr.reduce do |expression, expr_string|
          expression = str(expression).as(name) if expression.is_a?(type)
          expression | str(expr_string).as(name)
        end
      end

      def read_text
        dynamic do |_sour, context|
          rparen = Constants::PARENTHESIS[context.captures[:paren].to_sym]
          match("[^#{rparen}]").repeat
        end
      end

      def hash_to_expression(arr)
        type = arr.first.class
        @@expression ||= arr.reduce do |expression, expr_string|
          expression = dynamic_parser_rules(expression) if expression.is_a?(type)
          expression | dynamic_parser_rules(expr_string)
        end
      end

      def dynamic_parser_rules(expr)
        first_value = str(expr.first.to_s)
        case expr.last
        when :symbol then first_value.as(:symbol)
        when :unary_class then (first_value.as(:unary_class) >> space.maybe >> sequence.maybe).as(:unary)
        when :fonts then first_value.as(:fonts_class) >> space.maybe >> sequence.as(:fonts_value)
        when :special_fonts then first_value.as(:bold_fonts)
        end
      end
    end
  end
end