defmodule HL7.Header do
  @moduledoc """
  An HL7 header implementation that can be used to build MSH segments and HL7 messages.
  It is also exposed as metadata after parsing any HL7 message.
  """

  @type t :: %HL7.Header{
          message_type: binary(),
          trigger_event: binary(),
          sending_facility: binary(),
          sending_application: binary(),
          message_date_time: String.t(),
          security: String.t(),
          message_control_id: String.t(),
          processing_id: String.t(),
          separators: HL7.Separators.t(),
          hl7_version: binary()
        }

  defstruct message_type: "",
            trigger_event: "",
            sending_facility: "",
            sending_application: "",
            receiving_facility: "",
            receiving_application: "",
            message_date_time: "",
            security: "",
            message_control_id: "",
            processing_id: "",
            separators: %HL7.Separators{},
            hl7_version: ""

  @spec new(String.t(), String.t(), String.t(), String.t() | list(), String.t()) :: HL7.Header.t()
  def new(message_type, trigger_event, message_control_id, processing_id \\ "P", version \\ "2.1") do
    %HL7.Header{
      hl7_version: version,
      message_type: message_type,
      trigger_event: trigger_event,
      message_control_id: message_control_id,
      processing_id: processing_id,
      message_date_time: get_message_date_time()
    }
  end

  @spec to_msh(HL7.Header.t()) :: [any(), ...]
  def to_msh(%HL7.Header{} = h) do
    [
      "MSH",
      h.separators.field,
      h.separators.encoding_characters,
      h.sending_application,
      h.sending_facility,
      h.receiving_application,
      h.receiving_facility,
      h.message_date_time,
      h.security,
      get_message_type_field(h),
      h.message_control_id,
      h.processing_id,
      h.hl7_version
    ]
  end

  @spec zero_pad(pos_integer(), pos_integer()) :: String.t()
  defp zero_pad(num, digits_needed) when is_integer(num) and is_integer(digits_needed) do
    string_num = Integer.to_string(num)
    pad_size = digits_needed - String.length(string_num)
    zeros = String.duplicate("0", pad_size)
    zeros <> string_num
  end

  @spec get_message_date_time() :: String.t()
  def get_message_date_time() do
    now = DateTime.utc_now()

    zero_pad(now.year, 4) <>
      zero_pad(now.month, 2) <>
      zero_pad(now.day, 2) <>
      zero_pad(now.hour, 2) <> zero_pad(now.minute, 2) <> zero_pad(now.second, 2) <> "+0000"
  end

  @spec get_message_type_field(HL7.Header.t()) :: [any()]
  def get_message_type_field(%HL7.Header{} = h) do
    v =
      case h.hl7_version do
        "2.1" -> [h.message_type, h.trigger_event]
        "2.2" -> [h.message_type, h.trigger_event]
        "2.3" -> [h.message_type, h.trigger_event]
        "2.3.1" -> [h.message_type, h.trigger_event]
        _ -> [h.message_type, h.trigger_event, h.message_type <> "_" <> h.trigger_event]
      end

    [v]
  end
end
