#!/bin/sh

# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

. "$(dirname "$0")/common.sh" || exit 1

# A local die function to print the message and then exit
die() {
  echo -e "$@"
  exit 1
}

# Assert inside chroot
assert_inside_chroot

# Read command flags
. /usr/share/misc/shflags
DEFINE_string board '' 'which board the firmware is for' 'b'
DEFINE_boolean clean false 'clean up all temporary files' 'c'
DEFINE_boolean debug false 'debug flag' 'd'
DEFINE_string infile '' 'the input (zip) file' 'f'
DEFINE_string issue '' 'the issue number on the issue tracker' 'i'
DEFINE_string revision '' 'the revision number of the firmware' 'r'

PROG=$0
FLAGS_HELP="USAGE: $PROG [flags]"

FLAGS "$@" || exit 1
eval set -- "${FLAGS_ARGV}"
set -e

# Set global variables
infile="$FLAGS_infile"
FW_PATH="opt/google/touchpad/firmware"
FW_MODE=755
FW_OWN="root:root"
TMP_DIR="/tmp/touch_fw"
TMP_RESULT="${TMP_DIR}/login.result"
COOKIE_FILE="${TMP_DIR}/login.cookie"

# Some URLs to set up credentials
AUTH_URL="https://accounts.google.com/ServiceLoginAuth?hl=en&continue="
TRACKER_URL="https%3A%2F%2Fcode.google.com%2Fp%2Fchrome-os-partner"\
"%2Fissues%2Fdetail%3Fid%3D"
issue_auth_url="$AUTH_URL$TRACKER_URL$FLAGS_issue"

# Per board definitions
# Only works for lumpy/cypress for now.
# TODO: work on other boards too.
FW_UPDATE_TOOL_CYPRESS="/sbin/cyapa_fw_update"
CYPRESS_TOOLS="cypress-tools"

# Print the message when debug flag is set.
debug_print() {
  if [ $FLAGS_debug = "$FLAGS_TRUE" ]; then
    local name=$1
    local value=${!name}
    echo "    (DEBUG) $name: $value"
  fi
}

# Print the usage and examples, and then exit.
usage_and_exit() {
  flags_help
  die "\nExamples:
  Specify the issue # and download the firmware from the remote server.
  \$ $PROG -i 12345 -b lumpy \n
  Convert the iic file in a local zip file to bin file.
  \$ $PROG -f /tmp/CYTRA-116001-00-v11.26-Testdrop.zip -b lumpy \n
  Specify that it is revision 'r2' and also enable the debug flag.
  \$ $PROG -i 12345 -b lumpy -r r2 -d \n
  Clean up all temporary files after finished.
  \$ $PROG -c \n"
}

# Make sure that exactly an issue number or an local zip file is specified.
check_argument() {
  if [ -z "$FLAGS_issue" -a -z "$FLAGS_infile" -o \
       "$FLAGS_issue" -a "$FLAGS_infile" ]; then
    echo -e "Specify exactly either an issue number or a local zip file.\n"
    usage_and_exit
  elif [ -z "$FLAGS_board" ]; then
    echo -e "You need to specify a board.\n"
    usage_and_exit
  fi
}

# Set up a temporary directory that only the owner could access.
setup_tmp_dir() {
  mkdir -p -m 0700 $TMP_DIR
}

# Prompt the user to type chromium email account and password.
read_user_and_password() {
  # Read the user email address and password if not yet.
  if [ -z $USR -o -z $PASS ]; then
    echo Please type your user name of chromium account.
    read -p "Username (without @chromium.org): " USR
    read -s -p "Password: " PASS
  fi
  USR_PASS="Email=${USR}@chromium.org&Passwd=${PASS}"
}

# Get dsh and GALX
get_credential() {
  # Try to get dsh and GALX values.
  if ! curl -s -c "$COOKIE_FILE" -L -d $USR_PASS $issue_auth_url > \
      "$TMP_RESULT"; then
    die "Error: curl failed to get $issue_auth_url"
  fi

  # The dsh value looks like
  #    name="dsh" id="dsh" value="3039562716843903054"
  dsh=`grep -i "dsh" "$TMP_RESULT" | \
      sed -n "s/.*value=\"\(-*[0-9]*\)\"/\1/p"`

  # The GALX value looks like
  #          name="GALX"
  #          value="Bfr17SQrDe4">
  galx=`grep -i -A1 "GALX" "$TMP_RESULT" | \
      sed -n "s/.*value=\"\(.*\)\".*/\1/p"`

  if [ -z dsh -o -z galx ]; then
    die "Error: fail to get dsh or galx."
  fi
}

# Get the firmware attachment URL from the specified tracker issue.
# And then download the firmware zip file.
download_fw_file() {
  # The returned attachment syntax looks like
  #   &nbsp; <a href="//chrome-os-partner.googlecode.com/issues/attachment?
  #   aid=172300000000&amp;name=CYTRA-116001-00-v11.27-Testdrop.zip&amp;
  #   token=E9jFSVjVX9abUmyCrGm0ms0YuBI%3A1359083647918">Download</a>
  local attach_str=`curl -s -b "$COOKIE_FILE" -L -d \
      "GALX=${galx}&bgresponse=js_disabled&dsh=${dsh}&$USR_PASS" \
      $issue_auth_url | \
      grep "href=.*CYTRA.*\.zip.*Download" | \
      sed -n 's/.*href=\"\(.*\)\">Download.*/\1/p'`
  attachment_url="https:"`echo $attach_str | sed 's/\&amp;/\&/g'`

  fw_zipfile="${TMP_DIR}/`echo $attachment_url | \
      sed -n 's/http.*name=\(CYTRA.*\.zip\)\&token=.*/\1/p'`"

  if [ "$attachment_url" = "https:" -a "$fw_zipfile" = "${TMP_DIR}/" ]; then
    die "\n Error: either the firmware url or the firmware zip name
                   cannot be found
         \n firmware url: $attachment_url
         \n firmware name: $fw_zipfile"
  else
    echo Downloading the firmware: $fw_zipfile
    curl -s -o "$fw_zipfile" "$attachment_url"
  fi
}

# Check the existence of the file
check_file_existence() {
  local target_file="$1"
  if [ ! -f "$target_file" ]; then
    die "File does not exist: $target_file"
  fi
}

# Get the firmware version from infile.
get_fw_version() {
  # The following command will extract the firmware version number from a
  # firmware zip file.
  #
  #     "CYTRA-116001-00-v11.26-Testdrop.zip" => 11.26
  #     "CYTR-116001-00-V11.2.6.zip"          => 11.2.6
  #     "CYTRA-116001-00-v27.zip"             => 27
  #     "CYTRA_103002_00 FW_V1.9.zip.zip"     => 1.9
  #
  # Note:
  #   A in "CYTRA" is optional
  #   Either v or V is acceptable
  #   The firmware version could be something like 11, or 11.26, or 11.2.5, etc.
  fw_version=`echo $infile | sed -nr 's/.*CYTRA?.*v([0-9]+[\.0-9]*).*/\1/pi'`
  if [ -z $fw_version ]; then
    die "Failed to extract the firmware version number from $infile"
  fi
  echo Firmware version: $fw_version
}

# Get the correct firmware .bin file name.
# It converts any incorrect dashes and underscores to the correct format.
#
#   Example: it makes the following name conversion
#            from ".../CYTRA_116001_00_v11.27.iic"
#            to   ".../CYTRA-116001-00_11.27.bin"
#
#   Note that the correct format of a bin file should look like
#       ${product_id}_${version_number}.bin
#       where $product_id is "CYTRA-116001-00", and
#             $version_number is "11.27"
#       Also note that there is a '_" between $product_id and $version_number.
get_fw_bin_name() {
  sed_script="s/(.*)(CYTRA?)[-_]([0-9]+)[-_]([0-9]+)[-_].*\.iic"\
"/\1\2-\3-\4_${fw_version}.bin/pi"
  bin_name=`echo $1 | sed -nr $sed_script`
}

# Convert an iic file to a bin file.
convert_iic_to_bin() {
  local iic_file="${TMP_DIR}/$1"
  get_fw_bin_name $iic_file
  test "$FLAGS_revision" && rev="-$FLAGS_revision"
  # A top_dir looks like "chromeos-touch-firmware-lumpy-11.27-r1"
  top_dir="chromeos-touch-firmware-${FLAGS_board}-${fw_version}${rev}"
  relative_bin_file="${top_dir}/${FW_PATH}/$(basename $bin_name)"
  bin_file="$(dirname $bin_name)/$relative_bin_file"
  mkdir -p "$(dirname $bin_file)"

  # Get the firmware update tool ready.
  local FW_UPDATE_TOOL="$FW_UPDATE_TOOL_CYPRESS"
  # If the firmware upate tool does not exist in the host yet, we need to
  # unmerge the package, and then run update_chroot to get the firmware
  # update tool installed through the virtual/target-sdk package.
  if [ ! -f $FW_UPDATE_TOOL ]; then
    echo $CYPRESS_TOOLS does not exist.
    echo We will unmerge $CYPRESS_TOOLS and then run update_chroot.
    sudo emerge --unmerge $CYPRESS_TOOLS || \
        die "Failed to unmerge '$CYPRESS_TOOLS'"
    ~/trunk/src/scripts/update_chroot || die "Failed to run 'update_chroot'"
    echo "Compile and install $FW_UPDATE_TOOL successfully."
  fi

  # Convert the iic file to the bin file. Set the proper mode and owner/group.
  sudo "$FW_UPDATE_TOOL" -c "$iic_file" -o "$bin_file" > /dev/null
  sudo chmod "$FW_MODE" "$bin_file"
  sudo chown "$FW_OWN" "$bin_file"

  debug_print iic_file
  debug_print bin_name
  echo "Firmware bin file: $bin_file"
}

# Get the firmware file in .bin format
get_bin_fw_file() {
  local zipfile="$1"
  local FILE_TYPE=$(file "$zipfile")
  # Convert it to lower case for comparison
  FILE_TYPE=${FILE_TYPE,,}

  if [[ "$FILE_TYPE" = *"zip archive"* ]]; then
    # Try to find if there exists the .bin file.
    # Otherwise, try to locate the .iic file.
    local fw_file=`zipinfo -1 $zipfile | grep .bin`
    if [ $fw_file ]; then
      echo Find firmware bin file: $fw_file
      unzip -q -o -d "$TMP_DIR" $zipfile $fw_file
    else
      local fw_file=`zipinfo -1 $zipfile | grep .iic`
      if [ $fw_file ]; then
        unzip -q -o -d "$TMP_DIR" $zipfile $fw_file
        convert_iic_to_bin $fw_file
      else
        echo Neither .bin nor .iic file could be found in $zipfile
      fi
    fi
  else
    die "Error: '$zipfile' is not a zip file."
  fi
}

create_tbz2() {
  local target_file="$1"
  local tarball_name="chromeos-touch-firmware"
  local work_dir=$PWD

  tarball="${TMP_DIR}/${tarball_name}-${FLAGS_board}-${fw_version}${rev}.tbz2"
  debug_print target_file
  cd "$TMP_DIR"
  sudo tar --owner=root --group=root --mode="$FW_MODE" -cjf "$tarball" \
      "$target_file"
  cd $work_dir
  echo -e "\nTarball created: ${tarball}"
  echo -e "You could upload this tarball to the Binary Components Server:"
  echo -e "    https://www.google.com/chromeos/partner/fe/ \n"
  echo -e "Type '$PROG -c' to clean up all temporary files" \
          "after uploading the files.\n"
}

# Clean up unnecessary files in the tmp directory.
# This function is invoked automatically after the tarball is created.
clean_up_tmp_files() {
  # Keep only .tbz2 and .bin files.
  find "$TMP_DIR" -type f -not -iregex ".*tbz2\|.*bin" | xargs rm
}

# Clean up the whole tmp directory.
# This function is invoked when '-c' clean up flag is specified.
clean_up_tmp_dir() {
  rm -fr "$TMP_DIR"
  die "Clean up all temporary files in '$TMP_DIR'."
}


# main program begins here.
# -------------------------

if [ $FLAGS_clean = $FLAGS_TRUE ]; then
  clean_up_tmp_dir
fi

check_argument
setup_tmp_dir

# If an issue number is given, get the firmware zip file from a remote server.
# This is executed only when '-i' command flag is specified.
# If '-f' command flag is specified, skip this block.
if [ $FLAGS_issue ]; then
  read_user_and_password
  get_credential
  download_fw_file
  infile=$fw_zipfile
fi

# Now we have the local zip file.
debug_print infile
check_file_existence $infile
get_fw_version
get_bin_fw_file $infile
create_tbz2 "$relative_bin_file"

# Clean up some unnecessary temporary files.
# Keep the created tarball and the .bin firmware file.
clean_up_tmp_files
