// Structured log queries over in-memory records.

///|
pub(all) struct LogQuery {
  minimum_level : Level?
  maximum_level : Level?
  message_fragment : String?
  required_field_key : String?
  required_field_value : String?
  start_timestamp : Int?
  end_timestamp : Int?
  result_limit : Int?
}

///|
pub fn LogQuery::new() -> LogQuery {
  LogQuery::{
    minimum_level: None,
    maximum_level: None,
    message_fragment: None,
    required_field_key: None,
    required_field_value: None,
    start_timestamp: None,
    end_timestamp: None,
    result_limit: None,
  }
}

///|
pub fn LogQuery::min_level(self : LogQuery, level : Level) -> LogQuery {
  LogQuery::{ ..self, minimum_level: Some(level) }
}

///|
pub fn LogQuery::max_level(self : LogQuery, level : Level) -> LogQuery {
  LogQuery::{ ..self, maximum_level: Some(level) }
}

///|
pub fn LogQuery::message_contains(
  self : LogQuery,
  fragment : String,
) -> LogQuery {
  LogQuery::{ ..self, message_fragment: Some(fragment) }
}

///|
pub fn LogQuery::field_exists(self : LogQuery, key : String) -> LogQuery {
  LogQuery::{
    ..self,
    required_field_key: Some(key),
    required_field_value: None,
  }
}

///|
pub fn LogQuery::field_equals(
  self : LogQuery,
  key : String,
  value : String,
) -> LogQuery {
  LogQuery::{
    ..self,
    required_field_key: Some(key),
    required_field_value: Some(value),
  }
}

///|
pub fn LogQuery::since(self : LogQuery, timestamp : Int) -> LogQuery {
  LogQuery::{ ..self, start_timestamp: Some(timestamp) }
}

///|
pub fn LogQuery::until(self : LogQuery, timestamp : Int) -> LogQuery {
  LogQuery::{ ..self, end_timestamp: Some(timestamp) }
}

///|
pub fn LogQuery::limit(self : LogQuery, count : Int) -> LogQuery {
  let normalized = if count < 0 { 0 } else { count }
  LogQuery::{ ..self, result_limit: Some(normalized) }
}

///|
pub fn LogQuery::matches(self : LogQuery, record : LogRecord) -> Bool {
  query_matches_minimum(self.minimum_level, record.level) &&
  query_matches_maximum(self.maximum_level, record.level) &&
  query_matches_message(self.message_fragment, record.message) &&
  query_matches_field(
    self.required_field_key,
    self.required_field_value,
    record.fields,
  ) &&
  query_matches_start(self.start_timestamp, record.timestamp) &&
  query_matches_end(self.end_timestamp, record.timestamp)
}

///|
fn query_matches_minimum(minimum : Level?, actual : Level) -> Bool {
  match minimum {
    None => true
    Some(level) => level_ordinal(actual) >= level_ordinal(level)
  }
}

///|
fn query_matches_maximum(maximum : Level?, actual : Level) -> Bool {
  match maximum {
    None => true
    Some(level) => level_ordinal(actual) <= level_ordinal(level)
  }
}

///|
fn query_matches_message(fragment : String?, message : String) -> Bool {
  match fragment {
    None => true
    Some(value) => message.contains(value)
  }
}

///|
fn query_matches_field(
  key : String?,
  value : String?,
  fields : Array[(String, String)],
) -> Bool {
  match key {
    None => true
    Some(required_key) => query_find_field(fields, required_key, value, 0)
  }
}

///|
fn query_find_field(
  fields : Array[(String, String)],
  required_key : String,
  required_value : String?,
  index : Int,
) -> Bool {
  if index >= fields.length() {
    false
  } else {
    let (key, value) = fields[index]
    if key == required_key {
      let matched = match required_value {
        None => true
        Some(expected) => value == expected
      }
      matched ||
      query_find_field(fields, required_key, required_value, index + 1)
    } else {
      query_find_field(fields, required_key, required_value, index + 1)
    }
  }
}

///|
fn query_matches_start(start : Int?, timestamp : Int) -> Bool {
  match start {
    None => true
    Some(value) => timestamp >= value
  }
}

///|
fn query_matches_end(finish : Int?, timestamp : Int) -> Bool {
  match finish {
    None => true
    Some(value) => timestamp <= value
  }
}

///|
pub fn LogQuery::execute(
  self : LogQuery,
  records : Array[LogRecord],
) -> Array[LogRecord] {
  execute_query(self, records, 0, [])
}

///|
fn execute_query(
  query : LogQuery,
  records : Array[LogRecord],
  index : Int,
  result : Array[LogRecord],
) -> Array[LogRecord] {
  if index >= records.length() ||
    query_limit_reached(query.result_limit, result) {
    result
  } else {
    if query.matches(records[index]) {
      result.push(records[index].copy())
    }
    execute_query(query, records, index + 1, result)
  }
}

///|
fn query_limit_reached(limit : Int?, result : Array[LogRecord]) -> Bool {
  match limit {
    None => false
    Some(value) => result.length() >= value
  }
}

///|
pub fn LogQuery::count(self : LogQuery, records : Array[LogRecord]) -> Int {
  self.execute(records).length()
}

///|
pub fn LogQuery::first(
  self : LogQuery,
  records : Array[LogRecord],
) -> LogRecord? {
  let matches = self.limit(1).execute(records)
  if matches.length() == 0 {
    None
  } else {
    Some(matches[0])
  }
}

///|
pub fn Logger::query(self : Logger, query : LogQuery) -> Array[LogRecord] {
  query.execute(self.records())
}