#!/bin/bash

if [ -n "$APPSH_REPO" ]
then
  repo="$APPSH_REPO"
else
  repo="http://repo1.maven.org"
fi

calculate_md5() {
  local file="$1"; shift

  md5sum "$file" | cut -c 1-32
}
 
# TODO: support file:// repositories
# TODO: look in the local repository first
get() { 
  local url=$1
  local file=$2
  local exit

  curl -o $file $url -D curl.tmp

  exit=`grep "^HTTP/[0-9]\.[0-9] 200 .*" curl.tmp >/dev/null; echo $?`
  head=`head -n 1 curl.tmp`
  rm -f curl.tmp
  if [ "$exit" != 0 ]
  then
    echo "Unable to download $url: $head" >&2
    exit 1
  fi
}

resolve_snapshot() {
  local groupId=$1; shift
  local groupIdSlash=$1; shift
  local artifactId=$1; shift
  local version=$1; shift

  local metadata=$apps/.app/var/download/$groupId-$artifactId-$version-metadata.xml
  local base_url=$repo/$groupIdSlash/$artifactId/$version
  get $base_url/maven-metadata.xml $metadata
  local resolved_version=`xmlstarlet sel -t -m '//snapshotVersion[extension[text()="zip"]]' -v value $metadata`
  echo $resolved_version
}

download_artifact() {
  local file="$1"; shift
  local url="$1"; shift

  echo "Downloading $url.md5"
  get $url.md5 $file.md5
  local expected_md5="`cat $file.md5`"

  if [ -r $file ]
  then
    if [ "$expected_md5" == "`calculate_md5 $file`" ]
    then
      echo "Artifact already downloaded."
    else
      rm -f "$file"
    fi
    return 0
  fi
  echo "Downloading artifact: $url"
  get $url $file

  local actual_md5="`calculate_md5 $file`"
  if [ "$expected_md5" == "$actual_md5" ]
  then
    echo "Artifact downloaded."
  else
    echo "Invalid checksum. Expected $expected_md5, got $actual_md5" >&2
    exit 1
  fi
}

method_install_usage() {
  if [ -n "$1" ]
  then
    echo "Error:" "$@" >&2
  fi

  echo "usage: install <-r resolver> -u <url>" >&2
  echo "" >&2
  echo "Install package from a Maven repository:" >&2
  echo "  $0 [-n name] [-i instance] instance install -r maven -u groupId:artifactId:version" >&2
  echo "" >&2
  echo "Install package from a file:" >&2
  echo "  $0 [-n name] [-i instance] instance install -r file -u file [-v version]" >&2
  echo "The version defaults to the current timestamp" >&2
  exit 1
}

method_install() {
  local name="$1"; shift
  local instance="$1"; shift
  local version
  local resolver
  local url
  local groupId
  local artifactId
  local zip_file


  if [ $# -eq 0 ]
  then
    method_install_usage
  fi

  while getopts "n:i:v:r:u:" opt
  do
    case $opt in
      n)
        name=$OPTARG
        ;;
      i)
        instance=$OPTARG
        ;;
      v)
        version=$OPTARG
        ;;
      r)
        resolver=$OPTARG
        ;;
      u)
        url=$OPTARG
        ;;
      \?)
        method_install_usage "Invalid option: -$OPTARG" 
        ;;
    esac
  done

  if [ -z "$name" ]
  then
    method_install_usage "Missing required argument: -i name."
  fi

  if [ -z "$instance" ]
  then
    method_install_usage "Missing required argument: -i instance."
  fi

  if [ -z "$resolver" ]
  then
    method_install_usage "Missing required option: -r resolver"
  fi

  if [ -z "$url" ]
  then
    method_install_usage "Missing required option: -u url"
  fi

  case "$resolver" in
    maven)
      url=`echo $url | tr ":" " "`; set -- $url
      groupId=$1
      artifactId=$2
      version=$3

      if [ -z "$groupId" -o -z "$artifactId" -o -z "$version" ]
      then
        method_install_usage "Invalid Maven url."
      fi

      local groupIdSlash=$(echo $groupId | sed "s,\.,/,g")
      if [ "`echo $version | sed -n s,.*-SNAPSHOT$,SNAPSHOT,p`" == "SNAPSHOT" ]
      then
        echo "Resolving version $version..."
        local resolved_version=`resolve_snapshot $groupId $groupIdSlash $artifactId $version`
        if [ -z "$resolved_version" ]
        then
          echo "Unable to resolve version."
          exit 1
        fi
        echo "Resolved version $version to $resolved_version"
      else
        resolved_version=$version
      fi

      zip_file=$apps/.app/var/download/$groupId-$artifactId-$resolved_version.zip
      artifact_url=$repo/$groupIdSlash/$artifactId/$version/$artifactId-$resolved_version.zip

      download_artifact "$zip_file" "$artifact_url"
      ;;
    file)
      if [ ! -r "$url" ]
      then
        echo "Could not read file: $url" >&2
        exit 1
      fi

      # TODO: should the zip file be copied into download/ so that
      # there's always a local copy?
      zip_file=$url

      if [ -z "$version" ]
      then
        version=`TZ=UTC date +"%Y%m%d-%H%M%S"`
      fi

      resolved_version=$version
      ;;
    *)
      method_install_usage "Invalid resolver type: $resolver" 
      ;;
  esac

  if [ -d $name/$instance/versions/$resolved_version ]
  then
    echo "Version $resolved_version is already installed"
    exit 1
  fi

  if [ ! -d $name/$instance ]
  then
    echo "Creating instance '$instance' for '$name'"
    mkdir -p $name/$instance
  fi

  mkdir -p $name/$instance/versions/$resolved_version

  echo "Unpacking..."
  unzip -q -d $name/$instance/versions/$resolved_version $zip_file

  if [ ! -d $name/$instance/versions/$resolved_version/root ]
  then
    echo "Invalid zip file, did not contain a ./root directory." >&2
    exit 1
  fi

  echo "Changing current symlink"
  rm -f $apps/$name/$instance/current
  ln -s versions/$resolved_version/root $apps/$name/$instance/current

  if [ -d $name/$instance/current/bin ]
  then
    (
      cd $name/$instance/current
      find bin -type f | xargs chmod +x
    )
  fi

  (
    cd $name/$instance/versions/$resolved_version
    if [ -d scripts ]
    then
      find scripts | xargs chmod +x
    fi

    if [ -x scripts/postinstall ]
    then
      echo "Running postinstall..."
      cd root
      set +e
      env -i \
        PATH=/bin:/usr/bin \
        APPSH_APPS=$apps \
        APPSH_HOME=$APPSH_HOME \
        APPSH_NAME=$name \
        APPSH_INSTANCE=$instance \
        APPSH_VERSION=$resolved_version \
        ../scripts/postinstall
      set -e
      ret=`echo $?`
      if [ "$ret" != 0 ]
      then
        echo "Postinstall failed!"
        exit 1
      fi
      echo "Postinstall completed successfully"
    fi
  )

  if [ -r $apps/.app/var/list ]
  then
    sed "/^$name:$instance/d" $apps/.app/var/list > $apps/.app/var/list.new
  fi
  echo "$name:$instance:$version:$url" >> $apps/.app/var/list.new
  mv $apps/.app/var/list.new $apps/.app/var/list
}

method_set_current_usage() {
  if [ -n "$1" ]
  then
    echo "Error:" "$@" >&2
  fi

  echo "usage: set-current -v version" >&2
  exit 1
}

method_set_current() {
  local name="$1"; shift
  local instance="$1"; shift
  local version

  if [ $# -eq 0 ]
  then
    method_set_current_usage
  fi

  while getopts "n:i:v:" opt
  do
    case $opt in
      n)
        name=$OPTARG
        ;;
      i)
        instance=$OPTARG
        ;;
      v)
        version=$OPTARG
        ;;
      \?)
        method_set_current_usage "Invalid option: -$OPTARG" 
        ;;
    esac
  done

  if [ -z "$version" ]
  then
    echo "Missing required option -v version." >&2
    exit 1
  fi

  assert_is_instance method_set_current_usage "$name" "$instance" "no"

  if [ ! -d $apps/$name/$instance/versions/$version ]
  then
    echo "Invalid version: $version."
    exit 1
  fi

  rm -f $apps/$name/$instance/current
  ln -s versions/$version/root $apps/$name/$instance/current

  return 0
}

method_list_usage() {
  if [ -n "$1" ]
  then
    echo "Error:" "$@" >&2
  fi

  echo "usage: list [-n name] [-P field]" >&2
  echo ""
  echo "List all installed applications:" >&2
  echo "  list" >&2
  echo ""
  echo "List all applications with the selected fields with parseable output:" >&2
  echo "  list -P instance -P version -n foo" >&2
  exit 1
}

method_list() {
  local filter_name="$1"; shift
  local filter_instance="$1"; shift
  local mode="pretty"
  local vars
  local filter_name

  while getopts "P:n:i:" opt
  do
    case $opt in
      P)
        mode="parseable"
        vars="$vars $OPTARG"
        ;;
      n)
        filter_name=$OPTARG
        ;;
      i)
        filter_instance=$OPTARG
        ;;
      \?)
        method_list_usage "Invalid option: -$OPTARG" 
        ;;
    esac
  done
 
  if [ ! -r $apps/.app/var/list ]
  then
    return
  fi

  if [ $mode = "pretty" ]
  then
    printf "%-20s %-20s %-20s\n" "Name" "Instance" "Version"
    list_apps "$filter_name" "$filter_instance" name instance version | (IFS=:; while read name instance version
    do
      printf "%-20s %-20s %-20s\n" "$name" "$instance" "$version"
    done)
  else
    list_apps "$filter_name" "$filter_instance" $vars
  fi
}

method_list_versions_usage() {
  if [ -n "$1" ]
  then
    echo "Error:" "$@" >&2
  fi

  echo "usage: list-versions -n name -i instance [-P]" >&2
  exit 1
}

method_list_versions() {
  local filter_name="$1"; shift
  local instance="$1"; shift
  local version
  local mode="pretty"

  if [ $# -eq 0 ]
  then
    method_list_versions_usage
  fi

  while getopts "n:i:P" opt
  do
    case $opt in
      n)
        name=$OPTARG
        ;;
      i)
        instance=$OPTARG
        ;;
      v)
        version=$OPTARG
        ;;
      P)
        mode="parseable"
        ;;
      \?)
        method_list_versions_usage "Invalid option: -$OPTARG" 
        ;;
    esac
  done

  assert_is_instance method_list_versions_usage "$name" "$instance" "no"

  if [ $mode = "pretty" ]
  then
    echo "Available versions for $name/$instance:"
  fi

  find_versions $name $instance

  return 0
}

method_instance_usage() {
  if [ -n "$1" ]
  then
    echo "Error:" $@ >&2
  fi

  echo "usage: $0 instance <method>" >&2
  echo "" >&2
  echo "Available methods:" >&2
  echo "  install       - Installs an application" >&2
  echo "  list          - List all installed applications" >&2
  echo "  list-versions - List all available versions for a single application" >&2
  echo "  set-current   - Set the current version" >&2
}

method_instance() {
  local name="$1"; shift
  local instance="$1"; shift
  local method="$1"

  if [ $# -gt 0 ]
  then
    shift
  fi

  case "$method" in
    install)       method_install       "$name" "$instance" "$@" ;;
    list)          method_list          "$name" "$instance" "$@" ;;
    list-versions) method_list_versions "$name" "$instance" "$@" ;;
    set-current)   method_set_current   "$name" "$instance" "$@" ;;
    *)
      if [ -z "$method" ]
      then
        method_instance_usage
      else
        method_instance_usage "Unknown method $method"
      fi
      ;;
  esac
  exit $?
}