# Copyright (c) 2010-2013 The Mirah project authors. All Rights Reserved.
# All contributing project authors may be found in the NOTICE file.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module Mirah::JVM::Types
  class ComparisonIntrinsic < Intrinsic
    attr_reader :name, :op
    def initialize(type, name, op, args)
      super(type, name, args, type.type_system.type(nil, 'boolean')) do; end
      @type = type
      @op = op
    end

    def call(compiler, call, expression)
      if expression
        @type.compile_boolean_operator(compiler, op, false, call, nil)
      end
    end

    def jump_if(compiler, call, label)
      @type.compile_boolean_operator(compiler, @op, false, call, label)
    end

    def jump_if_not(compiler, call, label)
      @type.compile_boolean_operator(compiler, @op, true, call, label)
    end
    
    def accept(visitor, expression)
      visitor.visitComparison(self, expression)
    end
    
    def kind
      Java::OrgMirahJvmTypes::MemberKind::COMPARISON_OP
    end
  end
  
  class MathIntrinsic < Intrinsic
    def accept(visitor, expression)
      visitor.visitMath(self, expression)
    end
    
    def kind
      Java::OrgMirahJvmTypes::MemberKind::MATH_OP
    end
  end

  class Number < PrimitiveType
    TYPE_ORDERING = ['byte', 'short', 'int', 'long', 'float', 'double']

    # The type returned by arithmetic operations with this type.
    def math_type
      self
    end

    def suffix
      ''
    end

    # Adds an intrinsic that delegates to an intrinsic in another primitive
    # type. That type must support promoting the "this" argument.
    def delegate_intrinsic(name, type, return_type)
      args = [type]
      delegate = type.intrinsics[name][args]
      if delegate.kind_of?(ComparisonIntrinsic)
        add_method(name, args, ComparisonIntrinsic.new(type, name, delegate.op, args))
      elsif delegate.kind_of?(MathIntrinsic)
        method = MathIntrinsic.new(self, name, args, return_type) do |compiler, call, expression|
          if expression
            delegate.call(compiler, call, expression)
          end
        end
        add_method(name, args, method)
      else
        add_method(name, args, return_type) do |compiler, call, expression|
          if expression
            delegate.call(compiler, call, expression)
          end
        end
      end
    end

    def add_delegates(name, return_type = nil)
      index = TYPE_ORDERING.index(math_type.name)
      larger_types = TYPE_ORDERING[index + 1, TYPE_ORDERING.size]
      larger_types.each do |type|
        type = @type_system.type(nil, type)
        delegate_intrinsic(name, type, return_type || type)
      end
    end

    # if_cmpxx for non-ints
    def jump_if(builder, op, label)
      builder.send "#{prefix}cmp#{suffix}"
      builder.send "if#{op}", label
    end

    def boolean_operator(name, op)
      args = [math_type]
      add_method(name, args, ComparisonIntrinsic.new(self, name, op, args))
      add_delegates(name, @type_system.type(nil, 'boolean'))
    end

    def invert_op(op)
      inverted = {
        :lt => :ge,
        :le => :gt,
        :eq => :ne,
        :ne => :eq,
        :gt => :le,
        :ge => :lt
      }[op]
      raise "Can't invert #{op}." unless inverted
      inverted
    end

    def compile_boolean_operator(compiler, op, negated, call, label)
      # Promote the target or the argument if necessary
      convert_args(compiler,
                   [call.target, *call.parameters],
                   [math_type, math_type])
      if negated
        op = invert_op(op)
      end
      if label
        jump_if(compiler.method, op, label)
      else
        compiler.method.op_to_bool do |label|
          jump_if(compiler.method, op, label)
        end
      end
    end

    def math_operator(name, op)
      args = [math_type]
      method = MathIntrinsic.new(self, name, args, math_type) do |compiler, call, expression|
        if expression
          # Promote the target or the argument if necessary
          convert_args(compiler,
                       [call.target, *call.parameters],
                       [math_type, math_type])
          compiler.method.send "#{prefix}#{op}"
        end
      end
      add_method(name, args, method)
      add_delegates(name)
    end

    def unary_operator(name, op)
      add_method(name, [], math_type) do |compiler, call, expression|
        if expression
          call.target.compile(compiler, true)
          compiler.method.send("#{prefix}#{op}") if op
        end
      end
    end

    def add_intrinsics
      boolean_operator('<', :lt)
      boolean_operator('<=', :le)
      boolean_operator('==', :eq)
      boolean_operator('!=', :ne)
      boolean_operator('>=', :ge)
      boolean_operator('>', :gt)
      math_operator('+', :add)
      math_operator('-', :sub)
      math_operator('*', :mul)
      math_operator('/', :div)
      math_operator('%', :rem)
      unary_operator('-@', :neg)
      unary_operator('+@', nil)
    end
  end
end
