#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

#
# METADATA
#

# Merge with previous metadata, avoid duplicates
def merge_meta(last_meta, meta) =
  log(
    level=5,
    label="metadata",
    "Merge | last metadata: #{last_meta}"
  )
  log(
    level=5,
    label="metadata",
    "Merge | current metadata: #{meta}"
  )
  merged = ref(last_meta)

  def add_meta_entry(entry) =
    let (k, _) = entry
    if
      list.assoc.mem(k, merged())
    then
      log(
        level=5,
        label="metadata",
        "Remove existing entry #{entry}"
      )
      merged := list.assoc.remove(k, merged())
    end
    merged := list.add(entry, merged())
  end

  list.iter(add_meta_entry, (meta))
  log(
    level=5,
    label="metadata",
    "Merge | resulting metadata: #{merged()}"
  )
  merged()
end

# Checks for the existence of show-specific metadata
def has_show_meta(meta) =
  list.assoc.mem(engine_meta_key_show_id, meta) ? true : false
end

# Checks if the show ID in two metadata objects matches
def is_same_show(last_meta, current_meta) =
  last_meta[engine_meta_key_show_id] == current_meta[engine_meta_key_show_id]
  ? true : false
end

# Checks if the current show metadata is same as the previous one
def is_same_show(last_meta, current_meta) =
  if
    has_show_meta(last_meta)
  then
    if
      not has_show_meta(current_meta)
    then
      # No current show meta: handle as same show
      true
    elsif is_same_show(last_meta, current_meta) then true
    else
      # A new show has started
      false
    end
  else
    # Last show has no show meta
    if
      not has_show_meta(current_meta)
    then
      # And the current one either: handle as same show
      true
    else
      # Treat missing last show info as the same show
      true
    end
  end
end

# Handles either insert or merge & insert of metadata, depending on the show ID
def do_meta_insert(last_meta_callback, insert_meta_callback, meta) =
  lm = (last_meta_callback() ?? [])
  if
    is_same_show(lm, meta)
  then
    lm = (last_meta_callback() ?? [])
    merged = merge_meta(lm, meta)
    insert_meta_callback(merged)
  else
    insert_meta_callback(meta)
  end
end

# Builds a metadata object from data passed as JSON
def build_metadata(json_string) =
  let json.parse (data :
    {
      show_name: string,
      show_id: int,
      timeslot_id: int,
      playlist_id: int,
      playlist_item: string,
      track_type: int,
      track_start: string?,
      track_duration: float?,
      track_title: string?,
      track_album: string?,
      track_artist: string?
    }
  ) = json_string
  [
    ("show_name", data.show_name),
    ("show_id", "#{data.show_id}"),
    ("timeslot_id", "#{data.timeslot_id}"),
    ("playlist_id", "#{data.playlist_id}"),
    ("playlist_item", "#{data.playlist_item}"),
    ("track_type", "#{data.track_type}"),
    ("track_start", "#{data.track_start}"),
    ("track_duration", "#{data.track_duration}"),
    ("track_title", "#{data.track_title}"),
    ("track_album", "#{data.track_album}"),
    ("track_artist", "#{data.track_artist}")
  ]
end

# Reads the track duration
#   a.) when available from the file
#   b.) as a fallback from the meta field "track_duration"
#
# Returns
#   (float) duration in seconds
def get_meta_track_duration(meta) =
  track_duration = request.duration(meta["filename"])
  if
    track_duration > 0.0
  then
    track_duration
  else
    float_of_string(meta["track_duration"])
  end
end

# Posts a playlog to the Engine API
def post_playlog(api_url, data) =
  json_data = json()

  json_data.add("showId", int_of_string(list.assoc("show_id", data)))
  json_data.add("showName", list.assoc("show_name", data))
  json_data.add("timeslotId", int_of_string(list.assoc("timeslot_id", data)))
  json_data.add("playlistId", int_of_string(list.assoc("playlist_id", data)))
  json_data.add("trackType", int_of_string(list.assoc("track_type", data)))
  json_data.add("trackStart", list.assoc("track_start", data))
  json_data.add(
    "trackDuration", float_of_string(list.assoc("track_duration", data))
  )
  json_data.add("trackTitle", list.assoc("track_title", data))
  json_data.add("trackAlbum", list.assoc("track_album", data))
  json_data.add("trackArtist", list.assoc("track_artist", data))
  if
    list.assoc("track_num", data) != ""
  then
    json_data.add("trackNum", int_of_string(list.assoc("track_num", data)))
  end
  json_data.add("logSource", int_of_string(list.assoc("log_source", data)))

  playlog = json.stringify(json_data)
  log(
    "Posting playlog to '#{api_url}': #{playlog}"
  )
  headers = [("Content-Type", "application/json")]
  result = http.post(headers=headers, data="#{playlog}", "#{api_url}")

  if
    result.status_code < 400
  then
    log(
      "Successfully posted playlog to Engine API."
    )
  else
    log(
      "ERROR during playlog POST: #{result.status_code} | #{result.status_message}"
    )
  end
end

#
# SOURCE
#

# Evaluate the type of source
#
# Returns
#   "fallback", "queue", "stream", "analog_in", "unknown_source"
def eval_source_type(source_id) =
  type_mapping =
    [
      ("fallback_folder", "fallback"),
      ("fallback_playlist", "fallback"),
      ("in_queue_0", "queue"),
      ("in_queue_1", "queue"),
      ("in_stream_0", "stream"),
      ("in_stream_1", "stream"),
      ("in_line_0", "analog_in"),
      ("in_line_1", "analog_in"),
      ("in_line_2", "analog_in"),
      ("in_line_3", "analog_in"),
      ("in_line_4", "analog_in")
    ]
  let source_type =
    list.assoc(default="unknown_source", source_id, type_mapping)
  source_type
end

# Evaluates the track type based on the given:
#   a.) "meta.track_type" passed as annotation, and if not available on
#   b.) "engine_current_track_type" passed via server function
#   c.) "meta.source" and if not available on
#   d.) configured default track type setting
#
# Returns:
#   0=QUEUE/FILE, 1=STREAM, 2=LIVE ANALOG, 3=PLAYLIST
#
def eval_track_type(meta_track_type, meta_source) =
  type_mapping =
    [
      ("fallback_folder", "0"),
      ("fallback_playlist", "3"),
      ("in_queue_0", "0"),
      ("in_queue_1", "0"),
      ("in_stream_0", "1"),
      ("in_stream_1", "1"),
      ("in_line_0", "2"),
      ("in_line_1", "2"),
      ("in_line_2", "2"),
      ("in_line_3", "2"),
      ("in_line_4", "2")
    ]

  track_type =
    list.assoc(default=engine_default_track_type, meta_source, type_mapping)

  if
    meta_track_type != ""
  then
    meta_track_type
  elsif track_type != "" then track_type
  else
    engine_default_track_type
  end
end