require "./serializable"

def Object.fromRsconf(toplevel : ::RemiLib::RSConf::RSValue)
  new(toplevel)
end

def Object.fromRsconf(data : String|IO|Path)
  parsed = ::RemiLib::RSConf::Parser.parse(data)
  new(parsed)
end

def Array.fromRsconf(toplevel : ::RemiLib::RSConf::RSValue, &) : Nil
  unless toplevel.is_a?(::RemiLib::RSConf::RSArray)
    raise ::RemiLib::RSConf::RSConfError.new("Toplevel is not an array.")
  end

  new(toplevel) do |element|
    yield element
  end

  nil
end

def Array.fromRsconf(toplevel : String|IO|Path, &) : Nil
  toplevel = ::RemiLib::RSConf::Parser.parse(toplevel)
  unless toplevel.is_a?(::RemiLib::RSConf::RSArray)
    raise ::RemiLib::RSConf::RSConfError.new("Toplevel is not an array.")
  end

  new(toplevel) do |element|
    yield element
  end

  nil
end

def Deque.fromRsconf(toplevel : ::RemiLib::RSConf::RSValue, &) : Nil
  unless toplevel.is_a?(::RemiLib::RSConf::RSArray)
    raise ::RemiLib::RSConf::RSConfError.new("Toplevel is not an array.")
  end

  new(toplevel) do |element|
    yield element
  end

  nil
end

def Deque.fromRsconf(toplevel : String|IO|Path, &) : Nil
  toplevel = ::RemiLib::RSConf::Parser.parse(toplevel)
  unless toplevel.is_a?(::RemiLib::RSConf::RSArray)
    raise ::RemiLib::RSConf::RSConfError.new("Toplevel is not an array.")
  end

  new(toplevel) do |element|
    yield element
  end

  nil
end

def Nil.new(val : ::RemiLib::RSConf::RSValue)
  unless val.is_a?(::RemiLib::RSConf::RSValue)
    raise ::RemiLib::RSConf::RSConfError.new("Not a scalar")
  end

  unless val.isNil?
    raise ::RemiLib::RSConf::RSConfError.new("Not a null value")
  end
  Nil.new
end

def Bool.new(val : ::RemiLib::RSConf::RSValue)
  unless val.is_a?(::RemiLib::RSConf::RSValue)
    raise ::RemiLib::RSConf::RSConfError.new("Not a scalar")
  end

  unless val.is_a?(::RemiLib::RSConf::RSScalar) && val.isBool?
    raise ::RemiLib::RSConf::RSConfError.new("Not a null value")
  end
  val.bool
end

{% for type, method in {
                         "Int8"    => "i8",
                         "Int16"   => "i16",
                         "Int32"   => "i32",
                         "Int64"   => "i64",
                         "Int128"  => "i128",
                         "UInt8"   => "u8",
                         "UInt16"  => "u16",
                         "UInt32"  => "u32",
                         "UInt64"  => "u64",
                         "UInt128" => "u128",
                       } %}
  def {{type.id}}.new(val : ::RemiLib::RSConf::RSValue)
    unless val.is_a?(::RemiLib::RSConf::RSScalar)
      raise ::RemiLib::RSConf::RSConfError.new("Not a scalar")
    end


    value = if val.isInt?
              val.int
            elsif val.isFloat?
              val.float
            else
              raise ::RemiLib::RSConf::RSConfError.new("Not an integer value or a value convertable to integer")
            end
    begin
      value.to_{{method.id}}
    rescue ex : OverflowError | ArgumentError
      raise ::RemiLib::RSConf::RSConfError.new("Can't read {{type.id}}")
    end
  end
{% end %}

def Float32.new(val : ::RemiLib::RSConf::RSValue)
  unless val.is_a?(::RemiLib::RSConf::RSScalar)
    raise ::RemiLib::RSConf::RSConfError.new("Not a scalar")
  end

  value = if val.isInt?
            val.int
          elsif val.isFloat?
            val.float
          else
            raise ::RemiLib::RSConf::RSConfError.new("Not an integer value or a value convertable to integer")
          end
  begin
    value.to_f32
  rescue ex : OverflowError | ArgumentError
    raise ::RemiLib::RSConf::RSConfError.new("Can't read Float32")
  end
end

def Float64.new(val : ::RemiLib::RSConf::RSValue)
  unless val.is_a?(::RemiLib::RSConf::RSScalar)
    raise ::RemiLib::RSConf::RSConfError.new("Not a scalar")
  end

  value = if val.isInt?
            val.int
          elsif val.isFloat?
            val.float
          else
            raise ::RemiLib::RSConf::RSConfError.new("Not an integer value or a value convertable to integer")
          end
  begin
    value.to_f64
  rescue ex : OverflowError | ArgumentError
    raise ::RemiLib::RSConf::RSConfError.new("Can't read Float64")
  end
end

def String.new(val : ::RemiLib::RSConf::RSValue)
  unless val.is_a?(::RemiLib::RSConf::RSScalar)
    raise ::RemiLib::RSConf::RSConfError.new("Not a scalar")
  end

  if val.isString?
    val.string
  else
    raise ::RemiLib::RSConf::RSConfError.new("Not a string")
  end
end

def Path.new(val : ::RemiLib::RSConf::RSValue)
  unless val.is_a?(::RemiLib::RSConf::RSScalar)
    raise ::RemiLib::RSConf::RSConfError.new("Not a scalar")
  end

  if val.isString?
    new(val.string)
  else
    raise ::RemiLib::RSConf::RSConfError.new("Not a string")
  end
end

def Array.new(val : ::RemiLib::RSConf::RSValue)
  unless val.is_a?(::RemiLib::RSConf::RSArray)
    raise ::RemiLib::RSConf::RSConfError.new("Not an array")
  end

  ret = new
  new(val) do |element|
    ret << element
  end
  ret
end

def Array.new(val : ::RemiLib::RSConf::RSValue, &)
  unless val.is_a?(::RemiLib::RSConf::RSArray)
    raise ::RemiLib::RSConf::RSConfError.new("Not an array")
  end

  val.each do |elt|
    yield T.new(elt)
  end
end

def Deque.new(val : ::RemiLib::RSConf::RSValue)
  unless val.is_a?(::RemiLib::RSConf::RSArray)
    raise ::RemiLib::RSConf::RSConfError.new("Not an array")
  end

  ret = new
  new(val) do |element|
    ret << element
  end
  ret
end

def Deque.new(val : ::RemiLib::RSConf::RSValue, &)
  unless val.is_a?(::RemiLib::RSConf::RSArray)
    raise ::RemiLib::RSConf::RSConfError.new("Not an array")
  end

  val.each do |elt|
    yield T.new(elt)
  end
end

def Set.new(val : ::RemiLib::RSConf::RSValue)
  unless val.is_a?(::RemiLib::RSConf::RSArray)
    raise ::RemiLib::RSConf::RSConfError.new("Not an array")
  end

  ret = new
  val.each do |elt|
    ret << T.new(elt)
  end
  ret
end

def Hash.new(value : ::RemiLib::RSConf::RSValue)
  unless value.is_a?(::RemiLib::RSConf::RSObject)
    raise ::RemiLib::RSConf::RSConfError.new("Not an object")
  end

  ret = new
  value.each do |key, val|
    ret[key] = V.new(val)
  end
  ret
end

def Tuple.new(val : ::RemiLib::RSConf::RSValue)
  {% begin %}
    unless val.is_a?(::RemiLib::RSConf::RSArray)
      raise ::RemiLib::RSConf::RSConfError.new("Not an array")
    end

    value = Tuple.new(
      {% for i in 0...T.size %}
        (self[{{i}}].new(val[i])),
      {% end %}
    )
    value
 {% end %}
end

def NamedTuple.new(val : ::RemiLib::RSConf::RSValue)
  {% begin %}
    {% for key, type in T %}
      {% if type.nilable? %}
        %var{key.id} = nil
      {% else %}
        %var{key.id} = uninitialized typeof(element_type({{ key.symbolize }}))
        %found{key.id} = false
      {% end %}
    {% end %}

    unless val.is_a?(::RemiLib::RSConf::RSObject)
      raise ::RemiLib::RSConf::RSConfError.new("Not an object")
    end

    val.each do |key, val|
      case key
        {% for key, type in T %}
          when {{key.stringify}}
            %var{key.id} = self[{{ key.symbolize }}].new(val)
            {% unless type.nilable? %}
              %found{key.id} = true
            {% end %}
        {% end %}
      end
    end

    {% for key, type in T %}
      {% unless type.nilable? %}
        unless %found{key.id}
          raise ::RemiLib::RSConf::RSConfError.new("Missing RSConf attribute: #{ {{ key.id.stringify }} }")
        end
      {% end %}
    {% end %}

    NamedTuple.new(
      {% for key in T.keys %}
        {{ key.id.stringify }}: %var{key.id},
      {% end %}
    )
  {% end %}
end

def Enum.new(val : ::RemiLib::RSConf::RSValue)
  {% if @type.annotation(Flags) %}
    unless val.is_a?(::RemiLib::RSConf::RSArray)
      raise ::RemiLib::RSConf::RSConfError.new("Not an array")
    end

    value = {{ @type }}::None
    val.each do |elt|
      if elt.isString?
        string = elt.string
        value |= parse?(string) || raise ::RemiLib::RSConf::RSConfError.new("Unknown enum #{self} value: #{string.inspect}")
      else
        raise ::RemiLib::RSConf::RSConfError.new("Not a string, cannot convert to enum #{self} value")
      end
    end
    value
  {% else %}
    unless val.is_a?(::RemiLib::RSConf::RSScalar)
      raise ::RemiLib::RSConf::RSConfError.new("Not a scalar")
    end

    if val.isString?
      string = val.string
      parse?(string) || raise ::RemiLib::RSConf::RSConfError.new("Unknown enum #{self} value: #{string.inspect}")
    else
      raise ::RemiLib::RSConf::RSConfError.new("Not a string, cannot convert to enum #{self} value")
    end
  {% end %}
end

module Enum::ValueConverter(T)
  def self.new(val : ::RemiLib::RSConf::RSValue) : T
    from_json(val)
  end

  def self.from_json(val : ::RemiLib::RSConf::RSValue) : T
    unless val.is_a?(::RemiLib::RSConf::RSScalar)
      raise ::RemiLib::RSConf::RSConfError.new("Not a scalar")
    end

    if val.isInt?
      T.from_value?(val.int) || raise RSConfError.new("Unknown enum #{T} value: #{val.int}")
    else
      raise RSConfError.new("Cannot convert to enum #{T}, not an integer")
    end
  end
end

def Union.new(val : ::RemiLib::RSConf::RSValue)
  {% begin %}
    case
    {% if T.includes? Nil %}
    when val.is_a?(::RemiLib::RSConf::RSScalar) && val.isNil?
      return nil
    {% end %}
    {% if T.includes? Bool %}
    when val.is_a?(::RemiLib::RSConf::RSScalar) && val.isBool?
      return val.bool
    {% end %}
    {% if T.includes? String %}
    when val.is_a?(::RemiLib::RSConf::RSScalar) && val.isString?
      return val.string
    {% end %}
    when val.is_a?(::RemiLib::RSConf::RSScalar) && val.isInt?
      {% for type, method in {
                               Int128 => "i128",
                               UInt128 => "u128",
                               Int64 => "i64",
                               UInt64 => "u64",
                               Int32 => "i32",
                               UInt32 => "u32",
                               Int16 => "i16",
                               UInt16 => "u16",
                               Int8 => "i8",
                               UInt8 => "u8"
                             } %}
        {% if T.includes?(type) %}
          value = val.tryInt
          return value.to_{{method.id}} unless value.nil?
        {% end %}
      {% end %}
    when val.is_a?(::RemiLib::RSConf::RSScalar) && val.isFloat?
      {% if T.includes?(Float64) %}
        value = val.tryFloat
        return value.to_f64 unless value.nil?
      {% elsif T.includes?(Float32) %}
        value = val.tryFloat
        return value.to_f64 unless value.nil?
      {% end %}
    else
      # no priority type
    end
  {% end %}

  {% begin %}
    {% primitive_types = [Nil, Bool, String] + Number::Primitive.union_types %}
    {% non_primitives = T.reject { |t| primitive_types.includes? t } %}

    # If after traversing all the types we are left with just one
    # non-primitive type, we can parse it directly
    {% if non_primitives.size == 1 %}
      return {{non_primitives[0]}}.new(val)
    {% else %}
      {% for type in non_primitives %}
        begin
          return {{type}}.fromRsconf(val)
        rescue ::RemiLib::RSConf::RSConfError
          # Ignore
        end
      {% end %}
        raise ::RemiLib::RSConf::RSConfError.new("Couldn't parse #{self} from RSConf value")
    {% end %}
  {% end %}
end

def Time.new(val : ::RemiLib::RSConf::RSValue)
  unless val.is_a?(::RemiLib::RSConf::RSScalar)
    raise ::RemiLib::RSConf::RSConfParseError.new("Not a scalar")
  end

  unless val.string?
    raise ::RemiLib::RSConf::RSConfParseError.new("Not a string")
  end

  Time::Format::ISO_8601_DATE_TIME.parse(val.string)
end

def Time::Location.new(val : ::RemiLib::RSConf::RSValue)
  unless val.is_a?(::RemiLib::RSConf::RSScalar)
    raise ::RemiLib::RSConf::RSConfParseError.new("Not a scalar")
  end

  unless val.string?
    raise ::RemiLib::RSConf::RSConfParseError.new("Not a string")
  end

  load(val.string)
end

struct Time::Format
  def fromRsconf(val : ::RemiLib::RSConf::RSValue) : Time
    unless val.is_a?(::RemiLib::RSConf::RSScalar)
      raise ::RemiLib::RSConf::RSConfParseError.new("Not a scalar")
    end

    unless val.string?
      raise ::RemiLib::RSConf::RSConfParseError.new("Not a string")
    end

    parse(val.string, Time::Location::UTC)
  end
end

module Time::EpochConverter
  def self.fromRsconf(val : ::RemiLib::RSConf::RSValue) : Time
    unless val.is_a?(::RemiLib::RSConf::RSScalar)
      raise ::RemiLib::RSConf::RSConfParseError.new("Not a scalar")
    end

    unless val.isInt?
      raise ::RemiLib::RSConf::RSConfParseError.new("Not an integer")
    end

    Time.unix(val.int)
  end
end

module Time::EpochMillisConverter
  def self.fromRsconf(val : ::RemiLib::RSConf::RSValue) : Time
    unless val.is_a?(::RemiLib::RSConf::RSScalar)
      raise ::RemiLib::RSConf::RSConfParseError.new("Not a scalar")
    end

    unless val.isInt?
      raise ::RemiLib::RSConf::RSConfParseError.new("Not an integer")
    end

    Time.unix_ms(val.int)
  end
end
