///|
pub struct HarSoftware {
  name_value : String
  version_value : String
  comment_value : String?
} derive(Eq, Debug)

///|
pub fn HarSoftware::new(
  name : String,
  version : String,
  comment? : String,
) -> Result[HarSoftware, HarError] {
  if name.trim().is_empty() {
    return Err(
      HarError::new(InvalidField, "$.name", "software name cannot be empty"),
    )
  }
  if version.trim().is_empty() {
    return Err(
      HarError::new(
        InvalidField,
        "$.version",
        "software version cannot be empty",
      ),
    )
  }
  Ok({ name_value: name, version_value: version, comment_value: comment })
}

///|
pub fn HarSoftware::name(self : HarSoftware) -> String {
  self.name_value
}

///|
pub fn HarSoftware::version(self : HarSoftware) -> String {
  self.version_value
}

///|
pub fn HarSoftware::comment(self : HarSoftware) -> String? {
  self.comment_value
}

///|
fn parse_software_cursor(cursor : JsonCursor) -> Result[HarSoftware, HarError] {
  let name = match cursor.required_string("name") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  if name.trim().is_empty() {
    return Err(
      HarError::new(
        InvalidField,
        append_json_field(cursor.path(), "name"),
        "software name cannot be empty",
      ),
    )
  }
  let version = match cursor.required_string("version") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  if version.trim().is_empty() {
    return Err(
      HarError::new(
        InvalidField,
        append_json_field(cursor.path(), "version"),
        "software version cannot be empty",
      ),
    )
  }
  let comment = match cursor.optional_string("comment") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({ name_value: name, version_value: version, comment_value: comment })
}

///|
pub fn parse_software(input : String) -> Result[HarSoftware, HarError] {
  let cursor = match parse_json_cursor(input) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  parse_software_cursor(cursor)
}

///|
pub struct HarPageTimings {
  on_content_load_value : Double?
  on_load_value : Double?
  comment_value : String?
} derive(Eq, Debug)

///|
pub fn HarPageTimings::new(
  on_content_load? : Double,
  on_load? : Double,
  comment? : String,
) -> Result[HarPageTimings, HarError] {
  let content = match
    normalize_page_timing(on_content_load, "$.onContentLoad") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let load = match normalize_page_timing(on_load, "$.onLoad") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({
    on_content_load_value: content,
    on_load_value: load,
    comment_value: comment,
  })
}

///|
fn normalize_page_timing(
  value : Double?,
  path : String,
) -> Result[Double?, HarError] {
  match value {
    None => Ok(None)
    Some(number) =>
      if number == -1.0 {
        Ok(None)
      } else if number < 0.0 {
        Err(
          HarError::new(
            InvalidTiming,
            path,
            "page timing must be -1 or non-negative",
          ),
        )
      } else {
        Ok(Some(number))
      }
  }
}

///|
pub fn HarPageTimings::on_content_load(self : HarPageTimings) -> Double? {
  self.on_content_load_value
}

///|
pub fn HarPageTimings::on_load(self : HarPageTimings) -> Double? {
  self.on_load_value
}

///|
pub fn HarPageTimings::comment(self : HarPageTimings) -> String? {
  self.comment_value
}

///|
pub fn HarPageTimings::latest_event(self : HarPageTimings) -> Double? {
  match (self.on_content_load_value, self.on_load_value) {
    (None, None) => None
    (Some(value), None) | (None, Some(value)) => Some(value)
    (Some(left), Some(right)) => Some(if left > right { left } else { right })
  }
}

///|
fn parse_page_timings_cursor(
  cursor : JsonCursor,
) -> Result[HarPageTimings, HarError] {
  let content_raw = match cursor.optional_number("onContentLoad") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let content = match
    normalize_page_timing(
      content_raw,
      append_json_field(cursor.path(), "onContentLoad"),
    ) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let load_raw = match cursor.optional_number("onLoad") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let load = match
    normalize_page_timing(load_raw, append_json_field(cursor.path(), "onLoad")) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let comment = match cursor.optional_string("comment") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({
    on_content_load_value: content,
    on_load_value: load,
    comment_value: comment,
  })
}

///|
pub struct HarPage {
  started_datetime_value : String
  id_value : String
  title_value : String
  page_timings_value : HarPageTimings
  comment_value : String?
} derive(Eq, Debug)

///|
pub fn HarPage::started_datetime(self : HarPage) -> String {
  self.started_datetime_value
}

///|
pub fn HarPage::id(self : HarPage) -> String {
  self.id_value
}

///|
pub fn HarPage::title(self : HarPage) -> String {
  self.title_value
}

///|
pub fn HarPage::page_timings(self : HarPage) -> HarPageTimings {
  self.page_timings_value
}

///|
pub fn HarPage::comment(self : HarPage) -> String? {
  self.comment_value
}

///|
fn validate_datetime(value : String, path : String) -> Result[Unit, HarError] {
  if value.trim().is_empty() {
    return Err(
      HarError::new(InvalidDateTime, path, "date-time cannot be empty"),
    )
  }
  if !value.contains("T") {
    return Err(
      HarError::new(
        InvalidDateTime,
        path,
        "date-time must contain an ISO 8601 date/time separator",
      ),
    )
  }
  Ok(())
}

///|
fn parse_page_cursor(cursor : JsonCursor) -> Result[HarPage, HarError] {
  let started = match cursor.required_string("startedDateTime") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  match
    validate_datetime(
      started,
      append_json_field(cursor.path(), "startedDateTime"),
    ) {
    Ok(_) => ()
    Err(error) => return Err(error)
  }
  let id = match cursor.required_string("id") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  if id.trim().is_empty() {
    return Err(
      HarError::new(
        InvalidField,
        append_json_field(cursor.path(), "id"),
        "page id cannot be empty",
      ),
    )
  }
  let title = match cursor.required_string("title") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let timings_cursor = match cursor.required_object("pageTimings") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let timings = match parse_page_timings_cursor(timings_cursor) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  let comment = match cursor.optional_string("comment") {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  Ok({
    started_datetime_value: started,
    id_value: id,
    title_value: title,
    page_timings_value: timings,
    comment_value: comment,
  })
}

///|
pub fn parse_page(input : String) -> Result[HarPage, HarError] {
  let cursor = match parse_json_cursor(input) {
    Ok(value) => value
    Err(error) => return Err(error)
  }
  parse_page_cursor(cursor)
}