///|
/// Basic information extracted from an ISO 20022 pain.001 message.
pub struct Pain001Summary {
  message_id : String?
  creation_datetime : String?
  payment_instruction_count : Int
  transaction_count : Int
}

///|
/// Internal parse result used by summary and validation milestones.
priv struct Pain001Data {
  summary : Pain001Summary
  payment_instruction_ids : Array[String?]
  execution_dates : Array[String?]
  end_to_end_ids : Array[String?]
  instruction_amounts : Array[String?]
  instruction_currencies : Array[String?]
  declared_transaction_count : String?
  declared_control_sum : String?
}

///|
fn path_equals(path : Array[String], expected : Array[String]) -> Bool {
  if path.length() != expected.length() {
    return false
  }
  for i = 0; i < path.length(); i = i + 1 {
    if path[i] != expected[i] {
      return false
    }
  }
  true
}

///|
fn set_last(values : Array[String?], value : String) -> Unit {
  if values.length() > 0 {
    values[values.length() - 1] = Some(value)
  }
}

///|
fn parse_pain001_data(xml_text : String) -> Pain001Data raise @xml.XmlError {
  let reader = @xml.Reader::from_string(xml_text)
  let path : Array[String] = []
  let mut message_id : String? = None
  let mut creation_datetime : String? = None
  let mut declared_transaction_count : String? = None
  let mut declared_control_sum : String? = None
  let payment_instruction_ids : Array[String?] = []
  let execution_dates : Array[String?] = []
  let end_to_end_ids : Array[String?] = []
  let instruction_amounts : Array[String?] = []
  let instruction_currencies : Array[String?] = []
  for ;; {
    let event = reader.read_event()
    match event.kind {
      Start(element) => {
        path.push(element.name)
        if path_equals(path, ["Document", "CstmrCdtTrfInitn", "PmtInf"]) {
          payment_instruction_ids.push(None)
          execution_dates.push(None)
        }
        if path_equals(
          path,
          [
            "Document",
            "CstmrCdtTrfInitn",
            "PmtInf",
            "CdtTrfTxInf",
          ],
        ) {
          end_to_end_ids.push(None)
          instruction_amounts.push(None)
          instruction_currencies.push(None)
        }
        if path_equals(
          path,
          [
            "Document",
            "CstmrCdtTrfInitn",
            "PmtInf",
            "CdtTrfTxInf",
            "Amt",
            "InstdAmt",
          ],
        ) {
          if instruction_currencies.length() > 0 {
            instruction_currencies[instruction_currencies.length() - 1] =
              element.get("Ccy")
          }
        }
      }
      Empty(element) => {
        path.push(element.name)
        if path_equals(
          path,
          [
            "Document",
            "CstmrCdtTrfInitn",
            "PmtInf",
            "CdtTrfTxInf",
            "Amt",
            "InstdAmt",
          ],
        ) {
          if instruction_currencies.length() > 0 {
            instruction_currencies[instruction_currencies.length() - 1] =
              element.get("Ccy")
          }
        }
        let _ = path.remove(path.length() - 1)
      }
      Text(content) => {
        if path_equals(
          path,
          ["Document", "CstmrCdtTrfInitn", "GrpHdr", "MsgId"],
        ) {
          message_id = Some(content)
        } else if path_equals(
          path,
          ["Document", "CstmrCdtTrfInitn", "GrpHdr", "CreDtTm"],
        ) {
          creation_datetime = Some(content)
        } else if path_equals(
          path,
          ["Document", "CstmrCdtTrfInitn", "GrpHdr", "NbOfTxs"],
        ) {
          declared_transaction_count = Some(content)
        } else if path_equals(
          path,
          ["Document", "CstmrCdtTrfInitn", "GrpHdr", "CtrlSum"],
        ) {
          declared_control_sum = Some(content)
        } else if path_equals(
          path,
          ["Document", "CstmrCdtTrfInitn", "PmtInf", "PmtInfId"],
        ) {
          set_last(payment_instruction_ids, content)
        } else if path_equals(
          path,
          ["Document", "CstmrCdtTrfInitn", "PmtInf", "ReqdExctnDt"],
        ) {
          set_last(execution_dates, content)
        } else if path_equals(
          path,
          [
            "Document",
            "CstmrCdtTrfInitn",
            "PmtInf",
            "CdtTrfTxInf",
            "PmtId",
            "EndToEndId",
          ],
        ) {
          set_last(end_to_end_ids, content)
        } else if path_equals(
          path,
          [
            "Document",
            "CstmrCdtTrfInitn",
            "PmtInf",
            "CdtTrfTxInf",
            "Amt",
            "InstdAmt",
          ],
        ) {
          set_last(instruction_amounts, content)
        }
      }
      End(_) => {
        if path.length() > 0 {
          let _ = path.remove(path.length() - 1)
        }
      }
      Eof => break
      _ => ()
    }
  }
  let summary = {
    message_id,
    creation_datetime,
    payment_instruction_count: payment_instruction_ids.length(),
    transaction_count: end_to_end_ids.length(),
  }
  {
    summary,
    payment_instruction_ids,
    execution_dates,
    end_to_end_ids,
    instruction_amounts,
    instruction_currencies,
    declared_transaction_count,
    declared_control_sum,
  }
}

///|
pub fn summarize_pain001(
  xml_text : String,
) -> Pain001Summary raise @xml.XmlError {
  parse_pain001_data(xml_text).summary
}

///|
pub fn Pain001Summary::to_text(self : Pain001Summary) -> String {
  let message_id = match self.message_id {
    Some(value) => value
    None => "(missing)"
  }
  let creation_datetime = match self.creation_datetime {
    Some(value) => value
    None => "(missing)"
  }
  "Message ID: " +
  message_id +
  "\nCreation time: " +
  creation_datetime +
  "\nPayment instructions: " +
  self.payment_instruction_count.to_string() +
  "\nTransactions: " +
  self.transaction_count.to_string()
}