h = object.new

eb = { s | s.sub("\\", "\\\\") }

native_ops = ["_percent", "_plus", "_minus", "_forward", "_star", "_up"]
compare_ops = ["_less", "_greater", "_equal_equal", "_less_equal", "_greater_equal"]

h.callable? = { name |
  true? my.env.get_type(name)
  {
    true? my.env.get_type(name) == :function
      { "true" }
      { "false" }
  }
  {
    "object._is_callable(#{name})"
  }
}

h.is_number? = { name |
  true? my.env.get_type(name)
  {
    true? my.env.get_type(name) == :number
      { "true" }
      { "false" }
  }
  {
    "_type(#{name}) == 'number'"
  }
}

h.get_action = { res_var |
  true? res_var == '_return_'
    { "return " }
    { true? res_var
      { "#{res_var} = " }
      { "_dummy_ = " }
    }
}

h.invoke = { node, target, invoke_meth, var |
  res = set_result var
  w = my
  target_var = null
  true? target
    {
       res.out << target.out; target_var = target.var
    }

  args = (node.args || {[]}).map { n | w.process n }
  avars = args.map({ a | a.var })
  res.out << args.map({ a | a.out }).join("\n") << "\n"
  res.out << invoke_meth target_var, node.method, avars, res.var
  avars.each { v | w.env.unset v }
  false? node.name == :invoke_function || { node.name == :invoke_self } 
    { env.unset target_var }
  res
}

h.invoke_local = { t_, name, args, res_var |
  args = (["_self"] + args).join(", ")
  temp = my.env[name]

  action = get_action res_var
  call_it = "#{action} #{temp}(#{args})\n"

  nonmethod_error = "_error(exception:new(\"Tried to invoke non-method: '#{name}' (\" .. object.__type(#{temp}) .. \")\"))"

  t = my.env.get_type temp

  true? t
   {
    true? t == :function
      { call_it }
      {
        nonmethod_error
      }
   }
   {
    "
    if #{callable? temp} then
      #{call_it}
    elseif #{temp} then
      #{action} #{temp}(#{args})
    else
      #{nonmethod_error}
    end
    "
  }
}

h.invoke_self = { t_, name, args, res_var |
  args_array = (["_self"] + args).join(", ")
  action = get_action res_var
  call_it = "#{action} #{name}(#{args_array})\n"

  "
  if #{name} then
    #{call_it}
  else
    #{invoke_method_helper '_self', name, args, res_var}
  end
  "
}

h.invoke_number_method = { target, name, args, res_var |
  "
    local _n = number:new(#{target})
    #{invoke_method_helper '_n', name, args, res_var}"
}

standard_invoke = { target, name, args, res_var |
  "if #{is_number? target} then
      #{target} = number:new(#{target})
    elseif #{callable? target} then
      #{target} = brat_function:new(#{target})
    end
    #{invoke_method_helper target, name, args, res_var}"
}

h.invoke_method = { target, name, args, res_var |
  true? (target.number? || { h.number? target })
  { invoke_number_method target, name, args, res_var }
  {
    true? args.length == 1 && { native_ops.include?(name) || { compare_ops.include? name }}
      {
        rhs = args[0]
        "if #{is_number? target} and #{is_number? rhs} and number._unchanged('#{name}') then
          #{op_helper target, name, rhs, res_var}
         else
          #{standard_invoke target, name, args, res_var}
         end
         "
      }
      {
        standard_invoke target, name, args, res_var
      }
  }
}

h.invoke_index_get = { target, n_, args, res_var |
  args_array = (['_self'] + args).join(", ")
  action = get_action res_var

  "
  if #{callable? target} then
    #{action} #{target}(#{args_array})
  else
    _error(exception:new(\"Tried to invoke non-method: '#{target}' (\" .. object.__type(#{target}) .. \")\"))
  end
  "
}

h.invoke_index_get_direct = { node, target, var |
  res = set_result var
  res_var = res.var
  action = get_action res_var
  target_var = target.var
  arg = node.args.first

  res.out << target.out
  res.out << true? my.env.get_type(target_var) == :hash
    {
      "
      if #{target_var}._unchanged('get') then
        #{action} #{target_var}:get('#{escape_string arg.value}')
      else
        #{invoke_method(target_var, :get, ["string:new('#{escape_string arg.value}')"], res_var) }
      end
      "
    }
    {
      "
      if #{target_var}._lua_hash and #{target.var}._unchanged('get') then
        #{action} #{target_var}:get('#{escape_string arg.value}')
      else
        #{invoke_method(target_var, :get, ["string:new('#{escape_string arg.value}')"], res_var) }
      end
      "
    }

  res
}

h.invoke_index_set_direct = { node, target, var |
  res = set_result var
  res_var = res.var
  action = get_action res_var
  target_var = target.var
  index = node.args.first
  value = process node.args.last

  res.out << target.out
  res.out << value.out
  res.out << true? my.env.get_type(target_var) == :hash
    {
      "
      if #{target_var}._unchanged('set') then
        #{action} #{target_var}:set('#{escape_string index.value}', #{value.var})
      else
        #{invoke_method(target_var, :set, ["string:new('#{escape_string index.value}')", value.var], res_var) }
      end
      "
    }
    {
      "
      if #{target_var}._lua_hash and #{target.var}._unchanged('set') then
        #{action} #{target_var}:set('#{escape_string index.value}', #{value.var})
      else
        #{invoke_method(target_var, :set, ["string:new('#{escape_string index.value}')", value.var], res_var) }
      end
      "
    }

  res
}

h.invoke_function = { target, n_, args, res_var |
  args_array = (['_self'] + args).join(", ")
  action = get_action res_var

  "
  #{action} #{target}(#{args_array})
  "
}

h.invoke_method_helper = { target, name, args, res_var |
  action = get_action res_var
  true? args.empty?
  {
     "
      local _m_#{target}_#{name} = #{target}.#{name}
      if #{callable? "_m_#{target}_#{name}"} then
        #{action} _m_#{target}_#{name}(#{target})
      elseif _m_#{target}_#{name} ~= nil then
        #{action} _m_#{target}_#{name}
      elseif #{target}.no_undermethod then
        #{action} #{target}:no_undermethod(string:new('#{eb unescape_identifier name}'))
      else
        _error(exception:method_error(#{target}, '#{name}'))
      end
      _m_#{target}_#{name} = nil
    "
  }
  {
    arg_list = ([target] + args).join(', ')

    "
      local _m_#{target}_#{name} = #{target}.#{name}
      if #{callable? "_m_#{target}_#{name}"} then
        #{action} _m_#{target}_#{name}(#{arg_list})
      elseif _m_#{target}_#{name} ~= nil then
          _error(exception:argument_error('function', 0, #{args.length - 1 }))
      elseif #{target}.no_undermethod then
        #{action} #{target}:no_undermethod(string:new('#{eb unescape_identifier name}'), #{args.join(', ')})
      else
        _error(exception:method_error(#{target}, '#{name}'))
      end
      _m_#{target}_#{name} = nil
    "
  }
}

h.op_helper = { lhs, op, rhs, res_var |
  action = get_action res_var

  true? native_ops.include?(op)
  {
   "#{action} #{lhs} #{unescape_op op} #{rhs}"
  }
  {
    "if #{lhs} #{unescape_op op} #{rhs} then
       #{action} object.__true
     else
       #{action} object.__false
     end"
  }
}

h.invoke_numbers = { lhs, op, rhs, res_var |

  invoked = invoke_method lhs, op, [rhs], res_var

  true? native_ops.include?(op) || { compare_ops.include?(op) }
  {
    "if number._unchanged('#{op}') then
      #{op_helper lhs, op, rhs, res_var}
    else
      #{invoked}
    end
    "
  }
  {
    invoked
  }
}

# When LHS is number literal
h.invoke_number_lhs = { lhs, op, rhs, res_var |
  "
  if #{is_number? rhs} then
    #{invoke_numbers lhs, op, rhs, res_var}
  else
    #{invoke_method lhs, op, [rhs], res_var}
  end
  "
}

h.invoke_number_rhs = { lhs, op, rhs, res_var |
  "
  if #{is_number? lhs} then
    #{invoke_numbers lhs, op, rhs, res_var}
  else
    #{invoke_method lhs, op, [rhs], res_var}
  end
  "
}

h.get_a_value = { node, var = null |
  res = set_result var
  res.out << get_local_value(node.value, res.var)
  res
}

h.get_local_value = { name, res_var |
  temp = my.env[name]

  action = get_action res_var

  call_it = "#{action} #{temp}(_self)\n"

  t = my.env.get_type temp

  true? t
   {
    true? t == :function
      { call_it }
      {
        true? res_var
          { my.env.set_type res_var, t }

        "#{action} #{temp}\n"
      }
   }
   {
    "
    if #{callable? temp} then
      #{call_it}
    elseif #{temp} then
      #{action} #{temp}
    else
      _error(exception:name_error(\"#{name}\"))
    end
    "
  }
}


h.get_value = { name, res_var |
  true? res_var == '_return_'
    { res_var = null }

  action = true? res_var
            { "#{res_var} =" }
            { "return" }

  call_it = "#{action} #{name}(_self)\n"

  "
   local _m_#{name}
   if #{name} then
     _m_#{name} = #{name}
   else
     _m_#{name} = _self[\"#{name}\"]
   end
   if #{callable? "_m_#{name}"} then
     #{action} _m_#{name}(_self)
   elseif _m_#{name} then
     #{action} _m_#{name}
   elseif _self.no_undermethod then
     #{action} _self:no_undermethod(string:new('#{eb unescape_identifier name}'))
   else
     _error(exception:name_error(\"#{name}\"))
   end
   _m_#{name} = nil
  "
}

h.get_method_local = { name, res_var |
  action = get_action res_var
  temp = my.env[name]

  "
  if #{temp} then
    #{action} #{temp}
  else
    _error(exception:null_error(\"#{name}\", \"access it\"))
  end
  "
}

h.get_method_self = { name, res_var |
  action = get_action res_var

  "
  if _self[\"#{name}\"] then
    #{action} _self[\"#{name}\"]
  else
    _error(exception:null_error(\"#{name}\", \"access it\"))
  end
  "
}

h.get_method = { target, name, res_var |
  action = get_action res_var

  "
  if #{target}[\"#{name}\"] then
    #{action} #{target}[\"#{name}\"]
  else
    _error(exception:method_error(\"#{target}\", \"#{name|}\"))
  end
  "
}

# Converts function to do/end
h.convert_func_to_do_end = { node, var, arg_values = null |
  w = my
  my.env.new_scope

  res = set_result var
  res.out << "do\n"
  action = get_action(res.var)

  # Allow passing values to the function as arguments
  true? arg_values && { node.args.any? }
    {
      false? arg_values.length == node.args.length
        {
          throw "Number of arguments and argument values in function do not match"
        }

      node.args.each_with_index { arg, index |
        temp = w.env.new_var arg.id
        res.out << "#{temp} = #{arg_values[index]}\n"
      }
    }

  body = node.body.copy
  last = body.pop

  body_res = body.map({ n | w.process(n, '_dummy').out }).join("\n")
  res.out << body_res

  true? last
   {
     last_res = my.process last
     res.out << last_res.out << "\n" << "#{action} #{last_res.var}\n"
   }
   {
     true? body.empty?
       { res.out << "#{action} object:null()" }
   }

  my.env.pop_scope
  res.out << "\nend\n"
  res
}

h.process_if_branch = { node, var, condition_var = null |
  true? (node.name == :function || {node.name == :liftable_function})
    { convert_func_to_do_end node, var, condition_var }
    { process node, var }
}

h.true_if = { node, ignored |
   res = set_result
   condition = process node.args[0]
   action = get_action(res.var)

   true_branch = true? node.args[1]
                   { process_if_branch(node.args[1], res.var, [condition.var]) }
                   { set_result(res.var, "object.__true") }

   false_branch = true? node.args[2]
                   { process_if_branch(node.args[2], res.var, [condition.var]) }
                   { set_result(res.var, "object.__false") }

   #TODO: yeck
   call_cond = true? node.args[0].has_method?(:name) && {node.args[0].name == :number}
                { '' }
                { "if object._is_callable(#{condition.var}) then
                    #{condition.var} = #{condition.var}(_self)
                   end"
                }

   regular = invoke(node, null, ->invoke_self, res.var)
 
   res.out << "
   if (_self == object or _rawget(_self, 'true_question') == nil) and true_question == nil and object._unchanged('#{node.method}') then
     -- yay if my var is #{res.var}
     #{condition.out}
     #{call_cond}
     -- end condition

     if object._is_true(#{condition.var}) then
      #{true_branch.out}
      #{action} #{true_branch.var}
     else
      #{false_branch.out}
      #{action} #{false_branch.var}
     end
     -- end yay if
   else
     -- fallback if
     #{regular.out}
     #{action} #{regular.var}
     -- end fallback if
   end
   "
   res
}

h.false_if = { node, ignored |
   res = set_result
   condition = process node.args[0]
   action = get_action(res.var)

   false_branch = true? node.args[1]
                   { process_if_branch(node.args[1], res.var, [condition.var]) }
                   { set_result(res.var, "object.__true") }

   true_branch = true? node.args[2]
                   { process_if_branch(node.args[2], res.var, [condition.var]) }
                   { set_result(res.var, "object.__false") }

   #TODO: yeck
   call_cond = true? node.args[0].has_method?(:name) && {node.args[0].name == :number}
                { '' }
                { "if object._is_callable(#{condition.var}) then
                    #{condition.var} = #{condition.var}(_self)
                   end"
                }

   regular = invoke(node, null, ->invoke_self, res.var)
 
   res.out << "
   if (_self == object or _rawget(_self, 'false_question') == nil) and false_question == nil and object._unchanged('false_question') then
     -- yay if my var is #{res.var}
     #{condition.out}
     #{call_cond}
     -- end condition

     if object._is_true(#{condition.var}) then
      #{true_branch.out}
      #{action} #{true_branch.var}
     else
      #{false_branch.out}
      #{action} #{false_branch.var}
     end
     -- end yay if
   else
     -- fallback false?
     #{regular.out}
     #{action} #{regular.var}
     -- end fallback false?
   end
   "
   res
}

h.null_if = { node, ignored |
   res = set_result
   condition = process node.args[0]
   action = get_action(res.var)

   true_branch = true? node.args[1]
                   { process_if_branch(node.args[1], res.var, [condition.var]) }
                   { set_result(res.var, "object.__true") }

   false_branch = true? node.args[2]
                   { process_if_branch(node.args[2], res.var, [condition.var]) }
                   { set_result(res.var, "object.__false") }

   #TODO: yeck
   call_cond = true? node.args[0].has_method?(:name) && {node.args[0].name == :number}
                { '' }
                { "if object._is_callable(#{condition.var}) then
                    #{condition.var} = #{condition.var}(_self)
                   end"
                }

   regular = invoke(node, null, ->invoke_self, res.var)
 
   res.out << "
   if (_self == object or _rawget(_self, 'null_question') == nil) and null_question == nil and object._unchanged('null_question') then
     -- yay if my var is #{res.var}
     #{condition.out}
     #{call_cond}
     -- end condition

     if #{condition.var} == object.__null or #{condition.var} == nil then
      #{true_branch.out}
      #{action} #{true_branch.var}
     else
      #{false_branch.out}
      #{action} #{false_branch.var}
     end
     -- end yay if
   else
     -- fallback null?
     #{regular.out}
     #{action} #{regular.var}
     -- end fallback null?
   end
   "
   res
}

h.inline_when = { node, var |
  res = set_result
  action = get_action(res.var)

  regular = invoke(node, null, ->invoke_self, res.var)

  w = my

  res.out << "--when\ndo\n" <<
    "if (_self == object or _rawget(_self, 'when') == nil) and null_question == nil and object._unchanged('when') then"

  node.args.each_slice 2, { cond, branch |
    cond_res = w.process_if_branch(cond, null)
    branch_res = w.process_if_branch(branch, null, [cond_res.var])

    res.out << "
    #{cond_res.out}
    if object._is_true(#{cond_res.var}) then
      #{branch_res.out}
      #{action} #{branch_res.var}
    else
    "
  }

  res.out << "
    -- no branches matched
    #{action} object.__null
    "
  ends = (node.args.length / 2).of("end").join(";")

  res.out << ends

  res.out << "
  else
  --when fallback
  #{regular.out}
  #{action} #{regular.var}
  end
  end
  "

  res
}

export h, :invoke_helper