#!/usr/bin/env bash

function docker2lxc() {
  local INTERACTIVE=
  local EXPORT_INVOCATION=
  local IMAGE_FOUND=
  local QUALIFIER=
  local SSH=
  local info=
  local answer=
  local REQUEST_HELP=
  local USAGE_SHOWN=
  local DOCKER_NOT_AVAILABLE=
  local docker_container=
  local output_file=
  local INCOMPLETE_1=
  local INCOMPLETE_2=
  local INCOMPLETE_3=
  
  if [ "${DEBUG@L}" = 'dev' ]; then
    set -x
  fi
  # local -i p_test=$(test -p "/dev/fd/1"; echo $?)
  # local -i f_test=$(test -f "/dev/fd/1"; echo $?)
  
  if test -p "/dev/fd/1" || test -f "/dev/fd/1"; then
    if [ "$INVOKE" != '1' ] && [ "${INVOKE@L}" != 'true' ]; then
      # export the command and invoke the exported implementation
      # if you are confused about this, please check the POC I built
      # and thoroughly tested in branch named `noninteractive-export`
      EXPORT_INVOCATION="$(declare -f docker2lxc); INVOKE=1 "
      if [ "$DEBUG" = '1' ] || [ "${DEBUG@L}" = 'true' ]; then
        EXPORT_INVOCATION+="DEBUG=1 "
      fi
      if [ "${DEBUG@L}" = 'dev' ]; then
        EXPORT_INVOCATION+="DEBUG='DEV' "
      fi
      EXPORT_INVOCATION+="docker2lxc"
    fi
  else
    INTERACTIVE=1
  fi

  if ps -o comm= $PPID | grep -q sshd; then SSH=1; fi

  QUALIFIER=${SSH:+remote}
  QUALIFIER=${QUALIFIER:-local}

  # printf >&2 "DEBUG: SSH=%s\n" "$SSH"
  # printf >&2 "DEBUG: INVOKE=%s\n" "$INVOKE"
  # printf >&2 "DEBUG: EXPORT_INVOCATION=%s\n" "$EXPORT_INVOCATION"
  # printf >&2 "DEBUG: ACTUAL_INVOCATION=%s\n" "$ACTUAL_INVOCATION"
  # printf >&2 "DEBUG: INTERACTIVE=%s\n" "$INTERACTIVE"
  # printf >&2 "DEBUG: -p /dev/fd/1=%s\n" "$(test -p "/dev/fd/1" && echo 1 || echo 0)"
  # printf >&2 "DEBUG: -f /dev/fd/1=%s\n" "$(test -f "/dev/fd/1" && echo 1 || echo 0)"

  # check if docker is installed, needed in 2/3 cases
  if ! command -v docker &>/dev/null; then
    # Docker is needed in most cases, except the case of generating
    # an invocation/eval string in a 2-step SSH usage scenario.
    DOCKER_NOT_AVAILABLE=1
    if [[ -z "$EXPORT_INVOCATION" ]]; then
      printf >&2 "\e[31m%s\e[0m\n" \
        "Docker not found on $QUALIFIER machine, aborting..."
    fi
  fi

  # check if an archive is provided, needed in 1/3 cases, only useful to dig
  # into if the other conditions are met for this one third case.
  # note that what comes out of this step is a non-empty $output_file variable
  if [[ -n "$INTERACTIVE" && -z "$DOCKER_NOT_AVAILABLE" && -n "$2" ]]; then
    output_file="${output_file%.tar.gz}.tar.gz"
  fi

  if [[ -n "$output_file" && -a "$output_file" ]]; then
    printf >&2 "\e[31;1m%s '%s' %s \e[2m" "Archive" "$2" "already exists, what to do?" 
    printf >&2 "\e[33m[\e[22;4m%c\e[24;2m%s/\e[33;22;4m%c\e[24;2m%s/\e[22;4m%c\e[24m%s\e[2m]\e[0m" \
      'O' "verwrite" 'R' "ename" 'A' "bort"
    read answer
    echo >&2

    case "$answer" in
    [Oo]*)
      # do nothing, still need this section to force the *) section to function
      # correctly as "neither overwrite nor rename"
      ;;
    [Rr]*)
      output_file=${output_file%.tar.gz}
      local -i counter=2
      until [[ ! -a "${output_file}-$counter.tar.gz" ]]; do
        ((counter++))
      done
      output_file="${output_file}-$counter.tar.gz"
      ;;
    *)
      # clear the $output_file variable resulting in aborting the operation
      output_file=
      ;;
    esac
  fi

  # check if a sufficiently-specified image is given, this is needed
  # in all 3/3 cases regardless of usage scenario
  if [[ -n "$1" && ! "$1" =~ ^'-' && ! "$1" =~ ^'/' ]]; then 
    # even though the sufficiently-specified image is needed in all
    # cases, it is only possible to verify its specifiedness using
    # docker, hence, we restrict the folowing check to cases 1/3 and
    # 3/3 which means the interactive case and the case when it is
    # not an eval-code-spitting non-interactive call
    if [[ -z "$DOCKER_NOT_AVAILABLE" && \
      ( -n "$INTERACTIVE" || -z "$EXPORT_INVOCATION" ) ]]; then
      # search for the image locally, the advantage of doing this in a
      # separate step is being able to use interactive operation to ask
      # for consent regarding pulling a new image. if launched in a way
      # that is non-interactive, then we abort and ask the user to pull
      # the image first.
      info=( $(docker image ls --format \
        "{{.Repository}}:{{.Tag}} {{.ID}}" "$1" | head -n 1) )

      if [[ -n "${info[*]}" && ( "$1" = "${info[0]}" || "$1:latest" = "${info[0]}" ) ]]; then
        IMAGE_FOUND=${info[1]}
        if [[ -n "$INTERACTIVE" && -n "$output_file" ]]; then
          printf >&2 "\e[33;2m%s\e[0m\n" "Image '${info[0]}' found locally..."
        fi
      else
        # if we are running interactively, and there is no archive name provided
        # then it would make sense to skip the docker pulling, knowing that an
        # abort is lying ahead.
        if [[ ( -n "$INTERACTIVE" && -n "$output_file" ) || -z "$INTERACTIVE" ]]; then
          printf >&2 "\e[33m%s\e[4m%s\e[24m%s [Y/n] \e[0m" \
            "Docker image not found ${QUALIFIER}ly, do we run '" "docker pull $1" "'?" 
          read answer
          echo >&2

          case "$answer" in
          [Nn]*)
            IMAGE_FOUND=
            ;;
          *)
            if docker pull --quiet "$1" &>/dev/null; then 
              IMAGE_FOUND=$(docker image ls --format "{{.ID}}" "$1")
            fi
            ;;
          esac
        fi
      fi
    else
      # this is the case 2/3 and we just accept $2 as $container_image without
      # asking further questions
      IMAGE_FOUND="$1"
    fi
  fi

  # check for the case when the command is calld specifically for instructions
  if [[ "$1" =~ ^'-h' || "$1" = '--help' || "$1" = '/?' || "$1" =~ ^'/h' ]]; then
    REQUEST_HELP=1
  fi

  # if asking for help or incomplete invocation for case 1/3 (local usage scenario)
  if [[ -n "$INTERACTIVE" && \
      ( -n "$DOCKER_NOT_AVAILABLE" || -z "$IMAGE_FOUND" || -z "$output_file" ) ]]; then
      INCOMPLETE_1="1"; fi

  if [[ -n "$REQUEST_HELP" || -n "$INCOMPLETE_1" ]]; then
    printf >&2 "\e[34;7;1m %s%s❯\e[27m \e[2m%s\e[0m \e[33;4m%s\e[24m:\e[4m%s\e[24m \e[4m%s\e[0m\n" \
      "Usage" "${REQUEST_HELP:+     }" "docker2lxc" "image" "tag" "template.tar.gz"
    USAGE_SHOWN=1
  fi

  # if asking for help or incomplete invocation for cases 2/3 and 3/3:
  # case 2/3: first phase of an SSH invocation (indicated by -n "$EXPORT_INVOCATION")
  # case 3/3: second phase of an SSH invocation (indicated by -z "$EXPORT_INVOCATION")
  if [[ -z "$INTERACTIVE" && -z "$EXPORT_INVOCATION" && \
      ( -n "$DOCKER_NOT_AVAILABLE" || -z "$IMAGE_FOUND" ) ]]; then
    INCOMPLETE_3="1"; fi
  if [[ -z "$INTERACTIVE" && -n "$EXPORT_INVOCATION" && -z "$IMAGE_FOUND" ]]; then
    INCOMPLETE_2="1"; fi

  if [[ -n "$REQUEST_HELP" || -n "$INCOMPLETE_2" || -n "$INCOMPLETE_3" ]]; then
    printf >&2 "\e[35;7;1m %s ❯\e[27m %s \e[2m\"\$(%s\e[0m \e[33;4m%s\e[24m:\e[4m%s\e[0;35;2;1m)\" > \e[33;22;4m%s\e[0m\n" \
      "SSH Usage" "ssh remote" "docker2lxc" "image" "tag" "template.tar.gz"
    USAGE_SHOWN=1
  fi

  # The showing of help indicates either that this was just a help call or that
  # it is an incomplete invocation of either usage scenario, in which case we
  # can just return, in which case we can differentiate between the two if we desire
  if [ -n "$USAGE_SHOWN" ]; then 
    if [ -n "$REQUEST_HELP" ]; then
      set +x
      return 0
    else
      # printf >&2 "DEBUG=%s\n" "$DEBUG"
      if [ "$DEBUG" = '1' ] || [ "${DEBUG@L}" = 'true' ] || [ "${DEBUG@L}" = 'dev' ]; then
        if [ "${DEBUG@L}" = 'dev' ]; then
          printf >&2 "DEBUG: HOST=%s\n" "$(hostname) (as $(whoami))"
          printf >&2 "DEBUG: SSH=%s\n" "$SSH"
          printf >&2 "DEBUG: INVOKE=%s\n" "$INVOKE"
          printf >&2 "DEBUG: EXPORT_INVOCATION=%s\n" "${EXPORT_INVOCATION:0:32}..."
          printf >&2 "DEBUG: DOCKER_NOT_AVAILABLE=%s\n" "$DOCKER_NOT_AVAILABLE"
          printf >&2 "DEBUG: IMAGE_FOUND=%s\n" "$IMAGE_FOUND"
          printf >&2 "DEBUG: ARCHIVE_FILE=%s\n" "$output_file"
          printf >&2 "DEBUG: INCOMPLETE_1=%s\n" "$INCOMPLETE_1"
          printf >&2 "DEBUG: INCOMPLETE_2=%s\n" "$INCOMPLETE_2"
          printf >&2 "DEBUG: INCOMPLETE_3=%s\n" "$INCOMPLETE_3"
        fi

        if   [[ -n "$INCOMPLETE_1" ]]; then
          # interactive operation, error messages must have been emitted
          printf >&2 "\e[36;7;1m%s" \
            "THE LOCAL CALL FAILED, "
        elif [[ -n "$INCOMPLETE_2" ]]; then
          # non-interactive operation, here more messages can help
          printf >&2 "\e[36;7;2;1m%s" \
            "THE LOCAL SSH CALL FAILED, "
          if [[ -z "$IMAGE_FOUND" ]]; then
            printf >&2 "%s" \
              "THE DOCKER IMAGE IS OMITTED"
          fi
          printf >&2 ".\e[0m\n"
          # in this particular case, we need to return an eval string to
          # substitute the one in $EXPORT_INVOCATION, otherwise the user
          # will end up SSHing into the machine using the SSH syntax.
          # a colon ":" is an effective noop (no operation) in the shell.
          echo >&1 ":"
        elif [[ -n "$INCOMPLETE_3" ]]; then
          # non-interactive operation, here more messages can help
          printf >&2 "\e[36;7;2;1m%s" \
            "THE REMOTE SSH EXECUTION FAILED, "
          if   [[ -n "$DOCKER_NOT_AVAILABLE" ]]; then
            printf >&2 "%s" \
              "DOCKER WAS NOT FOUND"
          elif [[ -z "$IMAGE_FOUND" ]]; then
            printf >&2 "%s" \
              "THE DOCKER IMAGE ISN'T CORRECT OR WASN'T FETCHED"
          fi
          printf >&2 ".\e[0m\n"
        fi
      fi

      set +x
      return 1
    fi
  fi

  # terminate the call in the case 2/3 (the first stage of a a two-phase invocation)
  # note that we can only do that if the call was complete
  if [ -n "$EXPORT_INVOCATION" ]; then
    if [ -z "$INCOMPLETE_2" ]; then
      # very important not to forget to pass the image found as the argument to the
      # second-stage calling of the cli tool (which happens on the remote)
      echo >&1 "$EXPORT_INVOCATION $IMAGE_FOUND"
      set +x
      return 0
    else
      set +x
      return 1
    fi
  fi

  # Extend to building Dockerfiles in the future using <image|Dockerfile>
  # if [ -f "$1" ]; then
  #   echo "found Dockerfile"
  #   return 1
  # fi

  echo >&2 -e "\e[33m-> Pulling Docker container '$1'...\e[0m"
  docker pull $1 >&2
  if [[ $? -ne 0 ]]; then 
    echo >&2 "\e[31m  Docker pull of image '$1' failed, aborting\e[0m"
    set +x
    return 2
  fi

  image_id=$(docker image ls --format="{{.ID}}" "$1" | head -n 1)
  image_repo=$(docker image ls --format="{{.Repository}}" "$1" | head -n 1)
  image_id=$(echo "${image_id@L}" | grep --only-matching -E '^[0-9a-f]{10,}')
  if [[ $? -ne 0 || -z "$image_id" ]]; then 
    echo >&2 "\e[31m  Image's ID couldn't be determined, aborting\e[0m"
    set +x
    return 3
  fi

  # this is to implement a future clean protocol
  docker tag $image_id "$image_repo:docker2lxc"

  # - create the container without running it (improved based on feedback from
  #   @AugustD on the Proxmox Forums in thread docker-to-pve-lxc-conversion-steps-tool
  # docker_container=$(docker run --rm --entrypoint sh -id $1)

  docker_container=$(docker create $1)
  # - deprecated but might still be desired
  # if [[ $? -ne 0 ]]; then 
  #   echo >&2 "\e[31m  Incompatible container '$1' detected, aborting\e[0m"
  #   return 3
  # fi
  docker_container=${docker_container:0:12}
  if [ -z "$SSH" ]; then
    output_file=${2:-template}
    output_file=${output_file%.tar.gz}.tar.gz
  fi
  
  echo >&2 -e "\e[33m-> Exporting root filesystem to '${output_file:-stdout}'...\e[0m"
  if [ -z "$output_file" ]; then
    # here comes the real action, let us not fuck up the dude's terminal
    printf >&2 "\e[33;4m%s \e[24;7m%s\e[27m? [y/N] \e[0m" \
            "Did you forward the output to a file using" " >template.tar.gz " 
    read answer
    echo >&2

    case "$answer" in
    [Yy]*)
      # finally 
      docker export $docker_container | gzip >&1
      ;;
    *)
      printf >&2 "\e[32;1m%s\e[0m\n" \
        "Glad not to mess up your terminal, then 😃. Fix that and call me again."
      set +x
      return 4
      ;;
    esac
    
  else 
    docker export $docker_container | gzip > $output_file
  fi
  
  echo >&2 -e "\e[33m-> Cleaning up...\e[0m"
  docker rm $docker_container >/dev/null
  
  echo >&2 -e "\e[32;1mDone\e[0m"
}

docker2lxc $*