<?php

const DEFAULT_PASSWORD = 'ChangeMe!';

/*
TODO Installer
- Check for GD - if not present, inform (but don't prevent install)
- If not configuring an imaging system, create a default scoring system.
*/

set_time_limit(0);
setUpClassAutoLoader();
date_default_timezone_set('UTC');

$installDir=config::getWwwRoot().'/install/';
Log::init(Log::LOGLEVEL_DEBUG, 'install.log');

$icebearSessionStarted=false;
$error=null;
$pass=0;

//Formulatrix database handles
$rm=null;
$ri=null;

if(!file_exists($installDir.'preInstallDone')){
    Log::error( 'Cannot run installer because set-up script has not been run.');
	writePage("dopre",'Run the set-up script first');
}

$installComplete=false;
if(file_exists($installDir.'installcomplete')){
    Log::info( 'Install complete.');
    $installComplete=true; //Used in "done" template - don't optimise out!
	writePage("done");
}

if(isset($_GET['page'])){
    $pass = $_GET['pass'] ?? 0;
	$pass++;
	writePage($_GET['page']);
}

if(isset($_POST['fromtemplate'])){
    $template=$_POST['fromtemplate'];
} else if(isset($_GET['fromtemplate'])){
    $template=$_GET['fromtemplate'];
} else {
	writePage("prestart");
}


Log::debug( '=================================');
Log::debug( ' ');
Log::debug( 'New POST, from '.$template);

switch($template){
	case 'dopre':
		/*
		 * The user has run the pre-installation script, having failed to do so previously.
		 * Show them the requirements for installation.
		 */
		$template='prestart';
		break;
	case 'prestart':
		/*
		 * They read the instructions (hopefully).
		 * Show them the configuration of image store and backup paths.
		 */
		$template='stores';
		break;
	case 'stores':
		/*
		 * They gave us image store and backup details. Attempt to configure.
		 * If successful, show the MySQL database config options.
		 */
		try{
			setUpStorage();
			$template='database';
		} catch (Exception $e){
			$error=$e->getMessage();
		}
		break;
	case 'database':
		/*
		 * Database now created by pre-installer.
		 * All we need to do is load the storage config from the temporary file, and create a temporary install user.
		 * Then redirect back to here with case "upgradeversion".
		 */
		try{
			setUpDatabase();
			echo '<html lang="en"><head><meta http-equiv="refresh" content="5; url=/install?fromtemplate=upgradeversion"><title>Database upgrade begun...</title></head><body>';
			echo 'Upgrading database to match code version. This may take several minutes. Please wait...<br/>';
			echo '</body></html>';
			exit();
		} catch (Exception $e){
			$error=$e->getMessage();
		}
		break;	
	case 'upgradeversion':
		/*
		 * Upgrade the DB to match the code.
		 * Show the form for creating the admin account.
		 */
		try{
			upgradeDatabase();
			$template='adminaccount';
		} catch (Exception $e){
			$error=$e->getMessage();
		}
		break;
	case 'adminaccount':
		/*
		 * They gave us the admin account username and password.
		 * Create the admin account, load reference projects and usergroups, load standard screens
		 * Ask whether they have an imaging system or not.
		 */
		try{
			createAdminAccount();
			$template='imagingsystem';
		} catch (Exception $e){
			$error=$e->getMessage();
		}
		break;
	case 'imagingsystem':
		/*
		 * They told us whether they have an imaging system.
		 * - if not, there is nothing else to do, so jump to the end.
		 * - if Formulatrix, ask them for connection details.
		 */
		try{
			$system=$_POST['imagingsystem'];
			if('fx'==$system){
				$template='imagerdb';
			} else if('none'==$system){
				setUpManualImaging();
                writeRootScriptToFinishInstall();
				$template='done';
			}
		} catch (Exception $e){
			$error=$e->getMessage();
		}
		break;
	case 'imagerdb':
		/*
		 * They told us about the Formulatrix database and image stores.
		 * Verify connection details, write a script for root (who will run it via cron later) to mount the network shares.
		 * Copy imagers and users from RockMaker to IceBear. Set importer start date.
		 * Show imager configuration.
		 */
		try{
			setUpFormulatrixImagerDatabase();
			writeRootScriptToMountFormulatrixImageStores($_POST['_isuser'],$_POST['_ispass']);
			$template='imagers';
		} catch (Exception $e){
			$error=$e->getMessage();
		}
		break;
	case 'imagers':
		/*
		 * They told us the temperature, friendly name, capacity, etc., of the imagers.
		 * Update IceBear.
		 * Show them the users form.
		 */
		try{
			updateImagers();
			$template='users';
		} catch (Exception $e){
			$error=$e->getMessage();
		}
		break;
	case 'users':
		/*
		 * User submitted the Users form. If no changes, proceed to supervisors,
		 * otherwise return to Users form for review/further work.
		 */
		try{
			updateUsers();
			$template='users';
			if(isset($_POST['next'])){
				$template='supervisors';
			}				
		} catch (Exception $e){
			$error=$e->getMessage();
		}
		break;
	case 'supervisors':
		/*
		 * User submitted the Supervisors form. If no changes, proceed to Done,
		 * otherwise return to Supervisors form for review/further work.
		 */
		try{
			setSupervisorIds();
			$template='supervisors';
			if(isset($_POST['next'])){
                writeRootScriptToFinishInstall();
                $template='done';
			}				
		} catch (Exception $e){
			$error=$e->getMessage();
		}
		break;
	case 'done':
		/*
		 * The user's part in the installation is complete. We are waiting for the root cron job
		 * to mount image stores, etc., and write the "installcomplete" file to the installation directory.
		 * 
		 */
		if(isset($_POST['pass'])){
			$pass=$_POST['pass'];
		}
		if(isRootCronFinished()){
			touch($installDir.'installcomplete');
		} else if($pass>20){
			writePage('done','Waited for final clean-up but it never happened.');
		}
		header('Location: /install/?page=done&pass='.$pass);
		break;
}

if(!empty($error)){
    Log::error( $error);
	writePage($template,$error);
} else {
	goToPage($template);
}

/* Functions only below here */

function goToPage($template){
	header("Location: /install/?page=".$template);
	exit();
}

/** @noinspection PhpUnusedParameterInspection */
function writePage($template, $error=null){
    /* $pass and $error are used in template. */
    /** @noinspection PhpUnusedLocalVariableInspection */
    global $pass, $installComplete; //$pass used in "done" template.
	include_once 'templates/_header.php';
    include_once 'templates/'.$template.'.php';
	include_once 'templates/_footer.php';
	if($installComplete){
        renameInstallDirectory();
    }
	exit();
}
/**
 * Register a class autoloader for the IceBear classes.
 */
function setUpClassAutoLoader(){
	spl_autoload_register(function($className){
		$paths=array(
				'../classes/',
				'../classes/core/',
				'../classes/core/exception/',
				'../classes/core/authentication/',
				'../classes/core/interface/',
				'../classes/model/',
		);
		foreach($paths as $path){
			if(file_exists($path.$className.'.class.php')){
                include_once($path.$className.'.class.php');
			}
		}
	});
}

/**
 * @throws BadRequestException
 * @throws NotFoundException
 * @throws ServerException
 */
function startIceBearSession(){
	global $icebearSessionStarted;
	database::connect();
	$firstUser=database::queryGetOne('SELECT id,name FROM user ORDER BY id ');
	$sharedProjectName=baseproject::SHARED;
	$select='SELECT id from project WHERE name=:name';
	$insert='INSERT INTO project(name,description,owner) VALUES(:n1, :n2, :uid)';
	if(!database::query($select, [':name'=>$sharedProjectName])){
		database::query($insert, [':n1'=>$sharedProjectName,':n2'=>$sharedProjectName,':uid'=>$firstUser['id']]);
	}

	session::init(new DummySession());
	session::set('isAdmin',true);
	if($firstUser){
		session::set('userId', $firstUser['id']);
	}
	$icebearSessionStarted=true;
}


/**
 * Configure IceBear live and backup storage
 * @throws Exception
 */
function setUpStorage(){
	$liveStore=$_POST['livestore'];
	$backupStore=$_POST['backupstore'];
	$backupsToKeep=(int)($_POST['backupstokeep']);
	$liveStore=rtrim($liveStore,'/');
	$backupStore=rtrim($backupStore,'/');
	if(empty($liveStore)){ throw new Exception('Storage path must be specified'); }
	if(empty($backupStore) && !empty($backupsToKeep)){ throw new Exception('Both backup store and number of backups must be specified to configure backups'); }
	if(!empty($backupStore) && empty($backupsToKeep)){ throw new Exception('Both backup store and number of backups must be specified to configure backups'); }
	//allow C: for dev on Windows only
	if(!str_starts_with($liveStore, '/') && !str_starts_with($liveStore, 'C:')){ throw new Exception('Storage path must be absolute (begin with /)'); }
	if(file_exists($liveStore.'/fileuploads')){ 
		throw new Exception('Storage path '.$liveStore.' already has IceBear subdirectories. Is there a previous installation here? Aborting.');
	}
	if(!file_exists($liveStore)){ throw new Exception('Either storage path '.$liveStore.' does not exist or the web server does not have permission to see it'); }
	if(!@mkdir($liveStore.'/fileuploads') || !@mkdir($liveStore.'/imagestore')){
		throw new Exception('Store path exists, but the web server cannot write to it');
	}
	if(!empty($backupStore)){
		//allow C: for dev on Windows only
		if(!str_starts_with($backupStore, '/') && !str_starts_with($backupStore, 'C:')){ throw new Exception('Backup path must be absolute (begin with /)'); }
		if(1>$backupsToKeep){ throw new Exception('If specifying a backup path, number of backups to keep must be at least 1'); }
		if(!file_exists($backupStore)){ throw new Exception('Either backup path '.$backupStore.' does not exist or the web server does not have permission to see it'); }
		$imageBackups=$backupStore.'/imagestore';
		$fileBackups=$backupStore.'/fileuploads';
		$dbBackups=$backupStore.'/database';
		if(file_exists($backupStore.'/fileuploads')){ 
			throw new Exception('Backups path '.$backupStore.' already has IceBear subdirectories. Is there a previous installation here? Aborting.');
		}
		if(!@mkdir($imageBackups) || !@mkdir($fileBackups) || !@mkdir($dbBackups)){
			throw new Exception('Backup path exists, but the web server cannot write to it');
		}
	} else {
		$imageBackups='';
		$fileBackups='';
		$dbBackups='';
	}
	//Temporary ini file, we didn't have a database.
	$contents=implode(PHP_EOL,array(
			";for security:",
			";<?php exit(); __halt_compiler();",
			"",
			"core_databasebackupstokeep=$backupsToKeep",
			"core_databasebackup=$dbBackups",
			"core_filestorebackup=$fileBackups",
			"core_imagestorebackup=$imageBackups",
			"core_filestore=$liveStore/fileuploads",
			"core_imagestore=$liveStore/imagestore",
	));
	$file=@fopen(config::getWwwRoot().'/install/storepaths.ini', 'w');
	if(!$file || !@fwrite($file, $contents) || !@fclose($file)){
		throw new Exception("Could not write the storage paths to a temporary config file");
	}
	
}


/**
 * Set up IceBear database - populate storage config from temporary file, and create a temporary user
 * @throws Exception
 */
function setUpDatabase(){

	$wwwRoot=config::getWwwRoot();
    $conf=parse_ini_file($wwwRoot.'/conf/config.ini');
    if(!$conf){ throw new Exception('Could not read database connection details from config file'); }
    $db = new PDO('mysql:host='.$conf['dbHost'].';dbname='.$conf['dbName'], $conf['dbUser'], $conf['dbPass'], array(PDO::MYSQL_ATTR_FOUND_ROWS => true));
    $conf=null; //for security, do not keep the DB credentials
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    //Read storage and backup paths from temporary file and set them in the config table
	$paths=parse_ini_file($wwwRoot.'/install/storepaths.ini');
	if(!$paths){
		throw new Exception("Couldn't get storage paths from temporary config file.");
	}
	$stmt=$db->prepare('UPDATE config SET value=:value WHERE name=:key');
	foreach($paths as $k=>$v){
		$stmt->execute(array(':key'=>$k, ':value'=>$v));
	}
	@unlink($wwwRoot.'/install/storepaths.ini');

	//Create a user for the installation
	$stmt=$db->prepare('INSERT INTO user(name, fullname, password, email, isactive) VALUES(:name, :fullname, :password, :email, :isactive)');
	$stmt->execute(array(
	    ':name'=>'installer',
	    ':password'=>password_hash('UselessPassword', PASSWORD_BCRYPT),
	    ':fullname'=>'Installation user',
	    ':email'=>'installer@null.null',
	    ':isactive'=>0
	));
	
}

/**
 * @throws BadRequestException
 * @throws ServerException
 * @throws Exception
 */
function upgradeDatabase(){
    Log::debug( 'In upgradeDatabase()');
	startIceBearSession();
	database::begin();
    $wwwRoot=config::getWwwRoot();

    if(!@include($wwwRoot.'/upgrade/includes/ReferenceDataLoader.class.php')){
	   throw new Exception('Could not include reference data loader class.');
	}
	ReferenceDataLoader::loadReferenceDataForInstallPart1();
	
	//update structure and load remaining ref data
	try {
	    $codeVersion=config::getCodeVersion();
	    Log::info( 'Database version: '.config::getDatabaseVersion());
	    Log::info( 'Code version: '.$codeVersion);
	    if(!$codeVersion){
	        throw new Exception('Could not determine code version from filesystem');
	    }
	    updater::updateDatabase($wwwRoot);
	    ReferenceDataLoader::loadReferenceDataForInstallPart2();

	    database::commit();
	} catch (Exception $e){
		database::abort();
		throw new Exception($e->getMessage());
	}
	Log::debug( 'upgradeDatabase() complete');
}


/**
 * Set up IceBear admin account
 * @throws Exception
 */
function createAdminAccount(){
	if($_POST['pass']!=$_POST['pass2']){
		throw new Exception('Password fields do not match');
	}
	
	startIceBearSession();
	try {

	    database::begin();
        $user=user::create(array(
            'name'=>$_POST['user'],
            'password'=>$_POST['pass'],
            'fullname'=>'IceBear Administrator',
            'email'=>'bogus@null.null',
            'isactive'=>1
        ));
        $user=$user['created'];
        $userId=$user['id'];
        session::set('userId', $userId);

        /** @noinspection SqlWithoutWhere */
        database::query('UPDATE project SET owner=:userid', array(':userid'=>$userId));
        /** @noinspection SqlWithoutWhere */
        database::query('UPDATE baseobject SET creator=:userid', array(':userid'=>$userId));
        /** @noinspection SqlWithoutWhere */
        database::query('UPDATE baseobject SET lasteditor=:userid', array(':userid'=>$userId));
        database::query("DELETE FROM user WHERE name='installer'");
		
        //put user into Admins group
        $adminGroup=usergroup::getByName('Administrators');
        $grpId=$adminGroup['id'];
        groupmembership::create(array(
            'userid'=>$userId,
            'usergroupid'=>$grpId,
            'isgroupadmin'=>1
        ));
        
        
        //Populate their homepage with bricks...
		$homepageBricks=array(
				array(1,1,'fi_oulu_PlateInspections'),
				array(2,1,'fi_oulu_Tasks'),
				array(3,1,'fi_oulu_DiskUsage'),
				array(1,2,'fi_oulu_Imagers'),
				array(2,2,'fi_oulu_AutoProvisionedUsers'),
				array(1,3,'fi_oulu_FirstBrick'),
		);
		foreach($homepageBricks as $b){
		    $brick=homepagebrick::getByName($b[2]);
		    if(!$brick){ continue; }
		    homepageuserbrick::create(array(
		        'userid'=>$userId,
		        'homepagebrickid'=>$brick['id'],
		        'row'=>$b[0],
		        'col'=>$b[1]
		    ));
		}

		//...and set up a default homepage for everyone else
		$homepageBricks=array(
		    array(1,1,'fi_oulu_PlateInspections'),
		    array(2,1,'fi_oulu_Tasks'),
		    array(1,2,'fi_oulu_Imagers'),
		    array(2,2,'fi_oulu_Plates'),
		    array(1,3,'fi_oulu_FirstBrick'),
		);
		foreach($homepageBricks as $b){
		    $brick=homepagebrick::getByName($b[2]);
		    if(!$brick){ continue; }
		    homepagedefaultbrick::create(array(
		        'homepagebrickid'=>$brick['id'],
		        'row'=>$b[0],
		        'col'=>$b[1]
		    ));
		}

		$defaultScoreSystemName="IceBear Default";
		$defaultScores=array(
            array(0,'666666','Clear'),
            array(1,'0000ff','Precipitate'),
            array(2,'00ff00','Phase separation'),
            array(2,'ffff00','Needles/plates'),
            array(3,'ff0000','Crystals'),
        );
		$defaultScoreSystem=crystalscoringsystem::create(array(
		    'name'=>$defaultScoreSystemName,
            'iscurrent'=>1
        ));
        $defaultScoreSystem=$defaultScoreSystem['created'];
        $defaultScoreSystemId=$defaultScoreSystem['id'];
        foreach ($defaultScores as $defaultScore) {
            crystalscore::create(array(
                'hotkey'=>$defaultScore[0],
                'scoreindex'=>$defaultScore[0],
                'color'=>$defaultScore[1],
                'name'=>$defaultScoreSystemName.'_'.$defaultScore[2],
                'label'=>$defaultScore[2],
                'crystalscoringsystemid'=>$defaultScoreSystemId
            ));
		}

		database::commit();
		
	} catch (Exception $e){
		database::abort();
		throw new Exception($e->getMessage());
	}

}

/**
 * Configures IceBear for manual imaging
 * @throws Exception
 */
 function setUpManualImaging(){
 	startIceBearSession();
	try {
 		database::begin();
 		config::set('imaging_allowmanual', 1);
 		config::set('fx_hasimagers', 0);
		//set has rigaku to false
 		database::commit();
	} catch (Exception $e){
		database::abort();
		throw new Exception($e->getMessage());
	}
 }

/**
 * Configures Formulatrix connection
 * @throws Exception
 */

function setUpFormulatrixImagerDatabase(){
	startIceBearSession();
	database::begin();
	try {
 		config::set('imaging_allowmanual', 0);
		config::set('fx_hasimagers', 1);
		//set has rigaku to false
		foreach($_POST as $k=>$v){
			if(str_starts_with($k, 'fx_')){
				config::set($k, trim($v));
			}
		}
		database::commit();
		fxConnect();
		fxGetImagers();
		fxGetUsers();
	} catch (Exception $e){
		database::abort();
		throw new Exception($e->getMessage());
	}
}

/**
 * @param $diskUser
 * @param $diskPass
 * @throws Exception
 */
function writeRootScriptToMountFormulatrixImageStores($diskUser,$diskPass){

    $suppliedHost=$_POST['fx_dbhost'];
    $wwwRoot=config::getWwwRoot();

    $out = '';
    $cron = array();

    $root = '/mnt/Formulatrix/';
    $imageStores = fxGetImageStores();
    $out .= 'mkdir ' . $root . PHP_EOL;
    $out .= 'rm ' . $root . '.smbcredentials' . PHP_EOL;
    $out .= 'echo \'username=' . $diskUser . '\' >> ' . $root . '.smbcredentials' . PHP_EOL;
    $out .= 'echo \'password=' . $diskPass . '\' >> ' . $root . '.smbcredentials' . PHP_EOL;
    $out .= 'echo \'# Formulatrix image stores - added by IceBear installer\' >> /etc/fstab' . PHP_EOL;
    $out .= 'echo \'# Each image store is mounted twice here, once with the path from the Formulatrix image\' >> /etc/fstab' . PHP_EOL;
    $out .= 'echo \'# store, once with the database server name given during install. One of these may fail,\' >> /etc/fstab' . PHP_EOL;
    $out .= 'echo \'# but this should cause no harm. We do this to prevent the image store from being unmounted, causing \' >> /etc/fstab' . PHP_EOL;
    $out .= 'echo \'# inspections to be imported with no images until someone sorts the mounting out manually.\' >> /etc/fstab' . PHP_EOL;
    $out .= 'echo \'# See the root user crontab, where mount -a is called on reboot, for further implications.\' >> /etc/fstab' . PHP_EOL;
    foreach ($imageStores as $s) {
        $s['Name'] = preg_replace('/[^0-9a-zA-Z]/i', '', $s['Name']);
        //mkdir
        $out .= 'mkdir ' . $root . $s['Name'] . PHP_EOL;
        //append to /etc/fstab
        $path = str_replace('\\', '/', $s['BasePath']);
        $out .= 'echo \'' . $path . ' ' . $root . $s['Name'] . ' cifs ro,credentials=' . $root . '.smbcredentials,iocharset=utf8,sec=ntlmssp,vers=3.0 0 0\' >> /etc/fstab' . PHP_EOL;
        $path = '//'.strtolower(str_replace('\\', '/', $suppliedHost)).'/'. $s['Name'];
        $out .= 'echo \'' . $path . ' ' . $root . $s['Name'] . ' cifs ro,credentials=' . $root . '.smbcredentials,iocharset=utf8,sec=ntlmssp,vers=3.0 0 0\' >> /etc/fstab' . PHP_EOL;
    }
    $out .= 'mount -a' . PHP_EOL;
    //add importer to crontab
    $cron[] = '# IceBear: Import plates, etc., from Formulatrix imagers.';
    $cron[] = '# Run the importer every 5 minutes, rotate the logs nightly.';
    $cron[] = '*/5 * * * * php ' . $wwwRoot . '/formulatriximporter/importer.php -m5 -l2 >> /var/log/icebear/fximport.log';
    $cron[] = '59 23 * * * ' . $wwwRoot . '/formulatriximporter/rotatelogs.sh';
    $cron[] = '#';
    $cron[] = '# IceBear: Re-mount Formulatrix image stores. Because of possible failure to mount the image stores using ';
    $cron[] = '# the paths specified in the Formulatrix database, we write two lines to /etc/fstab for each store - one ';
    $cron[] = '# using the value from the RockMaker.ImageStore.BasePath, the other using the database server supplied for ';
    $cron[] = '# the database connection. This ensures that the store is connected on install, but can cause mount errors ';
    $cron[] = '# on reboot. We mount -a as root on reboot to get around this. ';
    $cron[] = '@reboot mount -a';
    foreach($cron as $c){
        $out.='( crontab -l | grep -v -F "'.$c.'" ; echo "'.$c.'" ) | crontab -'.PHP_EOL;
    }
    $outfile=@fopen($wwwRoot.'/install/imagerinstalltasks.sh','w');
    if(!$outfile){ throw new Exception('Could not open temporary root script '.$wwwRoot.'install/imagerinstalltasks.sh'.' for writing'); }
    fwrite($outfile, $out);
    fclose($outfile);
    chmod($wwwRoot.'/install/imagerinstalltasks.sh',0755);
}

/**
 * Writes a script that will be run by root at the end of the installation. This script will schedule the backup, schedule the
 * Formulatrix importer if configured, and remove the per-minute cron that looks for this script.
 * @throws Exception
 */
function writeRootScriptToFinishInstall(){

    $wwwRoot=config::getWwwRoot();
    $out = 'touch '. $wwwRoot .'/install/rootcronstarted' . PHP_EOL;

    $imagerTasks=$wwwRoot.'/install/imagerinstalltasks.sh';
    if(file_exists($imagerTasks)){
        $in=@file($imagerTasks);
        if(!$in){
            throw new Exception('Could not open imager tasks file for processing');
        }
        foreach ($in as $line){
            $out.=$line.PHP_EOL;
        }
    }

    //add backup to crontab
	$cron[]='# IceBear: Back up the database and WWW files if configured to do so.';
	$cron[]='# Run the backup nightly, rotate the logs once a week.';
	$cron[]='# Back up just after 00:00 but before first importer run, so backup date is correct and importer waits.';
	$cron[]='4 0 * * * php '.$wwwRoot.'/backup/backup.php -l2 >> /var/log/icebear/backup.log';
	$cron[]='58 23 * * * '.$wwwRoot.'/backup/rotatelogs.sh';

	foreach($cron as $c){
		$out.='( crontab -l | grep -v -F "'.$c.'" ; echo "'.$c.'" ) | crontab -'.PHP_EOL;
	}
	
	//remove this temporary script from the crontab
	$out.='( crontab -l | grep -v -F "rootinstalltasks.sh" ) | crontab - '.PHP_EOL;
	$out.='touch '.$wwwRoot.'/install/rootcrondone'.PHP_EOL;

	//dump to file
	$outfile=@fopen($wwwRoot.'/install/rootinstalltasks.sh','w');
	if(!$outfile){ throw new Exception('Could not open temporary root script '.$wwwRoot.'/install/rootinstalltasks.sh'.' for writing'); }
	fwrite($outfile, $out);
	fclose($outfile);
	chmod($wwwRoot.'/install/rootinstalltasks.sh',0755);
}


/**
 * Connect to the RockMaker and RockImager databases and return true, or throw an exception.
 * @return boolean
 * @throws Exception
 */
function fxConnect(){
	global $ri,$rm;
	$hostname=config::get('fx_dbhost');
	$port=config::get('fx_dbport');
	$username=config::get('fx_dbuser');
	$password=config::get('fx_dbpass');
	$rmdbname=config::get('fx_rmdbname');
	$ridbname=config::get('fx_ridbname');
	$drivers=PDO::getAvailableDrivers();
	$message='';

	if(in_array('dblib', $drivers)){
		try {
			$rm = new PDO ("dblib:host=$hostname:$port;dbname=$rmdbname",$username,$password);
			$rm->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
			$ri = new PDO ("dblib:host=$hostname:$port;dbname=$ridbname",$username,$password);
			$ri->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
			return true;
		} catch(Exception $e){
			//dblib: attempt failed
			$message.=PHP_EOL."Attempting with dblib gave: ".$e->getMessage();
		}
	} else if(in_array('sqlsrv', $drivers)){
		try {
			$rm = new PDO ("sqlsrv:Server=$hostname,$port; Database=$rmdbname",$username,$password);
			$rm->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
			$ri = new PDO ("sqlsrv:Server=$hostname,$port;Database=$ridbname",$username,$password);
			$ri->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
			return true;
		} catch(Exception $e){
			//sqlsrv: attempt failed
			$message.=PHP_EOL."Attempting with sqlsrv gave: ".$e->getMessage();
		}
	}
	$message.=PHP_EOL."Available PDO drivers: ".implode(', ',$drivers);
	throw new Exception("Could not connect to Formulatrix databases:".$message);
}

/**
 * @throws BadRequestException
 * @throws ForbiddenException
 * @throws NotFoundException
 * @throws ServerException
 * @throws Exception
 */
function fxGetImagers(){
	global $ri;
	if(null==$ri){ throw new Exception('call fxConnect before fxGetImagers'); }

	$stmt=$ri->prepare('SELECT ' . 'Name FROM Robot');
	$stmt->execute(array());
	$imagers=$stmt->fetchAll(PDO::FETCH_ASSOC);
	foreach($imagers as $imager){
		$imagerName=$imager['Name'];
		$imagerType=explode('-', $imagerName)[0];
		switch($imagerType){
			case 'RI1':
				$capacity=1;
				break;
			case 'RI2':
				$capacity=2;
				break;
			default:
				/** @noinspection SqlAggregates Don't care about efficiency here. Small dataset, doing it once. */
                $stmt=$ri->prepare('SELECT COUNT(' . 'Robot.Name) AS capacity 
						FROM Address, Hotel, Robot
						WHERE Address.HotelID=Hotel.ID 
							AND Address.RobotID=Robot.ID
							AND Hotel.IsPresent=1
						GROUP BY Robot.Name
						HAVING Robot.Name=:name
				');
				$stmt->execute(array(':name'=>$imagerName));
				$result=$stmt->fetch(PDO::FETCH_ASSOC);
				if(!$result){
					$capacity=10000;
				} else {
					$capacity=$result['capacity'];
				}
		}
		imager::create(array(
				'name'=>$imagerName,
				'friendlyname'=>$imagerName,
				'manufacturer'=>'Formulatrix',
				'temperature'=>20,
				'platecapacity'=>$capacity,
				'alertlevel'=>floor($capacity*75/100),
				'warninglevel'=>floor($capacity*90/100)
		));
	}
}

/**
 * @return array
 * @throws Exception
 */
function fxGetImageStores(){
	global $rm;
	if(null==$rm){ throw new Exception('call fxConnect before fxGetImageStores'); }

	$stmt=$rm->prepare('SELECT Name, ' . 'BasePath FROM ImageStore');
	$stmt->execute(array());
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

/**
 * @throws BadRequestException
 * @throws ServerException
 * @throws Exception
 */
function fxGetUsers(){
	global $rm;
	if(null==$rm){ throw new Exception('call fxConnect before fxGetUsers'); }
	$stmt=$rm->prepare("SELECT Name," . "EmailAddress FROM Users WHERE Name NOT IN('Administrator','FormulatrixAdmin','Default User')");
	$stmt->execute(array());
	$users=$stmt->fetchAll(PDO::FETCH_ASSOC);
	foreach($users as $user){
		$temporaryUsername='AUTO'.rand(10000000,99999999);
		while(user::getByName($temporaryUsername)){
			$temporaryUsername='AUTO'.rand(10000000,99999999);
		}
		createUser($temporaryUsername, $user['Name'] , $user['EmailAddress'], true, false, false);
	}
}

/**
 * @param $name
 * @param $fullName
 * @param $email
 * @param $isActive
 * @param $isAdmin
 * @param $isSupervisor
 * @return mixed
 * @throws BadRequestException
 * @throws ForbiddenException
 * @throws NotFoundException
 * @throws ServerException
 */
function createUser($name, $fullName, $email, $isActive, $isAdmin, $isSupervisor){
	$user=array(
			'name'=>$name,
			'fullname'=>$fullName,
			'password'=>'ChangeMe!',
			'email'=>$email,
			'isactive='=>$isActive
	);
	$user=user::create($user);
	$userId=$user['created']['id']; //Puts user into "Everyone" group.
	$adminGroup=usergroup::getByName('Administrators');
	$superGroup=usergroup::getByName('Supervisors');
	$adminGroupId=$adminGroup['id'];
	$superGroupId=$superGroup['id'];
	if($isAdmin){
		groupmembership::create(array('userid'=>$userId, 'usergroupid'=>$adminGroupId));
	}
	if($isSupervisor){
		groupmembership::create(array('userid'=>$userId, 'usergroupid'=>$superGroupId));
	}
	return $userId;
}

/**
 * Configure imagers
 * @throws Exception
 */
function updateImagers(){
	startIceBearSession();
	try {
		database::begin();
		foreach($_POST as $k=>$v){
			if(str_starts_with($k, 'imager_')){
				$parts=explode('_',$k);
				$imagerId=$parts[1];
				$field=$parts[2];
				imager::update($imagerId, array($field=>$v));
			}
		}
		database::commit();
	} catch (Exception $e){
		database::abort();
		throw new Exception($e->getMessage());
	}
}

/**
 * Configure users
 * @throws Exception
 */
function updateUsers(){
	global $createdUsers;
	startIceBearSession();
	try {
		database::begin();
		$users=array();
		$newUsers=array();
		foreach($_POST as $k=>$v){
			if(str_starts_with($k, 'user_')){
				$parts=explode('_',$k);
				$userId=$parts[1];
				$field=$parts[2];
				if('delete'==$field){
					if(isset($users['user'.$userId])){
						unset($users['user'.$userId]);
					}
					database::query(
						'DELETE FROM user WHERE id=:userid',
						array(':userid'=>$userId)
					);
					continue;
				}
				if(!isset($users['user'.$userId])){
					$users['user'.$userId]=array(
							'id'=>$userId,
							'admin'=>0,
							'active'=>0,
							'super'=>0,
							'name'=>'',
							'fullname'=>''
					);
				}
				if('isactive'==$field){
					$users['user'.$userId]['active']=1;
				} else if('isadmin'==$field){
					$users['user'.$userId]['admin']=1;
				} else if('issuper'==$field){
					$users['user'.$userId]['super']=1;
				} else if('name'==$field){
					$users['user'.$userId]['name']=$v;
				} else if('fullname'==$field){
					$users['user'.$userId]['fullname']=$v;
				} 
			} else if(str_starts_with($k, 'create_')){
				$parts=explode('_',$k);
				$userId=$parts[1];
				$field=$parts[2];
				if(!isset($newUsers['new'.$userId])){
					$newUsers['new'.$userId]=array(
							'admin'=>0,
							'active'=>0,
							'super'=>0,
							'name'=>'',
							'fullname'=>'',
							'email'=>''
					);
				}
				if('isactive'==$field){
					$newUsers['new'.$userId]['active']=1;
				} else if('isadmin'==$field){
					$newUsers['new'.$userId]['admin']=1;
				} else if('issuper'==$field){
					$newUsers['new'.$userId]['super']=1;
				} else if('name'==$field){
					$newUsers['new'.$userId]['name']=$v;
				} else if('fullname'==$field){
					$newUsers['new'.$userId]['fullname']=$v;
				} else if('email'==$field){
					$newUsers['new'.$userId]['email']=$v;
				} 
			}
		}
		$adminGroup=usergroup::getByName('Administrators');
		$superGroup=usergroup::getByName('Supervisors');
		$adminGroupId=$adminGroup['id'];
		$superGroupId=$superGroup['id'];
		foreach($users as $u){
			$uid=$u['id'];
			user::update($uid,array(
					'name'=>$u['name'],
					'fullname'=>$u['fullname'],
					'isactive'=>$u['active']
			));
			if(1==$u['admin']){
				if(!usergroup::userisingroup($adminGroupId, $uid)){
					groupmembership::create(array('userid'=>$uid, 'usergroupid'=>$adminGroupId));
				}
			} else {
				database::query(
					'DELETE FROM groupmembership WHERE userid=:userid AND usergroupid=:usergroupid',
					array(':userid'=>$uid, ':usergroupid'=>$adminGroupId)
				);
			}
			if(1==$u['super']){
				if(!usergroup::userisingroup($superGroupId, $uid)){
					groupmembership::create(array('userid'=>$uid, 'usergroupid'=>$superGroupId));
				}
			} else {
				database::query(
					'DELETE FROM groupmembership WHERE userid=:userid AND usergroupid=:usergroupid',
					array(':userid'=>$uid, ':usergroupid'=>$superGroupId)
				);
			}
		}
		foreach($newUsers as $n){
			if(""==$n['name']){ continue; }
			createUser($n['name'], $n['fullname'], $n['email'], $n['active'], $n['admin'], $n['super']);
			$createdUsers=true;
		}
		database::commit();
	} catch (Exception $e){
		database::abort();
		throw new Exception($e->getMessage());
	}
}

/**
 * Configure supervision
 * @throws Exception
 */

function setSupervisorIds(){
	startIceBearSession();
	database::begin();
	try {
		foreach($_POST as $k=>$v){
			if(str_starts_with($k, 'user_')){
				$parts=explode('_',$k);
				$userId=$parts[1];
				$field=$parts[2];
				if('supervisorid'==$field){
					if(empty($v)){ $v=null; }
					user::update($userId, array('supervisorid'=>$v));
				}
			}
		}
		database::commit();
	} catch (Exception $e){
		database::abort();
		throw new Exception($e->getMessage());
	}
}

/**
 * Returns true and starts the Formulatrix importer if the root cron job written earlier has completed, otherwise returns false.
 * @throws Exception
 */
function isRootCronFinished(){
	$installDir=config::getWwwRoot().'/install/';
	if(file_exists($installDir.'rootcrondone')){
		//If we get here, cron did its thing. Formulatrix image stores are mounted and the importer is in the root cron, so enable the importer.
		startIceBearSession();
		database::begin();
		if(1==config::get('fx_hasimagers')){
			config::set('fx_importerrunning',true);
		}
		database::commit();
		return true;
	}	
	return false;
}

function renameInstallDirectory(){
    $wwwRoot=config::getWwwRoot();
    $installDir=$wwwRoot.'/install';
    $doneDir=$wwwRoot.'/installed';
    rename($installDir, $doneDir);
    $f=fopen($doneDir.'/.htaccess','w');
    fwrite($f, 'order deny,allow'.PHP_EOL.'deny from all');
    fclose($f);
}