#!/bin/bash

set -e -o pipefail

: "${MODS_FORGEAPI_KEY:=}"
: "${REMOVE_OLD_FORGEAPI_MODS:=false}"
: "${MODS_FORGEAPI_PROJECTIDS:=}"
: "${MODS_FORGEAPI_FILE:=}"
: "${MODS_FORGEAPI_RELEASES:=RELEASE}"
: "${MODS_FORGEAPI_DOWNLOAD_DEPENDENCIES:=false}"
: "${MODS_FORGEAPI_IGNORE_GAMETYPE:=false}"
: "${REMOVE_OLD_MODS_DEPTH:=1} "
: "${REMOVE_OLD_MODS_INCLUDE:=*.jar,*-version.json}"

#  FORGEAPI_BASE_URL used in manifest downloads below
FORGEAPI_BASE_URL=${FORGEAPI_BASE_URL:-https://api.curseforge.com/v1}
RELEASE_NUMBER_FILTER=1
MINECRAFT_GAME_ID=432
FILTER_BY_FAMILY=false
DOWNLOADED_MODIDS=()
out_dir=/data/mods

# shellcheck source=start-utils
. "${SCRIPTS:-/}start-utils"
isDebugging && set -x

# Remove old mods/plugins
if isTrue "${REMOVE_OLD_FORGEAPI_MODS}"; then
  removeOldMods "/data/mods"
fi

# Family filter is on by default for Forge, Fabric, and Bukkit
updateFamilyFilter(){
  if isFamily "FORGE" "FABRIC" "BUKKIT"; then
    FILTER_BY_FAMILY=true
  fi
}

ensureModKey(){
  if [ -z "$MODS_FORGEAPI_KEY" ]; then
    log "ERROR: MODS_FORGEAPI_KEY REQUIRED to Connect to FORGE API, you supplied: ${MODS_FORGEAPI_KEY}"
    exit 2
  fi
}

# Set the global release type per the text.
# NOTE: downcasing release type for comparing types.
updateReleaseNumber(){
  releaseType=$1
  if [ "release" = "${releaseType,,}" ] || [ 1 = "${releaseType,,}" ]; then
    RELEASE_NUMBER_FILTER=1
  elif [ "beta" = "${releaseType,,}" ] || [ 2 = "${releaseType,,}" ]; then
    RELEASE_NUMBER_FILTER=2
  elif [ "alpha" = "${releaseType,,}" ] || [ 3 = "${releaseType,,}" ]; then
    RELEASE_NUMBER_FILTER=3
  fi
}

retrieveVersionTypeNumber(){
  VERSION_NAME="Minecraft ${MAJOR_VANILLA_VERSION}"
  minecraft_types=$(curl -X GET -s \
    "${FORGEAPI_BASE_URL}/games/${MINECRAFT_GAME_ID}/version-types" \
    -H 'Accept: application/json' -H 'x-api-key: '${MODS_FORGEAPI_KEY}'')

  if [ ! "$minecraft_types" ]; then
    log "ERROR: unable to retrieve version types for ${VERSION_NAME} from ForgeAPI. Check Forge API key or supplied Minecraft version"
    exit 2
  fi

  TYPE_ID=$(jq -n "$minecraft_types" | jq --arg VERSION_NAME "$VERSION_NAME" -jc '
      .data[]? | select(.name==$VERSION_NAME) | .id')

  if [ ! "$TYPE_ID" ]; then
    log "ERROR: unable to retrieve version types for ${VERSION_NAME} from ForgeAPI"
    exit 2
  fi
}

modFileByProjectID(){
  project_id=$(echo "$1" | tr -d '"')
  project_id_release_type=$2
  project_id_file_name=$3
  unset PROJECT_FILE

  # if Type id isn't defined use minecraft version to go get it.
  if [ ! "$TYPE_ID" ]; then
    retrieveVersionTypeNumber
  fi

  # JQ is struggling with larger page sizes so having to pagination for mods with a lot of releases
  pageSize=42
  index=0
  total_count=1

  while [ $index -lt $total_count ]; do
    project_files=$(curl -X GET -s \
      "${FORGEAPI_BASE_URL}/mods/${project_id}/files?gameVersionTypeId=${TYPE_ID}&index=${index}&pageSize=${pageSize}" \
      -H 'Accept: application/json' -H 'x-api-key: '${MODS_FORGEAPI_KEY}'')

    if [ ! "$project_files" ]; then
      log "ERROR: unable to retrieve any project id files for ${project_id} from ForgeAPI"
      exit 2
    fi
    # Use project files to grab out the total count of mods.
    total_count=$(jq -n "$project_files" | jq -c '.pagination.totalCount' )

    # Checking for a individual release type input, if not use global
    if [ "$project_id_release_type" ]; then
      updateReleaseNumber "$project_id_release_type"
      unset project_id_release_type 
    else
      updateReleaseNumber $MODS_FORGEAPI_RELEASES
    fi

    # grabs the highest ID of the releaseTypes selected.
    # Default is 1 for Release, Beta is 2, and Alpha is 3. Using less than we can validate highest release.
    if [ "$project_id_file_name" ]; then
      # Looks for file by name
      current_project_file=$(jq -n "$project_files" | jq --arg FILE_NAME "$project_id_file_name" -jc '
        .data | map(select(.fileName<=($FILE_NAME))) | .[0] // empty')
    elif isFalse "${MODS_FORGEAPI_IGNORE_GAMETYPE}" && $FILTER_BY_FAMILY ; then
      # Looks for file by version and server type in lowercase
      current_project_file=$(jq -n "$project_files" | jq --arg RELEASE_FILTER "$RELEASE_NUMBER_FILTER" --arg GAME_TYPE "${FAMILY,,}" --arg VERSION "$VANILLA_VERSION" -jc '
        .data | sort_by(.id) | reverse | map(select(.gameVersions[] | ascii_downcase | contains ($GAME_TYPE))) |  map(select(.gameVersions[] | contains ($VERSION))) | map(select(.releaseType<=($RELEASE_FILTER|tonumber))) | .[0] // empty')
    else
      # Looks for file by version only.
      current_project_file=$(jq -n "$project_files" | jq --arg RELEASE_FILTER "$RELEASE_NUMBER_FILTER" --arg VERSION "$VANILLA_VERSION" -jc '
        .data | sort_by(.id) | reverse | map(select(.gameVersions[] | contains ($VERSION))) | map(select(.releaseType<=($RELEASE_FILTER|tonumber))) | .[0] // empty')
    fi

    # Logic to grab the latest release over the entire pagination
    if [ ! "$PROJECT_FILE" ]; then
      PROJECT_FILE=$current_project_file
    elif [ "$current_project_file" ]; then
      current_project_file_id=$(jq -n "$current_project_file" | jq -jc '.id // empty' )
      PROJECT_FILE_ID=$(jq -n "$PROJECT_FILE" | jq -jc '.id // empty' )
      if (( current_project_file_id > PROJECT_FILE_ID )); then
        PROJECT_FILE=$current_project_file
      fi
    fi
    
    # check to see if we have gone to far or lost our index and exit with an error
    if [ -z "$index" ] || [ -z "$total_count" ] || [ $index -ge "$total_count" ]; then
      log "ERROR: Unable to retrieve any files for ${project_id} from ForgeAPI also Validate files have release type associated with no. ${RELEASE_NUMBER_FILTER}"
      exit 2
    fi
    # Increment start index to new set.
    index=$((index + pageSize))
  done
  if [ ! "$PROJECT_FILE" ]; then
    log "ERROR: Unable to retrieve any files for ${project_id}, Release Type: ${RELEASE_NUMBER_FILTER}, FAMILY_TYPE: ${FAMILY,,}"
    exit 2
  fi
}

downloadModPackfromModFile() {
  if [ ! "$PROJECT_FILE" ]; then
    log "ERROR: Project File not found from the ForgeAPI"
    exit 2
  fi
  # trys to make the output directory incase it doesnt exist.
  mkdir -p "$out_dir"
  debug "DEBUG: PROJECT_FILE: ${PROJECT_FILE}"
  # grabs needed values from our json return
  file_name=$(jq -n "$PROJECT_FILE" | jq -jc '.fileName // empty' )
  download_url=$(jq -n "$PROJECT_FILE" | jq -jc '.downloadUrl // empty' )
  mod_id=$(jq -n "$PROJECT_FILE" | jq -jc '.modId // empty' )

  if [ ! -f "${out_dir}/${file_name}" ]; then
    echo "Downloading ${download_url}"
    # Track the mods we have downloaded.
    DOWNLOADED_MODIDS+=("${mod_id}")
    if ! get --skip-up-to-date -o "${out_dir}/${file_name}" "${download_url}"; then
      log "ERROR: failed to download from ${download_url}"
      exit 2
    fi
  fi
}

downloadDependencies(){
  if [ "$PROJECT_FILE" ]; then
    dependencies=$(jq -n "$PROJECT_FILE" | jq -jc '.dependencies' )
    required_dependencies=$(jq -n "$dependencies" | jq --arg REQUIRED_FILTER "3" -jc '
      map(select(.relationType==($REQUIRED_FILTER|tonumber)))')
    if [ "$required_dependencies" ]; then
      while read -r current_dependency; do
        mod_id=$(jq -n "$current_dependency" | jq -jc '.modId' )
        # Validate we have not tried to download the mod yet.
        if [[ ! "${DOWNLOADED_MODIDS[*]}" =~ $mod_id ]]; then
          modFileByProjectID "$mod_id" "release"
          downloadModPackfromModFile
        fi
      # needs to be piped in to keep look in main process
      done < <(jq -n "$required_dependencies" | jq -c '.[]?')
    fi
  fi
}

# Use forge api json file to filter and download the correct mods
if [ "$MODS_FORGEAPI_FILE" ] && [ -z "$MODS_FORGEAPI_PROJECTIDS" ]; then
  ensureModKey
  updateFamilyFilter
  if [ ! -f "$MODS_FORGEAPI_FILE" ]; then
    log "ERROR: given MODS_FORGEAPI_FILE file does not exist"
    exit 2
  fi
  debug "DEBUG: MODS_FORGEAPI_KEY: ${MODS_FORGEAPI_FILE}"
  # Needs loop here to look up release types befor calling download.
  while read -r current_project; do
    debug "DEBUG: current_project: ${current_project}"
    # Per stack overflow we can use //empty to return empty string that works with -z
    project_id=$(jq -n "$current_project" | jq -r '.projectId // empty' )
    current_release_type=$(jq -n "$current_project" | jq -r '.releaseType // empty' )
    current_file_name=$(jq -n "$current_project" | jq -r '.fileName // empty' )

    # Validate we have not tried to download the mod yet.
    if [[ ! "${DOWNLOADED_MODIDS[*]}" =~ $project_id ]]; then
      modFileByProjectID "$project_id" "$current_release_type" "$current_file_name"
      downloadModPackfromModFile
      if isTrue "${MODS_FORGEAPI_DOWNLOAD_DEPENDENCIES}"; then
        downloadDependencies
      fi
    fi
  # needs to be piped in to keep look in main process
  done < <(jq -c '.[]?' $MODS_FORGEAPI_FILE)
fi

# Use only project ids and global release data.
if [ "$MODS_FORGEAPI_PROJECTIDS" ] && [ -z "$MODS_FORGEAPI_FILE" ]; then
  ensureModKey
  updateFamilyFilter
  for project_id in ${MODS_FORGEAPI_PROJECTIDS//,/ }; do
    # Validate we have not tried to download the mod yet.
    if [[ ! "${DOWNLOADED_MODIDS[*]}" =~ $project_id ]]; then
      modFileByProjectID $project_id 
      downloadModPackfromModFile
      if isTrue "${MODS_FORGEAPI_DOWNLOAD_DEPENDENCIES}"; then
        downloadDependencies
      fi
    fi
  done
fi

exec "${SCRIPTS:-/}start-setupModpack" "$@"
