<?php 

/**
 * This class is used for management of the homepage bricks that are available to users.
 * New bricks can be added and existing ones removed.
 */

class homepagebrick {
	
	private static string $adminSelect='SELECT SQL_CALC_FOUND_ROWS id,name,height,width FROM homepagebrick WHERE 1=1 ';
	private static string $normalSelect='SELECT SQL_CALC_FOUND_ROWS id,name,height,width FROM homepagebrick WHERE adminonly=false ';
	private static function getSelect(): string {
		if(session::isAdmin()){ return static::$adminSelect; }
		return static::$normalSelect;
	}

	/**
	 * @param int $id
	 * @return array
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public static function getById(int $id): array {
		$sqlStatement=static::getSelect().' AND id=:id';
		$parameters=array(':id'=>$id);
        return database::queryGetOne($sqlStatement, $parameters);
	}

	/**
	 * @param string $name
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public static function getByName(string $name): ?array {
	    $sqlStatement=static::getSelect().' AND name=:name';
	    $parameters=array(':name'=>$name);
        return database::queryGetOne($sqlStatement, $parameters);
	}

	/**
	 * @param string $key
	 * @param string $value
	 * @param array $request
	 * @throws BadRequestException
	 */
	public static function getByProperty(string $key, string $value, array $request=[]): ?array {
		throw new BadRequestException('getByProperty not implemented on homepagebrick');
	}

	/**
	 * @param array $keyValuePairs
	 * @param array $request
	 * @return array|null
	 * @throws BadRequestException
	 */
	public static function getByProperties(array $keyValuePairs, array $request=[]): ?array {
		throw new BadRequestException('getByProperties not implemented on homepagebrick');
	}

	/**
	 * @param array $request
	 * @return array|null
	 * @throws BadRequestException
	 * @throws ServerException
	 */
	public static function getAll(array $request=[]): ?array {
		$sqlStatement=static::getSelect();
		$parameters=array();
        return database::queryGetAll($sqlStatement, $parameters);
	}

	/**
	 * @param int $id
	 * @param array $request
	 * @return array
	 * @throws BadRequestException
	 */
	public static function update(int $id, array $request=[]): array {
		throw new BadRequestException('homepagebrick::update not implemented');
	}

    /**
     *
     * @noinspection SqlInsertValues
     */
    /**
     * @throws BadRequestException
     * @throws ServerException
     */
    public static function parseBrickFiles(): void {
        if(!Log::isInited()){
            throw new ServerException('homepageBrick::parseBrickFiles - log not init()ed');
        }
	    $dir=config::getWwwRoot().'/client/bricks/';
	    $files=scandir($dir);
	    foreach($files as $f){
	        if(0!==@substr_compare($f, '.js', -3)){ continue; }
            Log::info('Parsing brick file '.$f);
	        $brick=array(
	            'name'=>'',
	            'title'=>'',
                'description'=>'',
                'version'=>1,
                'height'=>1,
	            'width'=>1,
	            'adminOnly'=>false
	        );
            $keys=array_keys($brick);
	        $lines=file($dir.$f);
	        foreach($lines as $line){
                $line=trim($line);
                $line=rtrim($line,',');
                $parts=explode(':',$line,2);
                if(2!==count($parts)){ continue; }
                $key=trim($parts[0],"\"\s");
                $value=trim(trim($parts[1]),'"');
                if(0===stripos($value,'function')) {
                    break;
                } else if(in_array($key,['version','height','width']) && is_numeric($value)){
                    $value=1*$value;
                } else if(in_array(strtolower($value),['true','yes','1'])){
                    $value=true;
                } else if(in_array(strtolower($value),['false','no','0'])){
                    $value=false;
                }
                if(in_array($key, $keys)){
                    Log::debug("$key=$value");
                    $brick[$key]=$value;
                }
	        }
            $brick['adminonly']=$brick['adminOnly'];
            unset($brick['adminOnly']);
            $hasError=false;
            foreach (['name','title','description'] as $key){
                if(empty($brick[$key])){
                    Log::warn("$key is empty");
                    $hasError=true;
                }
            }
            foreach (['height','width'] as $key){
                if(!is_int($brick[$key]) || 1>$brick[$key] || 3<$brick[$key]){
                    Log::warn("$key must be 1, 2, or 3");
                    $hasError=true;
                }
            }
            if($brick['name'].'.js'!==$f){
                Log::warning('Brick name '.$brick['name'].' does not match filename '.$f);
                $hasError=true;
            }
            if($hasError){
                Log::warning('Not registering brick into database');
                continue;
            }
	        $result=database::queryGetOne('SELECT * FROM homepagebrick WHERE name=:name', array(':name'=>$brick['name']));
	        if($result){
	            //update
                Log::info('Brick exists, updating');
	            $parts=array();
	            $params=array();
	            foreach($brick as $k=>$v){
	                if(empty($v) && $k!='adminonly'){ $v=''; }
	                $parts[]=$k.'=:'.$k;
	                $params[':'.$k]=$v;
	            }
                $sql='UPDATE homepagebrick SET '.implode(', ',$parts).' WHERE name=:name2';
	            $params[':name2']=$brick['name'];
                database::query($sql, $params);
	        } else {
	            //insert new
                Log::info('Brick does not exist, creating');
	            $keys=array();
	            $values=array();
	            $params=array();
	            foreach($brick as $k=>$v){
	                if(empty($v)){ continue; }
	                $keys[]=$k;
	                $values[]=':'.$k;
	                $params[':'.$k]=$v;
	            }
	            $sql='INSERT INTO homepagebrick ('.implode(', ',$keys).') VALUES ('.implode(', ',$values).')';
                database::query($sql, $params);
	        }
	    }
        Log::info('Purging cached brick JavaScript');
        homepagebrick::purgeCachedHomepageBricksJavascript();
    }

	/**
	 * @return string
	 * @throws BadRequestException
	 * @throws ServerException
	 */
    public static function getCachedHomepageBricksJavascriptPath(): string {
        $path=rtrim(config::get('core_filestore'),'/').'/cache';
        if(!file_exists($path) &&!@mkdir($path, 0777, true)){
            throw new ServerException('Could not create cache location for homepage bricks: '.$path);
        }
        return $path.'/homepagebricks.js';
    }

	/**
	 * @return bool
	 * @throws BadRequestException
	 * @throws ServerException
	 */
    public static function purgeCachedHomepageBricksJavascript(): bool {
        $path=static::getCachedHomepageBricksJavascriptPath();
        return @unlink($path);
    }

    /**
     * @throws ServerException
     * @throws BadRequestException
     */
    public static function echoHomepageBricksJavascript(): void {
        $path=static::getCachedHomepageBricksJavascriptPath();
        if(!file_exists($path)){
            static::writeCachedHomepageJavascript();
        }
        $stream=@fopen($path,'r');
        if(!$stream){
            throw new ServerException('Could not open cached homepage bricks script file for reading: '.$path);
        }
        @fpassthru($stream);
        @fclose($stream);
    }

    /**
     * @throws ServerException|BadRequestException
     */
    private static function writeCachedHomepageJavascript(): void {
        $cachedJsPath = static::getCachedHomepageBricksJavascriptPath();
        $bricksDir = dirname(__DIR__, 2) . '/client/bricks/';
        $files = scandir($bricksDir);
        $cacheFile = @fopen($cachedJsPath, 'w');
        if (!$cacheFile) {
            throw new ServerException('Could not open cached homepage bricks script file for writing: ' . $cachedJsPath);
        }
        foreach ($files as $file) {
            if (0 === @substr_compare($file, '.js', -3)) { //string_ends_with is PHP8-only, will break some installations
                $handle = @fopen($bricksDir . $file, 'r');
                if (!$handle) {
                    throw new ServerException('Could not open brick script file for reading: ' . $bricksDir . $file);
                }
                $js = @file_get_contents($bricksDir . $file);
                if (!$js) {
                    throw new ServerException('Could not read contents of brick script file: ' . $bricksDir . $file);
                }
                if (!@fwrite($cacheFile, $js . PHP_EOL . PHP_EOL)) {
                    throw new ServerException("Could not write JS block to bricks script file: \n$cachedJsPath");
                }
            }
        }
        $bricks = homepagebrick::getAll(['all' => 1]);
        if ($bricks) {
            $js = '';
            foreach ($bricks['rows'] as $brick) {
                if(file_exists($bricksDir.$brick['name'].'.js')){
                    $js .= 'Homepage["bricks"]["' . $brick['name'] . '"]["id"]=' . $brick['id'] . ';' . PHP_EOL;
                } else {
                    $msg='Bricks mismatch: Database has brick '.$brick['name'].' but file '.$brick['name'].'.js does not exist in '.str_replace("\\",'/',$bricksDir);
                    $js.="if(console){ console.log(\"$msg\"); }".PHP_EOL;
                }
            }
            if (!@fwrite($cacheFile, $js . PHP_EOL . PHP_EOL)) {
                throw new ServerException("Could not write JS ID-name block to bricks script file: \n$cachedJsPath");
            }
        }

        $js = implode("\n", [
            '//Bind all brick functions to the brick object',
            'Object.values(Homepage["bricks"]).forEach(function (brick){',
            '    Object.keys(brick).forEach(function (k){',
            '        if("function"==typeof brick[k]){',
            '            brick[k]=brick[k].bind(brick);',
            '        }',
            '    });',
            '});'
        ]);
        if (!@fwrite($cacheFile, $js . PHP_EOL . PHP_EOL)) {
            throw new ServerException("Could not write JS function-binding block to bricks script file: \n$cachedJsPath");
        }
        @fclose($cacheFile);
    }
}