class Dentaku::AST::Arithmetic

def calculate(left_value, right_value)

def calculate(left_value, right_value)
  l = cast(left_value)
  r = cast(right_value)
  l.public_send(operator, r)
rescue ::TypeError => e
  # Right cannot be converted to a suitable type for left. e.g. [] + 1
  raise Dentaku::ArgumentError.for(:incompatible_type, value: r, for: l.class), e.message
end

def cast(val)

def cast(val)
  validate_value(val)
  numeric(val)
end

def datetime?(val)

def datetime?(val)
  # val is a Date, Time, or DateTime
  return true if val.respond_to?(:strftime)
  val.to_s =~ Dentaku::TokenScanner::DATE_TIME_REGEXP
end

def decimal(val)

def decimal(val)
  BigDecimal(val.to_s, Float::DIG + 1)
rescue # return as is, in case value can't be coerced to big decimal
  val
end

def initialize(*)

def initialize(*)
  super
  unless valid_left?
    raise NodeError.new(:numeric, left.type, :left),
          "#{self.class} requires numeric operands"
  end
  unless valid_right?
    raise NodeError.new(:numeric, right.type, :right),
          "#{self.class} requires numeric operands"
  end
end

def numeric(val)

def numeric(val)
  case val.to_s
  when DECIMAL then decimal(val)
  when INTEGER then val.to_i
  else val
  end
end

def operator

def operator
  raise NotImplementedError
end

def type

def type
  :numeric
end

def valid_left?

def valid_left?
  valid_node?(left) || left.type == :datetime
end

def valid_node?(node)

def valid_node?(node)
  node && (node.type == :numeric || node.type == :integer || node.dependencies.any?)
end

def valid_right?

def valid_right?
  valid_node?(right) || right.type == :duration || right.type == :datetime
end

def validate_format(string)

def validate_format(string)
  unless string =~ /\A-?\d*(\.\d+)?\z/ && !string.empty?
    raise Dentaku::ArgumentError.for(:invalid_value, value: string, for: BigDecimal),
          "String input '#{string}' is not coercible to numeric"
  end
end

def validate_operation(val)

def validate_operation(val)
  unless val.respond_to?(operator)
    raise Dentaku::ArgumentError.for(:invalid_operator, operation: self.class, operator: operator),
          "#{ self.class } requires operands that respond to #{operator}"
  end
end

def validate_value(val)

def validate_value(val)
  if val.is_a?(::String)
    validate_format(val)
  else
    validate_operation(val)
  end
end

def value(context = {})

def value(context = {})
  calculate(left.value(context), right.value(context))
end