<?php class ReferenceDataLoader {

    const REFERENCE_DATA_LOCATION='https://icebear.fi/resources/referenceData/';

    /**
     * Triggers the loading of reference data during an upgrade.
     *
     * Caller is responsible for managing database transactions, including commit.
     * @param bool $referenceDataLocation
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public static function loadReferenceDataForUpgrade(bool $referenceDataLocation=false){
        if(!Log::isInited()){
            throw new ServerException('loadReferenceDataForUpgrade: Logging is not inited');
        }
        Log::write(Log::LOGLEVEL_DEBUG, 'In loadReferenceDataForUpgrade');
        static::setReferenceDataLocation($referenceDataLocation);
        static::loadReferenceDataForInstallPart1();
        static::loadReferenceDataForInstallPart2();
        Log::write(Log::LOGLEVEL_DEBUG, 'loadReferenceDataForUpgrade complete');
    }

    /**
     * Loads the first parts of the reference data - specifically, projects and usergroups.
     * Because later parts assume that this is set up, we must do the initial load in two
     * parts.
     *
     * Caller is responsible for managing database transactions, including commit.
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public static function loadReferenceDataForInstallPart1(){
        if(!Log::isInited()){
            throw new ServerException('loadReferenceDataForInstallPart1: Logging is not inited');
        }
        if(empty(static::$referenceDataLocation)){
            static::setReferenceDataLocation();
        }
		Log::debug('Calling loadProjects()');
        static::loadProjects();
		Log::debug('Back from loadProjects()');
		Log::debug('Calling loadUserGroups()');
        static::loadUsergroups();
		Log::debug('Back from loadUsergroups()');

        //give Everyone read on Shared
		Log::debug('Getting Everyone usergroup');
        $everyone=usergroup::getByName(baseusergroup::EVERYONE);
		Log::debug('Getting Shared project');
        $shared=project::getByName(baseproject::SHARED);
		Log::debug('Granting read permission on Shared to Everyone');
        try {
			permission::create(array(
                'usergroupid'=>$everyone['id'],
                'projectid'=>$shared['id'],
                'type'=>'read'
            ));
        } catch(BadRequestException $e){
            if(false===stripos($e->getMessage(), 'already exists')){
                throw $e;
            }
        }
		Log::debug('Granted read permission on Shared to Everyone');
    }


    /**
     * Loads the reference data not handled by loadReferenceDataForInstallPart1.
     *
     * For both updates and new installations, full IceBear user auth and database handling must be
     * available before calling this.
     *
     * Caller is responsible for managing database transactions, including commit.
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public static function loadReferenceDataForInstallPart2(){
        if(!Log::isInited()){
            throw new ServerException('loadReferenceDataForInstallPart2: Logging is not inited');
        }
        if(empty(static::$referenceDataLocation)){
            static::setReferenceDataLocation();
        }
        static::loadContainerTypes();
        static::loadShipmentDestinations();
    }
    
    private static $referenceDataLocation;
    
    private static function setReferenceDataLocation($referenceDataLocation=false){
        if(empty(static::$referenceDataLocation)){
            static::$referenceDataLocation=config::getWwwRoot().'/upgrade/referenceData/';
        } else {
            static::$referenceDataLocation=rtrim($referenceDataLocation,'/').'/';
        }
    }

    /**
     * @throws ServerException
     * @throws ForbiddenException
     * @throws BadRequestException
     * @throws NotFoundException
     */
    public static function loadProjects($data=null){
        Log::write(Log::LOGLEVEL_DEBUG, 'In loadProjects...');
        session::requireAdmin();
        $adminId=session::getUserId();
        if(!$data){ $data=static::getReferenceDataAsArray('projects.json'); }
        if(!isset($data['projects'])){ throw new BadRequestException('Project data does not have a "projects" key'); }
        foreach ($data['projects'] as $project){
            $project['issystem']=1;
            $project['owner']=$adminId;
            if(!isset($project['description'])){ $project['description']='(no description)'; }
            //call to createOrUpdateReferenceDataItem in previous iteration might wipe admin permissions
            session::becomeAdmin();
            static::createOrUpdateReferenceDataItem($project, 'project');
        }
        //createOrUpdateReferenceDataItem might wipe admin permissions
        session::becomeAdmin();
        Log::write(Log::LOGLEVEL_DEBUG, 'loadProjects complete.');
    }


    /**
     * @throws ForbiddenException
     * @throws BadRequestException
     * @throws ServerException
     * @throws NotFoundException
     */
    public static function loadUsergroups($data=null){
        Log::write(Log::LOGLEVEL_DEBUG, 'In loadUsergroups...');
        session::requireAdmin();
        if(!$data){ $data=static::getReferenceDataAsArray('usergroups.json'); }
        if(!isset($data['usergroups'])){ throw new BadRequestException('Usergroup data does not have a "usergroups" key'); }
        foreach ($data['usergroups'] as $group){
            //call to createOrUpdateReferenceDataItem in previous iteration might wipe admin permissions
            session::becomeAdmin();
            static::createOrUpdateReferenceDataItem($group, 'usergroup');
        }
        //createOrUpdateReferenceDataItem might wipe admin permissions
        session::becomeAdmin();
        Log::write(Log::LOGLEVEL_DEBUG, 'loadUsergroups complete.');
    }

    /**
     * Creates container categories and types from the reference data, or updates existing ones.
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public static function loadContainerTypes($data=null){
        Log::write(Log::LOGLEVEL_DEBUG, 'In loadContainerTypes...');
        session::requireAdmin();

        $sharedProject=project::getByName(baseproject::SHARED);
        $sharedProjectId=$sharedProject['id'];

        if(!$data){ $data=static::getReferenceDataAsArray('containertypes.json'); }
        if(!isset($data['containertypes'])){ throw new BadRequestException('Container type data does not have a "containertypes" key'); }
        foreach($data['containertypes'] as $containerType){
            $categoryName=$containerType['containercategory'];
            static::createOrUpdateReferenceDataItem(array(
                    'name'=>$categoryName,
                    'userscancreate'=>1,
                    'projectid'=>$sharedProjectId
                ), 'containercategory');
            $category=containercategory::getByName($categoryName);
            if(!$category){ throw new NotFoundException("Container category \"$categoryName\" should have been created but does not exist"); }
            $containerType['containercategoryid']=$category['id'];
            $containerType['projectid']=$sharedProjectId;
            static::createOrUpdateReferenceDataItem($containerType, 'containertype');
        }
        Log::write(Log::LOGLEVEL_DEBUG, 'loadContainerTypes complete.');
    }

    /**
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    public static function loadShipmentDestinations($data=null){
        Log::write(Log::LOGLEVEL_DEBUG, 'in loadShipmentDestinations...');
        session::requireAdmin();

        if(!$data){ $data=static::getReferenceDataAsArray('synchrotrons.json'); }
        if(!isset($data['synchrotrons'])){ throw new BadRequestException('Synchrotron data does not have a "synchrotrons" key'); }
        foreach ($data['synchrotrons'] as $synchrotron){
            $synchrotron['ismanaged']=1;
            if(isset($synchrotron['geofence']) && is_array($synchrotron['geofence'])){
                $synchrotron['geofence']=implode(',', $synchrotron['geofence']);
            }
            static::createOrUpdateReferenceDataItem($synchrotron, 'shipmentdestination');
        }
        Log::write(Log::LOGLEVEL_DEBUG, 'loadShipmentDestinations complete.');
    }

    /**
     * @param $filename
     * @return mixed
     * @throws BadRequestException
     * @throws ForbiddenException
     * @throws NotFoundException
     * @throws ServerException
     */
    private static function getReferenceDataAsArray($filename){
        $data=corsproxy::doGet(static::REFERENCE_DATA_LOCATION.$filename,[]);
        if(false!==stripos($data, '404 Not Found')){
            throw new NotFoundException("404 Not Found error when loading $filename");
        } else if(empty($data)){
            throw new NotFoundException("Empty data returned when loading $filename");
        }
        $json=json_decode($data, true);
        $key=str_replace('.json','', $filename);
        if(!$json){
            throw new ServerException("Could not parse reference data file $filename to JSON");
        } else if(!isset($json[$key])){
            //synchrotrons.json must have a "synchrotrons" array, for example
            throw new ServerException("Data file $filename does not have a \"$key\" element");
        }
        return $json;
    }

    /**
     * @param $item
     * @param $objectType
     * @throws BadRequestException
     * @throws ServerException
     * @throws ForbiddenException
     */
    private static function createOrUpdateReferenceDataItem($item, $objectType){
        session::requireAdmin();
        if(!class_exists($objectType)){
            throw new BadRequestException("Class $objectType does not exist");
        }
        //Reference data might contain mixed-case keys, force to lowercase
        $item=array_change_key_case($item);

        //Check for existing item with same name, decides whether to update or create
        $itemName=$item['name'];
        $existing=null;
        try {
            $existing=forward_static_call_array(array($objectType, 'getByName'), array($item['name']));
        } /** @noinspection PhpRedundantCatchClauseInspection */
        catch (NotFoundException){
            //Expected if project or usergroup not found
            //Not recognised by IDE due to forward_static_call_array use.
        }
        if($existing){
            Log::write(Log::LOGLEVEL_DEBUG, "$objectType $itemName exists in IceBear, updating");
            $args=array($existing['id'], $item);
            $methodName='update';
        } else {
            Log::write(Log::LOGLEVEL_DEBUG, "$objectType $itemName does not exist in IceBear, creating");
            $args=array($item);
            $methodName='create';
        }
        if(!method_exists($objectType, $methodName)){
            throw new BadRequestException("Class $objectType: Method $methodName does not exist");
        }
        //Finally, create or update
	    forward_static_call_array(array($objectType, $methodName), $args);
	}
    

}