#!/bin/bash -
#
# Package: Reference Standard M
# File:    rsm/bin/upgrade
# Summary: Upgrade an RSM or MV1 database from version 1 to 2
#
# David Wicksell <dlw@linux.com>
# Copyright © 2020-2024 Fourth Watch Software LC
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License (AGPL) as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
# License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see https://www.gnu.org/licenses/.
#
# SPDX-FileCopyrightText:  © 2020 David Wicksell <dlw@linux.com>
# SPDX-License-Identifier: AGPL-3.0-or-later

def="\033[0m" log="\033[0;32m" err="\033[0;31m"

error_trap()
{
    local cmd="${BASH_COMMAND}" error=$?
    echo -e "\n${err}Upgrade failed: \`${cmd}\` exited with error ${error}!!!${def}"
}

error_on()
{
    set -eE
    trap error_trap ERR
}

error_off()
{
    set +eE
    trap - ERR
}

error_on

echo -e "${log}Reference Standard M Database Upgrade Utility${def}\n"
echo "Copyright © 2020-2024 Fourth Watch Software LC"
echo -e "https://gitlab.com/Reference-Standard-M/rsm\n"
echo -e "Upgrades an RSM (or MV1) database from version 1 to 2\n"
echo -e "${err}WARNING:${def} This script is experimental"

[[ $1 == "-h" ]] && echo -e "\n${log}Usage:${def}\t$0 [<database file to upgrade>]\n\t$0 -h" && exit 0

[[ -n $1 && ! -f $1 ]] && echo -e "\n${err}File does not exist or is not a regular file${def}"
[[ ! -f $1 ]] && echo && read -rep "Enter the name of the database file to upgrade: " dbfile || dbfile="$1"

dbfile="$(pwd)/${dbfile/\~\//~/}"

[[ -z ${dbfile} ]] && echo -e "\n${err}No file was entered, aborting${def}" && exit 1
[[ ! -f ${dbfile} ]] && echo -e "\n${err}File does not exist or is not a regular file, aborting${def}" && exit 2

cwd="$(pwd)"
repo="$(dirname "$(readlink -f "${cwd}"/"$(dirname "$0")" || true)")"
dbbkup="${dbfile}-$(date "+%Y%m%d%H%M%S").bkup"
version="$(file -bm "${repo}"/etc/magic "${dbfile}")"

if [[ ${version} != "Reference Standard M database file, version 1" ]]
then
    echo -e "\n${err}Wrong database file version [${version}], aborting${def}"
    exit 3
fi

cd "${repo}"

echo -e "\n${log}Building RSM for database version 1...${def}"

make clean 2>/dev/null
make debug dbver=1 2>/dev/null

echo -e "\n${log}Making sure the ${dbfile} environment is shut down for the database backup...${def}"

error_off

if "${repo}"/rsm -k "${dbfile}" 2>/dev/null
then
    echo -e "\n${log}The ${dbfile} environment is now shut down${def}"
else
    echo -e "\n${log}The ${dbfile} environment was already shut down${def}"
fi

error_on

echo -e "\n${log}Backing up the ${dbfile} database as ${dbbkup}...${def}"

cp -a "${dbfile}" "${dbbkup}"

echo -e "\n${log}Starting the ${dbfile} environment in single-user mode...${def}"

"${repo}"/rsm -j 1 "${dbfile}"

echo -e "\n${log}Extracting the database and journal configuration...${def}"

volume="$("${repo}"/rsm -x 'write ^$system("vol",1,"name")' "${dbfile}")"
blksize=$(($("${repo}"/rsm -x 'write ^$system("vol",1,"block")' "${dbfile}") / 1024))
blocks=$("${repo}"/rsm -x 'write ^$system("vol",1,"size")' "${dbfile}")
hdrsize=$(($("${repo}"/rsm -x 'write ^$system("vol",1,"header")' "${dbfile}") / 1024 + 2))

[[ ${hdrsize} -gt 262147 ]] && hdrsize=262147

nextok=$("${repo}"/rsm -x 'write ^$system("$nextok")' "${dbfile}")
eok=$("${repo}"/rsm -x 'write ^$system("eok")' "${dbfile}")
jrnfile="$("${repo}"/rsm -x 'write ^$system("vol",1,"journal_file")' "${dbfile}")"
jrnreq=$("${repo}"/rsm -x 'write ^$system("vol",1,"journal_requested")' "${dbfile}")
globals=($("${repo}"/rsm -x 'set g="" for  set g=$order(^$global(g)) quit:g=""  write:^$global(g,"journal")=1 g" "' "${dbfile}"))

if [[ -f ${jrnfile} ]]
then
    jrnbkup="${jrnfile}-$(date "+%Y%m%d%H%M%S").bkup"

    echo -e "\n${log}Backing up the ${jrnfile} journal as ${jrnbkup}...${def}"

    cp -a "${jrnfile}" "${jrnbkup}"
fi

num=0 uci="" ucis="" cnt=0

while true
do
    num=$("${repo}"/rsm -x "write \$order(^\$system(\"vol\",1,\"uci\",${num}))" "${dbfile}")
    [[ ${num} == "" ]] && break

    uci="$("${repo}"/rsm -x "write ^\$system(\"vol\",1,\"uci\",${num})" "${dbfile}")"

    ucis[cnt]="${uci}"
    cnt=$((cnt + 1))

    echo -e "\n${log}Exporting the globals and routines from the ${uci} UCI...${def}"

    "${repo}"/rsm -e "${uci}" -x "do INT^%GS(\"*\",\"${dbfile}-${uci}.go\",\"RSM [${uci}] Globals\")" "${dbfile}"
    "${repo}"/rsm -e "${uci}" -x "do INT^%RS(\"*\",\"${dbfile}-${uci}.ro\",\"RSM [${uci}] Routines\")" "${dbfile}"

    echo -e "\n${log}Calculating new bytecode sizes...${def}"

    rtnmax=$("${repo}"/rsm -x "set rou=\"\",max=0 for  set rou=\$order(^\$routine(rou)) write:rou=\"\" max,! quit:rou=\"\"  \
      set cmp=^\$routine(rou,0),len=\$length(cmp),tag=\$ascii(cmp,16)*256+\$ascii(cmp,15),var=\$ascii(cmp,20)*256+\$ascii(cmp,19) \
      set max=\$select(tag+var*24+len>max:tag+var*24+len,1:max)" "${dbfile}" | tr -d '\r' || true)

    if [[ -n ${prevrtnmax} ]]
    then
        prevrtnmax=${rtnmax}
    else
        [[ ${prevrtnmax} -gt ${rtnmax} ]] && rtnmax=${prevrtnmax}
    fi
done

strmax=$("${repo}"/rsm -x 'write $get(^$system("string_max"),32767)+1' "${dbfile}")

if [[ ${rtnmax} -lt ${strmax} && ${rtnmax} -gt $((blksize * 1024)) ]]
then
    rtnmax=$((rtnmax / 1024 + 1))

    echo -e "\nTo load your routines, use a block size of at least ${rtnmax} KiB - current size is ${blksize} KiB"
    read -rep "Do you want to use a larger size? [N/y]: " ans

    if [[ ${ans} == y || ${ans} == Y ]]
    then
        read -rep "What block size in KiB do you want to use? [1 - 256 KiB]: " ans
        [[ -n ${ans} ]] && blksize=${ans}
    fi
fi

echo -e "\n${log}Shutting down the old ${dbfile} environment...${def}"

"${repo}"/rsm -k "${dbfile}"

echo -e "\n${log}Building RSM for database version 2...${def}"

make clean 2>/dev/null
make debug 2>/dev/null

echo -e "\n${log}Removing the old ${dbfile} database...${def}"

rm -f "${dbfile}"

echo -e "\n${log}Creating the new ${dbfile} database with the same configuration as the old one...${def}"

"${repo}"/rsm -v "${volume}" -b "${blksize}" -s "${blocks}" -m "${hdrsize}" "${dbfile}"

echo -e "\n${log}Starting the new ${dbfile} environment in single-user mode...${def}"

"${repo}"/rsm -j 1 "${dbfile}"

echo -e "\n${log}Loading the database and journal configuration...${def}"

"${repo}"/rsm -x "set ^\$system(\"\$nextok\")=${nextok}" "${dbfile}"
"${repo}"/rsm -x "set ^\$system(\"eok\")=${eok}" "${dbfile}"
"${repo}"/rsm -x "set ^\$system(\"vol\",1,\"journal_file\")=\"${jrnfile}\"" "${dbfile}"
"${repo}"/rsm -x "set ^\$system(\"vol\",1,\"journal_requested\")=${jrnreq}" "${dbfile}"

if [[ -f ${jrnfile} ]]
then
    echo -e "\n${log}Cutting a new journal file for database version 2...${def}"

    "${repo}"/rsm -x 'set ^$system("vol",1,"journal_size")=0' "${dbfile}"
fi

for global in "${globals[@]}"
do
    "${repo}"/rsm -x "set ^\$global(\"${global}\",\"journal\")=1" "${dbfile}"
done

echo -e "\n${log}Loading the vendor import utility...${def}"

"${repo}"/rsm -x "open 1:(\"${repo}/utils.rsm\":\"r\") use 1 read code xecute code" "${dbfile}"

uci="" cnt=1

for uci in "${ucis[@]}"
do
    if [[ ${cnt} != 1 ]]
    then
        echo -e "\n${log}Creating the ${uci} UCI...${def}"

        "${repo}"/rsm -x "set ^\$system(\"vol\",1,\"uci\",${cnt})=\"${uci}\"" "${dbfile}"
    fi

    cnt=$((cnt + 1))

    echo -e "\n${log}Importing the globals and routines to the ${uci} UCI...${def}"

    if [[ -f ${dbfile}-${uci}.go ]]
    then
        "${repo}"/rsm -e "${uci}" -x "do INT^%GR(\"${dbfile}-${uci}.go\",1)" "${dbfile}"
    fi

    if [[ -f ${dbfile}-${uci}.ro ]]
    then
        "${repo}"/rsm -e "${uci}" -x "do INT^%RR(\"${dbfile}-${uci}.ro\",1)" "${dbfile}"
    fi
done

if [[ ${jrnreq} == 1 ]]
then
    echo

    "${repo}"/rsm -k "${dbfile}"

    while fuser "${dbfile}" &>/dev/null
    do
        sleep 1
    done

    echo

    "${repo}"/rsm -j 2 "${dbfile}"
fi

echo -e "\n${log}Loading the new vendor utilities...${def}"

"${repo}"/rsm -x "open 1:(\"${repo}/utils.rsm\":\"r\") use 1 read code xecute code" "${dbfile}"

echo -e "\n${log}Shutting down the new ${dbfile} environment...${def}"

"${repo}"/rsm -k "${dbfile}"

echo -e "\n${log}Removing the temporary global and routine export files...${def}"

rm -f "${dbfile}"-*.[gr]o

echo -e "\n${log}Cleaning the build environment...${def}"

make clean 2>/dev/null

cd "${cwd}"

exit 0

# ex: filetype=bash
