<?php

class simpleweb_setup {
	const SETUP_DIR=".setup";
	const CONFIG_FILE="setup.xml";
	const ADMIN_USERNAME="admin";
	const DEFAULT_REPOSITORY="http://dist.simpleweb.cz/";
	const TEMP_DIR="temp";
	const VERSION="0.1.1";
	private $doc;
	private $temp=null;
	
	public static function main(){
		$i=new simpleweb_setup();
		//Header("Content-type: application/json");
		//Header("Content-disposition: attachment; filename=data.json");
		$r=$i->handleRequest();
		$i->cleanup();
		if(!$r)return;
		die(json_encode($r));
	}
	public function __construct(){
		$this->doc=new DOMDocument("1.0","utf-8");
		$this->doc->formatOutput = true;
	}
	
	public function cleanup(){
		if(!is_null($this->temp)){
			$md5=self::forEachFile($this->temp,function($link,$path){
				unlink($path);
			},function($link,$path){
				rmdir($path);
			});
		}
	}
	
	private function handleRequest(){
		if(isset($_GET['command']) && strtolower($_GET['command'])=='status'){
			return array(
				"version"=>self::VERSION,
				"initialized"=>(is_file(self::SETUP_DIR.DIRECTORY_SEPARATOR.self::CONFIG_FILE)));
		}
		if(!is_file(self::SETUP_DIR.DIRECTORY_SEPARATOR.self::CONFIG_FILE)){
			if(isset($_GET['command']) && strtolower($_GET['command'])=='init'){
				return $this->initialize();
			}
			return;
		}else{
			$this->doc->load(realpath(self::SETUP_DIR.DIRECTORY_SEPARATOR.self::CONFIG_FILE));
			if(isset($_GET['command'])){
				switch($_GET['command']){
					case "package/list": return $this->listPackagesArray();
					case "package/available": return $this->listAvailablePackages();
					case "package/updates": return $this->listUpgradablePackages();
					case "package/all": return $this->listAllPackages();
					case "package/install": return $this->install();
					case "repository/list": return $this->listRepositories();
					case "user/list": return $this->listUsers();
				}
			}
		}
		
	}
	
	private function install(){
		if(!isset($_GET['package']) || !isset($_GET['version'])){
			return array("error"=>true,"message"=>"Invalid request");
		}
		$packages=$this->listAllPackages();
		$package=$_GET['package'];
		$version=$_GET['version'];
		if(!isset($packages[$package]))
			return array("error"=>true,"message"=>"Invalid package");
		if(!isset($packages[$package]['versions'])
			|| array_search($version,$packages[$package]['versions'])===false)
			return array("error"=>true,"message"=>"Invalid version");
		if(!isset($packages[$package]['repository']))	
			return array("error"=>true,"message"=>"Repository for package not found");
			
		$fpattern=$package."-".$version;
		$zip=$this->getTemp().$fpattern.".zip";
		$cwd=$this->getTemp().$fpattern;
		file_put_contents($zip, fopen($packages[$package]['repository']."/".$package."/".$version.".zip", 'r'));
		if(!is_dir($cwd))mkdir($cwd,0777,true);

		//unzip
		$ziptool = new ZipArchive;
		$res = $ziptool->open($zip);
		if ($res === TRUE) {
		  $ziptool->extractTo($cwd);
		  $ziptool->close();
		  $ziptool=null;
		} else{
			return array("error"=>true,"message"=>"Package extraction failed");
		}
		$md5=self::forEachFile($cwd,function($link,$path){
			return(array("link"=>$link,"hash"=>md5_file($path)));
		});
		$md5files=array();
		foreach($md5 as $val){
			$md5files[$val['link']]=$val['hash'];
		}
		
		$collisions=self::forEachFile($cwd,function($link,$path){
			if(is_file($link) && is_file($path)){
				return(array("link"=>$link,"hash"=>md5_file($link)));
			}
		});
		
		foreach($collisions as $key => $val){
			$link=$val['link'];
			$hash=$val['hash'];
			if(isset($md5files[$link]) && $md5files[$link]==$hash){
				unset($collisions[$key]);
				continue;
			}
			$collisions[$key]['remoteHash']=$hash;
		}
		
		if(count($collisions))return array("error"=>true,"message"=>"conflict","files"=>$collisions);
		
		self::forEachFile($cwd,function($link,$path){
			if(!is_dir(pathinfo($link,PATHINFO_DIRNAME)))mkdir(pathinfo($link,PATHINFO_DIRNAME),0777,true);
			if(!is_file($link)){
				copy($path,$link);
			}
		},function($link,$path){
			if(!is_dir($link))mkdir($link,0777,true);
		});
		
		$this->addPackage($package,$version,$md5files);
		$this->saveConfig();
		return array("installed"=>$package);
	}
	private static function forEachFile($cwd,$callback,$directoryCallback=null){
		$dir=array();
		$path=array("");
		$result=array();
		for($x=0;true;){
			$wd[$x]=$cwd;
			$link[$x]="";
			for($y=0;$y<=$x;$y++){
				$wd[$x].=(($path[$y])?DIRECTORY_SEPARATOR:"").$path[$y];
				$link[$x].=$path[$y].(($path[$y])?"/":"");
			}
			if(is_dir($wd[$x])){
				if(!isset($dir[$x]) || is_null($dir[$x])){
					$dirobj=dir($wd[$x]);
					$dir[$x]=array();
					while (false !== ($entry = $dirobj->read())) {
						if($entry!="." && $entry!="..")array_push($dir[$x],$entry);
					}
					$dirobj->close();
				}
				while (sizeof($dir[$x])>0) {
					$entry = array_shift($dir[$x]);
					if($entry!="." && $entry!=".." && is_dir($wd[$x].DIRECTORY_SEPARATOR.$entry)){
						$x++;
						$path[$x]=$entry;
						continue 2;
					}else if(is_file($wd[$x].DIRECTORY_SEPARATOR.$entry)){
						$ret=$callback($link[$x].$entry,$wd[$x].DIRECTORY_SEPARATOR.$entry);
						if($ret)$result[]=$ret;
					}else{
					}
				}
				if(!is_null($directoryCallback)){
					$ret=$directoryCallback($link[$x],$wd[$x]);
					if($ret)$result[]=$ret;
				}
			}
			$dir[$x]=null;
			$x--;
			if($x<0)break;
		}
		return $result;
	}
	
	private function initialize(){
		$this->doc->appendChild($this->doc->createElement('setup'));
		$password=self::randPass();
		$this->addUser(self::ADMIN_USERNAME,$password);
		$this->addRepository(self::DEFAULT_REPOSITORY,"default");
		$this->doc->documentElement->appendChild($this->doc->createElement("tempDirectory",self::TEMP_DIR));
		//$this->addPackage("simpleweb",1,array("blabla"=>"123123","/asa/sadf"=>"sdafsd"));
		$this->saveConfig();
		return array("username"=>self::ADMIN_USERNAME,"password"=>$password);
	}
	
	private function getTemp(){
		if(is_null($this->temp)){
			$dl=$this->doc->documentElement->getElementsByTagName("tempDirectory");
			if($dl->length==0){
				$temp=self::TEMP_DIR;
			}else{
				$temp=$dl->item(0)->nodeValue;
			}
			$this->temp=self::SETUP_DIR.DIRECTORY_SEPARATOR.self::TEMP_DIR.DIRECTORY_SEPARATOR;
			if(!is_dir($this->temp))mkdir($this->temp,0777,true);
		};
		return $this->temp;
	}
	
	private function addUser($username,$password){
		$this->addConfigEntry("users","user",array("username"=>$username,"password"=>crypt($password,'$6$rounds=50000$'.self::randpass(16).'$')));
	}
	
	private function addRepository($url,$name){
		$this->addConfigEntry("repositories","repository",array("name"=>$name,"url"=>$url));
	}
	
	private function addPackage($name,$version,array $hashes){
		$node=$this->addConfigEntry("packages","package",array("name"=>$name,"version"=>$version));
		foreach($hashes as $key => $val){
			$el=$this->doc->createElement("file");
			$el->setAttribute("path",$key);
			$el->setAttribute("hash",$val);
			$node->appendChild($el);
		}
	}
	
	private function listRepositories(){
		return $this->listConfigEntries("repositories","repository");
	}
	
	private function listUsers(){
		$users=$this->listConfigEntries("users","user");
		foreach($users as $key=>$val){
			if(isset($val['password']))unset($users[$key]['password']);
		}
		return $users;
	}
	
	private function listPackages(){
		return $this->listConfigEntries("packages","package");
	}
	
	private function listPackagesArray(){
		$pcks=$this->listPackages();
		$ret=array();
		foreach($pcks as $val){
			$ret[$val['name']]=$val;
			unset($ret[$val['name']]['name']);
		}
		return $ret;
	}
	
	private function auth($username,$password){
		$xpath = new DOMXPath($this->doc);
		$entries = $xpath->query($query);
	}
	
	private function listConfigEntries($groupName,$elName){
		$dl=$this->doc->documentElement->getElementsByTagName($groupName);
		$ret=array();
		if($dl->length>0){
			$recs=$dl->item(0)->getElementsByTagName($elName);
			for($x=0;$x<$recs->length;$x++){
				$item=$recs->item($x);
				if($item->hasAttributes()){				
					$prms=array();
					foreach ($item->attributes as $attr) {
						$prms[$attr->nodeName]=$attr->nodeValue;
					}
					$ret[]=$prms;
				}
			}
		}
		return $ret;
	}
	
	private function &addConfigEntry($groupName,$elName,array $attrs){
		$rel;
		$dl=$this->doc->documentElement->getElementsByTagName($groupName);
		if($dl->length==0){
			$rel=$this->doc->createElement($groupName);
			$this->doc->documentElement->appendChild($rel);
		}else{
			$rel=$dl->item(0);
		}
		$repo=$this->doc->createElement($elName);
		foreach($attrs as $key => $val){
			$repo->setAttribute($key,$val);
		}
		$rel->appendChild($repo);
		return $repo;
	}

	
	private function saveConfig(){
		if(!is_dir(self::SETUP_DIR)){
			mkdir(self::SETUP_DIR,0777,true);
		}
		$this->doc->save(self::SETUP_DIR.DIRECTORY_SEPARATOR.self::CONFIG_FILE);
	}
	
	private function listUpgradablePackages(){
		$packages=$this->listAllPackages();
		$installed=$this->listPackages();
		$upgradable=array();
		foreach($installed as $key => $val){
			if(isset($val['name']) && isset($val['version'])
				&& isset($packages[$val['name']]) && isset($packages[$val['name']]['latestVersion'])
				&& $val['version'] != $packages[$val['name']]['latestVersion']){
				$upgradable[$val['name']]=$packages[$val['name']];
				$upgradable[$val['name']]['currentVersion']=$val['version'];
			}
		}
		return $upgradable;
	}
	
	private function listAvailablePackages(){
		$packages=$this->listAllPackages();
		$installed=$this->listPackages();
		foreach($installed as $key => $val){
			if(isset($val['name']) && isset($packages[$val['name']])){
				unset($packages[$val['name']]);
			}
		}
		return $packages;
	}
	
	private function listAllPackages(){
		$repos=$this->listRepositories();
		$packages=array();
		foreach($repos as $repo){
			if(isset($repo['url'])){
				$np=self::listRepositoryPackages($repo['url']);
				foreach($np as $key => $val){
					if(!isset($packages[$key])){
						$packages[$key]=$val;
						$packages[$key]['repository']=$repo['url'];
					}
				}
			}
		}
		return $packages;
	}
	
	private static function listRepositoryPackages($url){
		$url=$url."/repository.php";
		$json = file_get_contents($url);
		return json_decode($json, TRUE);
	}
	
	/**
	 * Generate random password
	 * 
	 * @param int $length
	 * @return string
	 */
	private static function randPass($length=10){
		do{
			$chars=array(0,1,2,3,4,5,6,7,8,9,
				'a','b','c','d','e','f','g','h','i','j','k','m','n','p','q','r','s','t','u','v','w','x','y','z',
				'A','B','C','D','E','F','G','H','I','J','K','M','N','P','Q','R','S','T','U','V','W','X','Y','Z');
			$max=count($chars)-1;
			$str='';
			for($x=0;$x<$length;$x++){
				$str.=$chars[rand(0,$max)];
			}
		}while(!preg_match('/^\S*(?=\S*[a-z])(?=\S*[A-Z])(?=\S*[\d])\S*$/',$str));
		return $str;
	}

}

simpleweb_setup::main();
?><!-- ?xml version='1.0' encoding='utf-8'? -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="cs">
	<head>
		<meta http-equiv="Content-Language" content="cs"/>
		<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
		<meta http-equiv="Cache-Control" content="must-revalidate, post-check=0, pre-check=0"/>
		<meta http-equiv="Pragma" content="public"/>
		<meta http-equiv="Cache-Control" content="no-cache"/>
		<meta http-equiv="Pragma" content="no-cache"/>
		<meta http-equiv="Expires" content="-1"/>
		<meta name="author" content="Pavel Šedek &lt;pavel dot sedek at schedek dot com&gt;"/>
		<meta name="copyright" content="&copy; Schedek, s.r.o. 2013"/>
		<meta http-equiv="imagetoolbar" content="no"/>
		<meta name="autosize" content="off"/>

		<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
		<script src="http://code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
		
		<link type="text/css" rel="stylesheet" href="//cdn.jsdelivr.net/bootstrap/2.3.2/css/bootstrap.min.css" />
		<script type="text/javascript" src="//cdn.jsdelivr.net/bootstrap/2.3.2/js/bootstrap.min.js"></script>
		<script type="text/javascript" src="//cdn.jsdelivr.net/bootstrap/2.3.2/js/bootstrap-tab.js"></script>
		
		<style type="text/css">
			.nodisp{display: none;}
			.packages {width: 300px; float:left;}
			.packages select {width: 280px;}
		</style>
		<title>Simpleweb setup utility</title>
		
	</head>
	<body>
		<div class="alert alert-info" id="alert-loading">
			<strong>Loading...</strong> The application is starting
		</div>
		<div class="alert nodisp" id="alert-init">
			<strong>Initialization</strong> The setup utility requires first time initialization. In this phase, the setup will generate a password for you, which you can later use for managing packages
			<button class="btn btn-warning" onclick="s_init();">Initialize</button>
		</div>
		<div class="alert alert-success nodisp" id="alert-init-ok">
			<button type="button" class="close" data-dismiss="alert">&times;</button>
			<strong>Initialization succeeded.</strong> Please notice following credentials:
			<table>
				<tr>
					<th>Username: </th>
					<td id="alert-init-username"></td>
				</tr>
				<tr>
					<th>Password: </th>
					<td id="alert-init-password"></td>
				</tr>
			</table>
		</div>
		<div class="nodisp" id="ui">
			<div class="tabbable">
			  <ul class="nav nav-tabs">
				<li class="active"><a href="#tab1" data-toggle="tab">Packages</a></li>
				<li><a href="#tab2" data-toggle="tab">Updates</a></li>
				<li><a href="#tab3" data-toggle="tab">Repositories</a></li>
				<li><a href="#tab4" data-toggle="tab">Users</a></li>
				<li><a href="#tab5" data-toggle="tab">About</a></li>
			  </ul>
			  <div class="tab-content">
				<div class="tab-pane active" id="tab1">
					<div class="packages">
						Installed <br />
						<select id="packages-list" multiple="true" size="20"></select>
					</div>
					<div class="packages">
						Available <br />
						<select id="packages-available" multiple="true" size="20"></select>
						<button class="btn btn-success" onclick="s_install();">Install selected</button>
					</div>
				</div>
				<div class="tab-pane" id="tab2">
					<div class="packages">
						Updates <br />
						<select id="packages-updates" multiple="true" size="20"></select>
					</div>
				</div>
				<div class="tab-pane" id="tab3">
				</div>
				<div class="tab-pane" id="tab4">
				</div>
				<div class="tab-pane" id="tab5">
					<h1>Simpleweb setup tool</h1>
					<p>Version: <?echo simpleweb_setup::VERSION?></p>
				</div>
			  </div>
			</div>
		</div>
		<script type="text/javascript">
		/*<![CDATA[*/
$(function(){
	$.getJSON('?command=status', function(data) {
		if(data.initialized){
			s_loadui();
		}else{
			$('#alert-init').show();
		}
		$('#alert-loading').hide();
	});	
});
function s_init(){
	$.getJSON('?command=init', function(data) {
//		alert(data.username);
		$('#alert-init-username').text(data.username);
		$('#alert-init-password').text(data.password);
		$('#alert-init').hide();
		$('#alert-init-ok').show();
		s_loadui();
	});	
}
function s_loadui(){
	s_loadpackages();
	$('#ui').show();
}
function s_loadpackages(){
	s_packages('available');
	s_packages('list');
	s_packages('updates');
}
function s_packages(els){
	$('#packages-'+els).find('option').remove()
	$.ajax({
		type: 'GET',
		url: '?command=package/'+els,
		dataType: 'json',
		success: function(data) {
			$.each(data, function(key, val) {
				lv=(val.version!==undefined)?val.version:val.latestVersion;
				if(els=='updates')lv+=", installed: "+val.currentVersion;
				$('#packages-'+els)
				 .append($("<option></option>")
				 .attr("value",key)
				 .attr("version",lv)
				 .text(key+" ("+lv+")")); 
			});
		},
		data: {},
		async: false
	});
}
function s_install(){
	$("#packages-available option:selected").each(function(){
		s_installPackage($(this).val(),$(this).attr('version'));
	});
	s_loadpackages();
}
function s_installPackage(packageName,version){
	$.ajax({
		type: 'GET',
		url: '?command=package/install',
		dataType: 'json',
		success: function(data) {
			//data.installed
		},
		data: {"package":packageName,"version":version},
		async: false
	});
}

		/*]]>*/
		</script>
	</body>
</html>