#!/usr/bin/env bash

# Usage: p42 cluster [host|unhost|create|add|stop|ls|env] [<option>...]
# Summary: Create/manage a cluster (a Docker Swarm).
# Help: To find out about a specific subcommand:
#
#    p42 cluster help <command>
#

action="$1"
shift

# test SSH
# ssh -i ~/.docker/machine/certs/cert.pem ubuntu@<IP-of-your-instance> -v

get_security_group() {
  local vpc name
  local "${@}"
  aws ec2 describe-security-groups \
    --filters \
      "Name=vpc-id,Values=${vpc}" \
      "Name=group-name,Values=${name}" |\
    json SecurityGroups[0].GroupId
}

create_instance() {
  local name region zone vpc subnet options
  local "${@}"
  docker-machine create ${name} \
    --driver amazonec2 \
    --amazonec2-region ${region} \
    --amazonec2-vpc-id ${vpc} \
    --amazonec2-subnet-id ${subnet} \
    --amazonec2-zone ${zone} \
    ${options}
}

create_swarm_node() {

  local name master token region zone vpc subnet groups
  local "${@}"

  create_instance \
    name=${name} \
    region=${region} \
    zone=${zone} \
    vpc=${vpc} \
    subnet=${subnet} \
    options="--swarm \
      ${master+--swarm-master} \
      --swarm-discovery ${token}"

  # We need to add the VPC default SG


  # get the instance ID of the newly created machine
  id=$(aws ec2 describe-instances \
    --filters "Name=tag-value,Values=${name}" |\
    json 'Reservations[0].Instances[0].InstanceId')

  # add the default VPC security group
  aws ec2 modify-instance-attribute \
    --instance-id ${id} \
    --groups \
      $(get_security_group vpc=${vpc} name=default) \
      $(get_security_group vpc=${vpc} name=docker-machine)
}

case "$action" in

  create)

    name=$(p42 name)

    echo Creating VPC [${name}]...
    tmpDir=$(mktemp -d "${TMPDIR:-/tmp}/p42.XXXXXXXXX")

    # Originally, I was going to templatize the file,
    # but I'm no longer sure that's necessary...
    yaml json write $_P42_ROOT/share/cf/vpc.yaml > ${tmpDir}/vpc.json

    aws cloudformation create-stack \
      --stack-name ${name} \
      --template-body file:///${tmpDir}/vpc.json \
      > /dev/null

    while true; do
      sleep 5
      description=$(aws cloudformation describe-stacks --stack-name ${name})
      status=$(json Stacks[0].StackStatus <<<"${description}")
      if [ "$status" == "CREATE_COMPLETE" ]; then
        break
      fi
    done

    echo VPC [${name}] created.

    vpc=$(json Stacks[0].Outputs[0].OutputValue <<<"${description}")
    subnet=$(json Stacks[0].Outputs[1].OutputValue <<<"${description}")
    az=$(json Stacks[0].Outputs[2].OutputValue <<<"${description}")
    region="${az%?}"
    zone="${az: -1}"
    dns=$(json Stacks[0].Outputs[3].OutputValue <<<"${description}")
    echo Creating Docker host...

    create_instance \
      name=${name} \
      region=${region} \
      zone=${zone} \
      vpc=${vpc} \
      subnet=${subnet}

    cat > "${clusters}/${name}" <<EOF
name: ${name}
vpc: ${vpc}
subnet: ${subnet}
region: ${region}
zone: ${zone}
dns: ${dns}
EOF

    echo 'Creating Swarm master...'

    eval "$(docker-machine env ${name})"

    echo Generating token...
    token=$(docker run swarm create)

    echo "Creating Swarm Master with token://${token}..."

    create_swarm_node \
      name=${name}-00 \
      master=true \
      token="token://${token}" \
      region=${region} \
      zone=${zone} \
      vpc=${vpc} \
      subnet=${subnet}

    ;;

  expand)

    source "${lib}/context.sh"
    name=${1:-${cluster}}
    shift
    if [ ! -e "${clusters}/${name}" ]; then
      echo p42: Invalid cluster: ${name}
      exit -1
    fi

    # process options...
    while [ $# -gt 0 ]; do
      case $1 in
        -n)
          n=$2
          shift; shift
          ;;
        *)
          # ignore
          shift
          ;;
      esac
    done

    i=${n:-1}
    token=$(docker-machine inspect ${name}-00 \
      -f '{{ .HostOptions.SwarmOptions.Discovery }}')
    echo "Adding $i node(s) to cluster [${name}]..."

    region=$(yaml get ${clusters}/${name} region)
    zone=$(yaml get ${clusters}/${name} zone)
    vpc=$(yaml get ${clusters}/${name} vpc)
    subnet=$(yaml get ${clusters}/${name} subnet)

    while [ $i -gt 0 ]; do
      i=$[$i-1]
      candidates=$(echo ${name}-{0..9}{0..9})
      for _name in $candidates; do
        docker-machine inspect $_name &> /dev/null
        if [ $? -ne 0 ]; then

          echo "Creating Swarm Node '${_name}' with '${token}'..."
          create_swarm_node \
            name=${_name} \
            token=${token} \
            region=${region} \
            zone=${zone} \
            vpc=${vpc} \
            subnet=${subnet}

          break
        fi
      done
    done

    ;;

  contract)
    echo 'Not implemented yet.'
    exit -1
    ;;

  rm)

    name=$1
    shift
    if [ ! -e "${clusters}/${name}" ]; then
      echo p42: Invalid cluster: ${name}
      exit -1
    fi

    echo Stopping Swarm...
    machines=$(docker-machine ls \
      --format '{{ .Name }}' \
      --filter "name=${name}")
    docker-machine stop $machines
    docker-machine rm $machines

    echo Deleting VPC...
    aws cloudformation delete-stack --stack-name ${name}
    rm ${clusters}/${name}
    ;;

  ls)
    ls "${clusters}"
    ;;

  ps)
    name=$1
    shift
    if [ -z $name ]; then
      source $
      name=$cluster
    fi

    if [ -f "${clusters}/${name}" ]; then
      docker-machine ls --filter "name=${name}"
    else
      echo "p42: cluster '${name}' does not exist."
    fi
    ;;

  env)

    name=$1
    shift
    if [ ! -e "${clusters}/${name}" ]; then
      echo p42: Invalid cluster: ${name}
      exit -1
    fi

    docker-machine env --swarm "${name}-00"

    ;;

  get)
    name="${1}"
    shift
    if [ ! -e "${clusters}/${name}" ]; then
      echo p42: Invalid cluster: ${name}
      exit -1
    fi
    property="${1}"

    yaml get "${clusters}/${name}" "${property}"
    ;;

  help)
    topic="$1"
    if [ ! $topic ]; then
      exec $_P42_ROOT/libexec/p42-help cluster
    fi

    case "$topic" in

      create)
        cat <<EOF
Usage: p42 cluster create
Create a VPC, bootstrap Docker Host, and a master Swarm node.
EOF
        ;;
      add)
        cat <<EOF
Usage: p42 cluster add <name> [-n <size>]
Add a node to the cluster.

You can add multiple nodes using -n.
To add 3 nodes to the cluster named "red-ghost":

    p42 cluster add red-ghost -n 3
EOF
        ;;
      stop)
        cat <<EOF
Usage: p42 cluster stop <name>
Stop a given cluster (Swarm) and remove the associated machines.
EOF
        ;;
      ls)
        cat <<EOF
Usage: p42 cluster ls <name>
List all the machines in a cluster.
EOF
        ;;
      env)
        cat <<'EOF'
Usage: p42 cluster env <name>
Set up your environment to use Docker with your cluster.

    eval $(p42 cluster env red-ghost)
EOF
        ;;
      *)
        echo "'$topic' is not a valid subcommand"
        exec $_P42_ROOT/libexec/p42-help cluster
    esac
    ;;

  *)
    exec $_P42_ROOT/libexec/p42-help cluster
    ;;

esac
