#!/bin/bash
if [[ ! -d /opt/swift ]]; then
  mkdir /opt/swift
  mkdir /opt/swift/include
  mkdir /opt/swift/internal-modules
  mkdir /opt/swift/lib
  mkdir /opt/swift/packages
  mkdir /opt/swift/pipes
  mkdir /opt/swift/progress
  mkdir /opt/swift/toolchains
  touch /opt/swift/toolchains/index
  echo "swift" > /opt/swift/runtime
fi

# Process command-line arguments

# Check whether the first argument contains "https://", "http://", "file://", or 
# another protocol.
if [[ "$1" == *"://"* ]]; then
  toolchain_type="url"
  location=-1
  
  # Store URLs in an index because "/" can't be in file names.
  while read line; do
    if [[ $line == $1 ]]; then
      if [[ $location != -1 ]]; then
        echo "Cached toolchain URL index contained duplicates."
        exit -1
      fi
      location=${#index_lines[*]}
    fi
    index_lines=( "${index_lines[@]}" "$line" )
  done < /opt/swift/toolchains/index
  
  if [[ $location == -1 ]]; then
    location=${#index_lines[*]}
    if [[ $location == 0 ]]; then
      index_contents="$1"
    else
      NEWLINE=$'\n'
      index_contents=`cat /opt/swift/toolchains/index`
      index_contents="${index_contents}${NEWLINE}${1}"
    fi
    echo "$index_contents" > /opt/swift/toolchains/index
  fi
  
  version="url-${location}"
else
  old_IFS=$IFS
  IFS='.'
  read -a strarr <<< "$1"
  component_count=${#strarr[*]}
  
  if [[ $component_count -ge 2 ]]; then
    # First argument is two components separated by a period like "5.6" or three
    # components like "5.5.3".
    toolchain_type="release"
  else
    IFS='-'
    read -a strarr <<< "$1"
    component_count=${#strarr[*]}
    
    if [[ $component_count == 3 ]]; then
      # First argument is three components in the format "YYYY-MM-DD".
      toolchain_type="snapshot"
    else
      # First argument is absent or improperly formatted.
      toolchain_type="invalid"
    fi
  fi
  
  version=$1
  IFS=$old_IFS
fi

if [[ $# == 1 ]]; then
  # Release mode - fine-tuned for the fastest user experience.
  mode="release"
elif [[ $# == 2 && $2 == "--swift-colab-dev" ]]; then
  # Dev mode (undocumented) - best for debugging and modifying Swift-Colab.
  mode="dev"
else
  # Unrecognized flags were passed in.
  mode="invalid"
fi

if [[ $toolchain_type == "invalid" || $mode == "invalid" ]]; then
  echo "Usage: install_swift.sh {MAJOR.MINOR.PATCH | YYYY-MM-DD | URL} [--swift-colab-dev]"
  exit -1
fi

cd /opt/swift
echo $mode > /opt/swift/mode

# Determine whether to reuse cached files

if [[ -e "progress/swift-version" ]]; then
  old_version=`cat "progress/swift-version"`
  
  if [[ $version == $old_version ]]; then
    using_cached_swift=true
  elif [[ -d "toolchains/$version" ]]; then
    using_cached_swift=true
    mv "toolchain" "toolchains/$old_version"
    mv "toolchains/$version" "toolchain"
    echo $version > "progress/swift-version"  
  else
    using_cached_swift=false
    mv "toolchain" "toolchains/$old_version"
  fi
else
  using_cached_swift=false
fi

if [[ $using_cached_swift == false && -e "toolchain" ]]; then
  echo "There should be no 'toolchain' folder unless using cached Swift."
  exit -1
fi

if [[ $using_cached_swift == true && ! -e "toolchain" ]]; then
  echo "There should be a 'toolchain' folder when using cached Swift."
  exit -1
fi

# Download Swift toolchain

if [[ $toolchain_type == "url" ]]; then
  swift_desc="from URL: ${1}"
else
  swift_desc=$version
fi

if [[ $using_cached_swift == true ]]; then
  echo "Using cached Swift $swift_desc"
else
  echo "Downloading Swift $swift_desc"
  
  if [[ $toolchain_type == "url" ]]; then
    mkdir /opt/swift/download
    cd /opt/swift/download
    
    curl $1 | tar -xz
    src_filename="$(ls)"
    mv $src_filename "/opt/swift/toolchain"
    
    cd /opt/swift
    rm -r /opt/swift/download
  else
    if [[ $toolchain_type == "release" ]]; then
      branch="swift-$version-release"
      release="swift-$version-RELEASE"
    elif [[ $toolchain_type == "snapshot" ]]; then
      branch="development"
      release="swift-DEVELOPMENT-SNAPSHOT-$version-a"
    fi
    
    tar_file="$release-ubuntu20.04.tar.gz"
    url="https://download.swift.org/$branch/ubuntu2004/$release/$tar_file"
    
    curl $url | tar -xz
    mv "$release-ubuntu20.04" "toolchain"
  fi
  
  echo $version > "progress/swift-version"
fi

export PATH="/opt/swift/toolchain/usr/bin:$PATH"

# Download Swift-Colab

if [[ $mode == "dev" || ! -e "progress/downloaded-swift-colab" ]]; then
  if [[ -d "swift-colab" ]]; then
    rm -r "swift-colab"
  fi
  
  git clone --depth 1 --branch main \
    "https://github.com/philipturner/swift-colab"
  
  swift_colab_include="/opt/swift/swift-colab/Sources/include"
  for file in $(ls $swift_colab_include)
  do
    src_path="$swift_colab_include/$file"
    dst_path="/opt/swift/include/$file"
    if [[ -e $dst_path ]]; then
      rm $dst_path
    fi
    cp $src_path $dst_path
  done
  
  touch "progress/downloaded-swift-colab"
else
  echo "Using cached Swift-Colab"
fi

# Build LLDB bindings

if [[ $mode == "dev" || ! -e "progress/lldb-compiler-version" ||
  $version != `cat "progress/lldb-compiler-version"` ]]
then
  echo "Compiling Swift LLDB bindings"
  cd swift-colab/Sources/LLDBProcess
  
  if [[ ! -d build ]]; then
    mkdir build
  fi
  cd build
  
  clang++ -Wall -O0 -I../include -c ../LLDBProcess.cpp -fpic
  clang++ -Wall -O0 -L/opt/swift/toolchain/usr/lib -shared -o \
    libLLDBProcess.so LLDBProcess.o -llldb
  
  lldb_process_link="/opt/swift/lib/libLLDBProcess.so"
  if [[ ! -L $lldb_process_link ]]; then
    ln -s "$(pwd)/libLLDBProcess.so" $lldb_process_link
  fi
  
  cd /opt/swift
  echo $version > "progress/lldb-compiler-version"
else
  echo "Using cached Swift LLDB bindings"
fi

# Build JupyterKernel

if [[ $mode == "dev" || ! -e "progress/jupyterkernel-compiler-version" ||
  $version != `cat "progress/jupyterkernel-compiler-version"` ]]
then
  echo "Compiling JupyterKernel"
  
  jupyterkernel_path="internal-modules/JupyterKernel"
  if [[ -d $jupyterkernel_path ]]; then
    echo "\
Previously compiled with a different Swift version. \
Removing existing JupyterKernel build products."
    rm -r $jupyterkernel_path
  fi
  cp -r "swift-colab/Sources/JupyterKernel" $jupyterkernel_path
  
  cd $jupyterkernel_path
  source_files=$(find $(pwd) -name '*.swift')
  
  mkdir build && cd build
  swiftc -Onone $source_files \
    -emit-module -emit-library -module-name "JupyterKernel"
  
  jupyterkernel_lib="/opt/swift/lib/libJupyterKernel.so"
  if [[ ! -L $jupyterkernel_lib ]]; then
    echo "Adding symbolic link to JupyterKernel binary"
    ln -s "$(pwd)/libJupyterKernel.so" $jupyterkernel_lib
  fi
  
  cd /opt/swift
  echo $version > "progress/jupyterkernel-compiler-version"
else
  echo "Using cached JupyterKernel library"
fi

# Overwrite Python kernel

if [[ $mode == "dev" || ! -e "progress/registered-jupyter-kernel" ]]; then
  register_kernel='
import Foundation
let libJupyterKernel = dlopen(
  "/opt/swift/lib/libJupyterKernel.so", RTLD_LAZY | RTLD_GLOBAL)!
let funcAddress = dlsym(libJupyterKernel, "JupyterKernel_registerSwiftKernel")!

let JupyterKernel_registerSwiftKernel = unsafeBitCast(
  funcAddress, to: (@convention(c) () -> Void).self)
JupyterKernel_registerSwiftKernel()
'
  echo "$register_kernel" > register_kernel.swift
  swift register_kernel.swift
  rm register_kernel.swift
  
  touch "progress/registered-jupyter-kernel"
fi

runtime=`cat "/opt/swift/runtime"`
runtime=$(echo $runtime | tr '[:upper:]' '[:lower:]')

if [[ $runtime == "swift" ]]; then
  left="$(printf '\e[0;36m=== \e[0m')"
  right="$(printf '\e[0;36m ===\e[0m')"
  top="$(printf '\e[0;36m=== ------------------------------------------------------------------------ ===\e[0m')"
  
  # Escape single quote by specifying its hex value (0x27).
  button="$(printf '\e[0;32m\x27Runtime > Restart runtime\x27\e[0m')"
  
  # Print each line separately for clarity.
  echo $top
  echo "${left}Swift-Colab overwrote the Python kernel with Swift, but Colab is still  ${right}"
  echo "${left}in Python mode. To enter Swift mode, go to ${button}. ${right}"
  echo $top
fi
