require 'compiler/duby'

module Duby
  module Compiler
    class C
      class MathCompiler
        def call(compiler, name, recv, args)
          recv.call

          compiler.src << " #{name} "

          args.call
        end
      end

      attr_accessor :filename, :src

      def initialize(filename)
        @filename = filename
        @src = ""

        self.type_mapper[AST::TypeReference.new(:fixnum)] = "int"

        self.call_compilers[AST::TypeReference.new(:fixnum)] = MathCompiler.new
      end

      def compile(ast)
        ast.compile(this)
      end

      def define_method(name, signature, args, body)
        @src << "#{type_mapper[signature[:return]]} #{name}("

        args.call

        @src << ") {"

        body.call

        @src << "}\n\n"
      end

      def declare_argument(name, type)
        @src << "#{type_mapper[type]} #{name}"
      end

      def branch(condition, body_proc, else_proc)
        @src << "if ("

        condition.call

        @src << ") {"

        body_proc.call

        if else_proc
          @src << "} else {"

          else_proc.call
        end

        @src << "}"
      end

      def call(name, recv_type, recv, args)
        call_compilers[recv_type].call(self, name, recv, args)
      end

      def call_compilers
        @call_compilers ||= {}
      end

      def self_call(name, args)
        @src << "#{name}("

        args.call

        @src << ")"
      end

      def local(name)
        @src << name
      end

      def fixnum(value)
        @src << value.to_s
      end

      def newline
        @src << ";\n"
      end

      def ret
        @src << "return "

        yield
      end

      def generate
        @src
      end

      def type_mapper
        @type_mapper ||= {}
      end
    end
  end

  module AST
    class Script
      def compile(compiler)
        # preparations for the .c file would go here
        body.compile(compiler)
      end
    end

    class Body
      def compile(compiler)
        last = children[-1]
        children.each do |child|
          child.compile(compiler)
          compiler.newline
        end
      end
    end

    class MethodDefinition
      def compile(compiler)
        args_callback = proc {arguments.compile(compiler)}
        body_callback = proc {body.compile(compiler)}
        compiler.define_method(name, signature, args_callback, body_callback)
      end
    end

    class Arguments
      def compile(compiler)
        args.each {|arg| compiler.declare_argument(arg.name, arg.inferred_type)} if args
      end
    end

    class Noop
      def compile(compiler)
        # nothing
      end
    end

    class Fixnum
      def compile(compiler)
        compiler.fixnum(literal)
      end
    end

    class If
      def compile(compiler)
        cond_callback = proc { condition.compile(compiler) }
        body_callback = proc { body.compile(compiler) }
        else_callback = proc { self.else.compile(compiler)}

        compiler.branch(cond_callback, body_callback, else_callback)
      end
    end

    class Condition
      def compile(compiler)
        predicate.compile(compiler)
      end
    end

    class FunctionalCall
      def compile(compiler)
        args_callback = proc { parameters.each {|param| param.compile(compiler)}}

        compiler.self_call(name, args_callback)
      end
    end

    class Call
      def compile(compiler)
        recv_callback = proc { target.compile(compiler) }
        args_callback = proc { parameters.each {|param| param.compile(compiler)}}

        compiler.call(name, target.inferred_type, recv_callback, args_callback)
      end
    end

    class Local
      def compile(compiler)
        compiler.local(name)
      end
    end
  end
end

if __FILE__ == $0
  ast = Duby::AST.parse(File.read(ARGV[0]))

  typer = Duby::Typer::Simple.new(:script)
  ast.infer(typer)
  typer.resolve(true)

  compiler = Duby::Compiler::C.new("#{ARGV[0]}.c")
  ast.compile(compiler)

  File.open(compiler.filename, "w") {|file| file.write(compiler.generate)}
end