#!/bin/bash

if ! command -v jq &> /dev/null; then
	echo "jq - the commandline JSON processor - is required dependency, please install it on your system..."
	exit
fi


QUOTED_ARGS=$(printf " %q" "$@")
ORIGINAL_COMMAND="$0$QUOTED_ARGS"
handle_interrupt() {
  echo "Upload script interrupted" >&2
  echo "To continue, try running: ${RETRY_COMMAND:-$ORIGINAL_COMMAND}" >&2
  exit 1
}
trap handle_interrupt SIGINT

parse_from_json() {
	echo "$1" | jq -r ".$2"
}

print_response_verbose() {
  if ((VERBOSE)); then
    echo "Request: $1 $2"
    echo "Body: $3"
    echo "Response: $4"
    echo "Body: $5"
  fi
}

FILES=()

for ARG in "$@"; do
	case "$ARG" in
		--login=*)
		LOGIN="${ARG#*=}"
		;;

		--api-token=*)
		API_TOKEN="${ARG#*=}"
		;;

		--auth-token=*)
		AUTH_TOKEN="${ARG#*=}"
		;;

		--chunk-size=128MB)
		CHUNK_SIZE_SHORT=128MB
		CHUNK_SIZE=134217728
		;;

		--chunk-size=32MB)
		CHUNK_SIZE_SHORT=32MB
		CHUNK_SIZE=33554432
		;;

		--chunk-size=4MB)
		CHUNK_SIZE_SHORT=4MB
		CHUNK_SIZE=4194304
		;;

		--folder-slug=*)
		FOLDER_SLUG="${ARG#*=}"
		;;

		--check-crc)
		CHECK_CRC=1
		;;

		--verbose)
		VERBOSE=1
		;;

		--retry-slug=*)
		RETRY_SLUG="${ARG#*=}"
		;;

		--api-host=*)
		API_HOST="${ARG#*=}"
		;;

		*)
		FILES+=("$ARG")
		;;
	esac
done

if [[ "${FILES[*]}" == '' || "$LOGIN" == '' ]]; then
	echo "Usage ./upload_resumable.sh --login=LOGIN --auth-token=AUTH_TOKEN [--chunk-size=4MB|32MB|128MB] [--folder-slug=FOLDER_SLUG] [--check-crc] [--verbose] [--api-token=API_KEY] [--retry-slug=RETRY_SLUG] [--api-host=API_HOST] files..." >&2
	exit 2
fi

CHUNK_SIZE_SHORT=${CHUNK_SIZE_SHORT:-4MB}
CHUNK_SIZE=${CHUNK_SIZE:-4194304}
API_TOKEN=${API_TOKEN:-';HG%7jW6@6/8vx">R;f('}
RETRY_SLUG=${RETRY_SLUG:-''}
API_HOST=${API_HOST:-"apis.uloz.to"}
API_URL="https://$API_HOST"
RETRIES=5

AUTH_HEADER="X-Auth-Token: $API_TOKEN"
CONTENT_TYPE_HEADER="Content-type: application/json"
ACCEPT_HEADER="Accept: application/json"

if [[ "$RETRY_SLUG" != '' ]]; then
  IS_RERUN=1
fi

TEMPDIR=$(mktemp -d)
trap 'rm -rf $TEMPDIR' EXIT


echo "User $LOGIN authentication"

SESSION_REQUEST="{\"login\":\"$LOGIN\",\"token\":\"$AUTH_TOKEN\"}"
SESSION_URL="$API_URL/v5/auth/token"
RESPONSE_FILE="$TEMPDIR/response"

METHOD=POST
SESSION_RESPONSE_CODE=$(curl -s \
  -o "$RESPONSE_FILE" \
  -w "%{http_code}" \
	-X $METHOD \
	-H "$AUTH_HEADER" \
	-H "$CONTENT_TYPE_HEADER" \
	-H "$ACCEPT_HEADER" \
	-d "$SESSION_REQUEST" \
	"$SESSION_URL")
SESSION_RESPONSE=$(cat "$RESPONSE_FILE" 2> /dev/null)

print_response_verbose "$METHOD" "$SESSION_URL" "<hidden>" "$SESSION_RESPONSE_CODE" "<hidden>"

USER_TOKEN=$(parse_from_json "$SESSION_RESPONSE" token_id)

if [[ "$USER_TOKEN" == null || "$USER_TOKEN" == '' ]]; then
	echo 'User authentication failed' >&2
	exit 1
fi
USER_TOKEN_HEADER="X-User-Token: $USER_TOKEN"
echo "Authentication successful"

echo "Fetching upload link"
if [[ "$RETRY_SLUG" != '' ]]; then
  UPLOAD_LINK_REQUEST="{\"user_login\":\"$LOGIN\",\"private_slug\":\"$RETRY_SLUG\"}"
else
  UPLOAD_LINK_REQUEST="{\"user_login\":\"$LOGIN\"}"
fi
UPLOAD_LINK_REQUEST_URL="$API_URL/v6/upload/link"
METHOD=POST

UPLOAD_LINK_RESPONSE_CODE=$(curl -s \
  -o "$RESPONSE_FILE" \
  -w "%{http_code}" \
	-X $METHOD \
	-H "$AUTH_HEADER" \
	-H "$CONTENT_TYPE_HEADER" \
	-H "$ACCEPT_HEADER" \
	-H "$USER_TOKEN_HEADER" \
	-d "$UPLOAD_LINK_REQUEST" \
	"$UPLOAD_LINK_REQUEST_URL")
UPLOAD_LINK_RESPONSE=$(cat "$RESPONSE_FILE" 2> /dev/null)

print_response_verbose "$METHOD" "$UPLOAD_LINK_REQUEST_URL" "$UPLOAD_LINK_REQUEST" "$UPLOAD_LINK_RESPONSE_CODE" "$UPLOAD_LINK_RESPONSE"

UPLOAD_SIGNATURE=$(parse_from_json "$UPLOAD_LINK_RESPONSE" upload_signature)
BATCH_PRIVATE_SLUG=$(parse_from_json "$UPLOAD_LINK_RESPONSE" private_slug)
UPLOAD_URL=$(parse_from_json "$UPLOAD_LINK_RESPONSE" upload_resumable_base_url)

if [[ "$UPLOAD_SIGNATURE" == null || "$UPLOAD_SIGNATURE" == '' || "$BATCH_PRIVATE_SLUG" == null || "$BATCH_PRIVATE_SLUG" == '' ]]; then
	echo 'Upload link fetch failed' >&2
	exit 1
fi

if [[ "$RETRY_SLUG" != '' ]]; then
  echo "Updating upload validity"
  UPDATE_VALIDITY_REQUEST_URL="$UPLOAD_URL/v1/chunked-file-upload/$BATCH_PRIVATE_SLUG"
  METHOD=POST
  UPDATE_VALIDITY_REQUEST="{\"upload_signature\":\"$UPLOAD_SIGNATURE\"}"
  UPDATE_VALIDITY_RESPONSE_CODE=$(curl -s \
    -o "$RESPONSE_FILE" \
    -w "%{http_code}" \
    -X $METHOD \
    -H "$CONTENT_TYPE_HEADER" \
    -d "$UPDATE_VALIDITY_REQUEST" \
    "$UPDATE_VALIDITY_REQUEST_URL")
    UPDATE_VALIDITY_RESPONSE=$(cat "$RESPONSE_FILE" 2> /dev/null)

    print_response_verbose "$METHOD" "$UPDATE_VALIDITY_REQUEST_URL" "$UPDATE_VALIDITY_REQUEST" "$UPDATE_VALIDITY_RESPONSE_CODE" "$UPDATE_VALIDITY_RESPONSE"

    if [[ $UPDATE_VALIDITY_RESPONSE_CODE != 200 ]]; then
      echo "File upload validity update failed" >&2
      handle_interrupt
    fi
fi

echo "Upload link fetched"

if ((IS_RERUN)); then
  RETRY_COMMAND="$ORIGINAL_COMMAND"
else
  RETRY_COMMAND="$0 --retry-slug=\"$BATCH_PRIVATE_SLUG\"$QUOTED_ARGS"
fi


REGISTER_URL="$UPLOAD_URL/v1/chunked-file-upload"
echo "Uploading files through $REGISTER_URL"
BATCH_FILE_ID=1
for FILE in "${FILES[@]}"; do
  FILE_SIZE=$(wc -c < "$FILE")
  FILE_NAME=$(basename "$FILE")
  MISSING_CHUNKS=()

  REGISTRATION_NEEDED=1
  if ((IS_RERUN)); then
    echo "Getting file status"
    STATUS_URL="$UPLOAD_URL/v1/chunked-file-upload/$BATCH_PRIVATE_SLUG/$BATCH_FILE_ID/status"
    METHOD=GET
    STATUS_RESPONSE_CODE=$(curl -s \
      -o "$RESPONSE_FILE" \
      -w "%{http_code}" \
      -X $METHOD \
      "$STATUS_URL")
    STATUS_RESPONSE=$(cat "$RESPONSE_FILE" 2> /dev/null)

    print_response_verbose "$METHOD" "$STATUS_URL" "" "$STATUS_RESPONSE_CODE" "$STATUS_RESPONSE"

    STATUS_CODE=$(parse_from_json "$STATUS_RESPONSE" return_code)

    if [[ $STATUS_CODE == '' || $STATUS_CODE == null ]]; then
      echo "File status check failed" >&2
      handle_interrupt
    fi

    if [[ "$STATUS_CODE" != 400 ]]; then
      REGISTRATION_NEEDED=0
      MISSING_CHUNKS=($(parse_from_json "$STATUS_RESPONSE" "missing_chunks | .[]"))
    fi
  fi

  if ((REGISTRATION_NEEDED)); then
    if ((${CHECK_CRC:-0})); then
      CRC32=$(gzip -1 -c < "$FILE" | tail -c8 | od -t u4 -N 4 -A n | xargs)
    else
      CRC32=null
    fi

    REGISTER_REQUEST="{\"batch_file_id\": $BATCH_FILE_ID, \
    \"upload_signature\": \"$UPLOAD_SIGNATURE\", \
    \"name\": \"$FILE_NAME\", \
    \"filesize\": $FILE_SIZE, \
    \"chunksize\": $CHUNK_SIZE, \
    \"crc32\": $CRC32}"
    METHOD=POST
    REGISTER_RESPONSE_CODE=$(curl -s \
      -o "$RESPONSE_FILE" \
      -w "%{http_code}" \
      -X $METHOD \
      -H "$CONTENT_TYPE_HEADER" \
      -d "$REGISTER_REQUEST" \
      "$REGISTER_URL")
    REGISTER_RESPONSE=$(cat "$RESPONSE_FILE" 2> /dev/null)

    print_response_verbose "$METHOD" "$REGISTER_URL" "$REGISTER_REQUEST" "$REGISTER_RESPONSE_CODE" "$REGISTER_RESPONSE"

    if [[ $REGISTER_RESPONSE_CODE != 200 ]]; then
      echo "File upload registration failed" >&2
      handle_interrupt
    fi
    echo "Registered file $BATCH_FILE_ID \"$FILE_NAME\" for upload"
  fi

  echo "Uploading file $BATCH_FILE_ID \"$FILE_NAME\" using chunk size $CHUNK_SIZE_SHORT ($CHUNK_SIZE B)"
	CHUNK_COUNT=$((FILE_SIZE / CHUNK_SIZE + (FILE_SIZE % CHUNK_SIZE != 0)))
	CHUNK_FILE="$TEMPDIR/chunk"
	for ((SKIP=0;SKIP<CHUNK_COUNT;++SKIP)); do
	  CHUNK_ID=$((SKIP + 1))
	  if [[ $REGISTRATION_NEEDED != 1 && ! " ${MISSING_CHUNKS[*]} " =~ " $CHUNK_ID " ]]; then
	    continue
	  fi

    for ((TRY=1;TRY<=RETRIES;++TRY)); do
      echo "Uploading chunk $CHUNK_ID/$CHUNK_COUNT of file $BATCH_FILE_ID. Try $TRY/$RETRIES"
      dd if="$FILE" of="$CHUNK_FILE" bs="$CHUNK_SIZE" count=1 skip="$SKIP" 2> /dev/null
      CHUNK_UPLOAD_URL="$UPLOAD_URL/v1/chunked-file-upload/$BATCH_PRIVATE_SLUG/$BATCH_FILE_ID/$CHUNK_ID"
      METHOD=POST
      CHUNK_UPLOAD_RESPONSE_CODE=$(curl -s \
        -o "$RESPONSE_FILE" \
        -w "%{http_code}" \
        -X $METHOD \
        -H "Content-Type: application/octet-stream" \
        --data-binary "@$CHUNK_FILE" \
        "$CHUNK_UPLOAD_URL")
      CHUNK_UPLOAD_RESPONSE=$(cat "$RESPONSE_FILE" 2> /dev/null)

      print_response_verbose "$METHOD" "$CHUNK_UPLOAD_URL" "" "$CHUNK_UPLOAD_RESPONSE_CODE" "$CHUNK_UPLOAD_RESPONSE"

      if [[ $CHUNK_UPLOAD_RESPONSE_CODE == 201 ]]; then
        echo "Chunk $CHUNK_ID/$CHUNK_COUNT of file $BATCH_FILE_ID uploaded successfully"
        break
      fi
    done

    if ((TRY > RETRIES)); then
      echo "Chunk upload failed" >&2
      handle_interrupt
    fi
	done
	BATCH_FILE_ID=$((BATCH_FILE_ID + 1))
  sleep 1
done

echo "Waiting for uploads to finish"
FINISHED=()
FILE_URL_PARTS=()
for ((;;)); do
  INDEX=0
  DONE=1
  for FILE in "${FILES[@]}"; do
    if ((${FINISHED[$INDEX]})); then
      INDEX=$((INDEX + 1))
      continue
    fi

    BATCH_FILE_ID=$((INDEX + 1))
    STATUS_URL="$UPLOAD_URL/v1/chunked-file-upload/$BATCH_PRIVATE_SLUG/$BATCH_FILE_ID/status"
    METHOD=GET
    STATUS_RESPONSE_CODE=$(curl -s \
      -o "$RESPONSE_FILE" \
      -w "%{http_code}" \
      -X $METHOD \
      "$STATUS_URL")
    STATUS_RESPONSE=$(cat "$RESPONSE_FILE" 2> /dev/null)

    print_response_verbose "$METHOD" "$STATUS_URL" "" "$STATUS_RESPONSE_CODE" "$STATUS_RESPONSE"

    STATUS=$(parse_from_json "$STATUS_RESPONSE" status)

    if [[ $STATUS == '' || $STATUS == null ]]; then
      echo "File status check failed" >&2
      handle_interrupt
    fi

    echo "File $BATCH_FILE_ID, Status: $STATUS"

    if [[ "$STATUS" = finished ]]; then
      FINISHED[$INDEX]=1
      FILE_SLUG=$(parse_from_json "$STATUS_RESPONSE" irpc.slug)
      if [[ "$FILE_SLUGS" != "" ]]; then
		    FILE_SLUGS="$FILE_SLUGS,"
	    fi
	    if [[ "$UPLOAD_TOKENS" != "" ]]; then
		    UPLOAD_TOKENS="$UPLOAD_TOKENS,"
	    fi
      FILE_NAME=$(basename "$FILE")
      FILE_URL_PARTS+=("$FILE_SLUG/$FILE_NAME")
	    FILE_SLUGS="$FILE_SLUGS\"$FILE_SLUG\""
	    UPLOAD_TOKENS="$UPLOAD_TOKENS\"$FILE_SLUG\":\"$BATCH_PRIVATE_SLUG:$BATCH_FILE_ID\""
    else
      DONE=0
    fi
    INDEX=$((INDEX + 1))
    sleep 2
  done
  if ((DONE)); then
    break
  fi
done

echo "Setting file properties"

if [[ "$FOLDER_SLUG" == "" ]]; then
  USER_DATA_URL="$API_URL/v5/me"
  METHOD=GET
	USER_DATA_RESPONSE_CODE=$(curl -s \
    -o "$RESPONSE_FILE" \
    -w "%{http_code}" \
	 	-X $METHOD \
	 	-H "$AUTH_HEADER" \
	 	-H "$USER_TOKEN_HEADER" \
	 	"$USER_DATA_URL")
  USER_DATA=$(cat "$RESPONSE_FILE" 2> /dev/null)

  print_response_verbose "$METHOD" "$USER_DATA_URL" "" "$USER_DATA_RESPONSE_CODE" "$USER_DATA"

	FOLDER_SLUG=$(parse_from_json "$USER_DATA" user.root_folder_slug)
	if [[ "$FOLDER_SLUG" == '' || "$FOLDER_SLUG" == null ]]; then
		echo "Retrieving root folder failed" >&2
		handle_interrupt
	fi
fi

PROPERTIES_REQUEST="{\"slugs\":[$FILE_SLUGS],\"upload_tokens\":{$UPLOAD_TOKENS},\"folder_slug\":\"$FOLDER_SLUG\"}"
PROPERTIES_URL="$API_URL/v8/file-list/private"
METHOD=PATCH
SET_PROPERTIES_RESPONSE_CODE=$(curl -s \
  -o "$RESPONSE_FILE" \
  -w "%{http_code}" \
	-X $METHOD \
	-H "$AUTH_HEADER" \
	-H "$CONTENT_TYPE_HEADER" \
	-H "$ACCEPT_HEADER" \
	-H "$USER_TOKEN_HEADER" \
	-d "$PROPERTIES_REQUEST" \
	"$PROPERTIES_URL")
SET_PROPERTIES_RESPONSE=$(cat "$RESPONSE_FILE" 2> /dev/null)

print_response_verbose "$METHOD" "$PROPERTIES_URL" "$PROPERTIES_REQUEST" "$SET_PROPERTIES_RESPONSE_CODE" "$SET_PROPERTIES_RESPONSE"

if [[ $SET_PROPERTIES_RESPONSE_CODE != 200 ]]; then
	echo 'Set files properties failed' >&2
	handle_interrupt
fi
echo "File properties set"

echo "Committing files"
COMMIT_BATCH_REQUEST="{\"status\":\"confirmed\",\"owner_login\":\"$LOGIN\"}"
COMMIT_URL="$API_URL/v8/upload-batch/private/$BATCH_PRIVATE_SLUG"
METHOD=PATCH
COMMIT_RESPONSE_CODE=$(curl -s \
  -o "$RESPONSE_FILE" \
  -w "%{http_code}" \
	-X $METHOD \
	-H "$AUTH_HEADER" \
	-H "$CONTENT_TYPE_HEADER" \
	-H "$ACCEPT_HEADER" \
	-H "$USER_TOKEN_HEADER" \
	-d "$COMMIT_BATCH_REQUEST" \
	"$COMMIT_URL")
COMMIT_RESPONSE=$(cat "$RESPONSE_FILE" 2> /dev/null)

print_response_verbose "$METHOD" "$COMMIT_URL" "$COMMIT_BATCH_REQUEST" "$COMMIT_RESPONSE_CODE" "$COMMIT_RESPONSE"

if [[ $COMMIT_RESPONSE_CODE != 200 ]]; then
	echo 'Upload batch commit failed' >&2
	handle_interrupt
fi

echo "Files commited:"
SITE="https://${API_HOST/apis./}/file"
for URL_PART in "${FILE_URL_PARTS[@]}"; do
  echo "$SITE/$URL_PART"
done
