///|
fn _align_string(spec : FormatSpec, value : String) -> String {
  let sb = StringBuilder::new()
  let width = spec.width.or(0)
  let mut value = value
  let padding = width - value.length()
  let fill = spec.options.fill.or(' ')

  if spec.options.zero {
    if spec.options.sharp {
      value = value.pad_start(width - 2, '0')
    }else {
      value = value.pad_start(width, '0')
    }
  }

  if spec.options.sharp {
    value = match spec.typ {
      SpecType::Binary => "0b" + value
      SpecType::Octal => "0o" + value
      SpecType::HexLower => "0x" + value
      SpecType::HexUpper => "0X" + value
      _ => value
    }
    sb.write_string(value)
    return sb.to_string()
  }
  if padding > 0 {
    match spec.options.align {
      Align::Left => sb.write_string(value.pad_end(padding, fill))
      Align::Right => sb.write_string(value.pad_start(padding, fill))
      Align::Center => {
        let left_padding = padding / 2 + value.length() / 2
        sb.write_string(
          value.pad_start(left_padding, fill).pad_end(padding, fill),
        )
      }
    }
  } else {
    sb.write_string(value)
  }
  sb.to_string()
}

///|
fn format_basic_type(spec : FormatSpec, value : Json) -> String {
  match spec.typ {
    Float | ExponentLower | ExponentUpper | Percent =>
      _align_string(spec, format_float(spec, value))
    Digit | Binary | Octal | HexLower | HexUpper =>
      _align_string(spec, format_digit(spec, value))
    _ => abort("unexpected type in format_base_type: " + spec.typ.to_string())
  }
}

///|
pub impl Formatter for Int with format(self : Int, spec : FormatSpec) -> String {
  if spec.typ is Default {
    _align_string(spec, self.to_string())
  } else {
    format_basic_type(spec, self.to_json())
  }
}

///|
pub impl Formatter for Int64 with format(self : Int64, spec : FormatSpec) -> String {
  if spec.typ is Default {
    _align_string(spec, self.to_string())
  } else {
    format_basic_type(spec, self.to_json())
  }
}

///|
pub impl Formatter for Int16 with format(self : Int16, spec : FormatSpec) -> String {
  if spec.typ is Default {
    _align_string(spec, self.to_string())
  } else {
    format_basic_type(spec, self.to_json())
  }
}

///|
pub impl Formatter for UInt16 with format(self : UInt16, spec : FormatSpec) -> String {
  if spec.typ is Default {
    _align_string(spec, self.to_string())
  } else {
    format_basic_type(spec, self.to_json())
  }
}

///|
pub impl Formatter for UInt with format(self : UInt, spec : FormatSpec) -> String {
  if spec.typ is Default {
    _align_string(spec, self.to_string())
  } else {
    format_basic_type(spec, self.to_json())
  }
}

///|
pub impl Formatter for UInt64 with format(self : UInt64, spec : FormatSpec) -> String {
  if spec.typ is Default {
    _align_string(spec, self.to_string())
  } else {
    format_basic_type(spec, self.to_json())
  }
}

///|
pub impl Formatter for BigInt with format(self : BigInt, spec : FormatSpec) -> String {
  _align_string(spec, self.to_string())
}

///|
pub impl Formatter for Float with format(self : Float, spec : FormatSpec) -> String {
  if spec.typ is Default {
    _align_string(spec, self.to_string())
  } else {
    format_basic_type(spec, self.to_json())
  }
}

///|
pub impl Formatter for Double with format(self : Double, spec : FormatSpec) -> String {
  if spec.typ is Default {
    _align_string(spec, self.to_string())
  } else {
    format_basic_type(spec, self.to_json())
  }
}

///|
pub impl Formatter for String with format(self : String, spec : FormatSpec) -> String {
  _align_string(spec, self)
}

///|
pub impl Formatter for Char with format(self : Char, spec : FormatSpec) -> String {
  _align_string(spec, self.to_string())
}

///|
pub impl Formatter for Bool with format(self : Bool, spec : FormatSpec) -> String {
  _align_string(spec, self.to_string())
}

///|
pub impl Formatter for Json with format(self : Json, spec : FormatSpec) -> String {
  let value = match self {
    Json::String(s) => s
    Json::Number(n) => n.to_string()
    Json::True => "true"
    Json::False => "false"
    Json::Null => "None"
    Json::Array(arr) => arr.to_string()
    Json::Object(obj) => obj.to_string()
  }
  _align_string(spec, value)
}