require "rbs/state"
require "rbs/parser/negators"

module RBS
  class Parser
    # TODO: Should eventually generate a pure GreaseMonkey AST so that the
    #       formatted output could be generated by escodegen or any code
    #       generator compatible with esprima's output.
    class Rewriter
      attr_reader :state

      def initialize(parser)
        @parser = parser
        @state = State.new
      end

      def parse(experimental: false)
        @experimental = experimental
        @program ||= @parser.parse.tap(&method(:visit))
      end

      def visit(node)
        case node.type
        when :function_statement
          if class_constructor?(node)
            # TODO: declare the class constructor here (instead of RBS::Formatter)
          else
            returnable_function_statement(node)
          end
        when :lambda_expression
          returnable_block_statement(node.block)
        when :unless_statement, :until_statement
          negate_statement(node)
        end

        state << node.type

        node.params.each do |name, n|
          case n
          when Node  then visit(n)
          when Array then n.map(&method(:visit))
          end
        end

        state.pop
      end

      private

      def returnable_function_statement(node)
        if node.block === :try_statement
          returnable_try_statement(node.block)
        else
          returnable_block_statement(node.block)
        end
      end

      def returnable_block_statement(block)
        body = block.body
        body.pop while body.last === :empty_statement

        case body.last && body.last.type
        when :expression_statement
          body[-1] = Node.new(:return_statement, argument: body.last.expression)
        when :if_statement
          returnable_if_statement(body.last)
        when :unless_statement
          returnable_unless_statement(body.last)
        when :case_statement
          returnable_case_statement(body.last)
        when :try_statement
          returnable_try_statement(body.last)
        end
      end

      def returnable_if_statement(node)
        returnable_block_statement(node.consequent)

        if node.alternate
          if node.alternate === :if_statement
            returnable_if_statement(node.alternate)
          else
            returnable_block_statement(node.alternate)
          end
        end
      end

      def returnable_unless_statement(node)
        returnable_block_statement(node.consequent)
      end

      def returnable_case_statement(node)
        returnable_block_statement(node.alternate) if node.alternate
        node.cases.each do |c|
          returnable_block_statement(c.consequent) if c.consequent
        end
      end

      def returnable_try_statement(node)
        returnable_block_statement(node.block)
        returnable_block_statement(node.finalizer) if node.finalizer
        node.handlers.each(&method(:returnable_block_statement))
      end

      # TODO: consider to replace the current node with a fresh one (?)
      def negate_statement(node)
        case node.type
        when :unless_statement
          node.type = :if_statement
          node.test = negate(node.test)
        when :until_statement
          node.type = :while_statement
          node.test = negate(node.test)
        end
      end

      def negate(node)
        if @experimental
          Negators.experimental_negate(node)
        else
          Negators.simple_negate(node)
        end
      end

      def class_constructor?(node)
        state === :class_statement && node.id === :identifier && node.id.name == "constructor"
      end
    end
  end
end
