<?php
namespace GO\Dropbox;
require_once __DIR__ . '/lib/Dropbox/autoload.php';

use \Dropbox as dbx;





class DropboxModule extends \GO\Professional\Module {
	
	public function package() {
		return '3rd party API';
	}

	const DBX_ROOT = '/'; //todo this can't be changed because it will break dbxToGoFolder and vice versa
	const GO_ROOT = '/Dropbox';

	private static $client;
	
	public static function initListeners() {
		\GO\Base\Model\User::model()->addListener('delete', "GO\Dropbox\DropboxModule", "userDelete");
	}
	
	public function depends() {
		return array('files');
	}
	
	public function install() {
		parent::install();
		
		$cron = new \GO\Base\Cron\CronJob();
		
		$cron->name = 'Dropbox sync';
		$cron->active = true;
		$cron->runonce = false;
		$cron->minutes = '*';
		$cron->hours = '*';
		$cron->monthdays = '*';
		$cron->months = '*';
		$cron->weekdays = '*';
		$cron->job = 'GO\\Dropbox\\Cron\\Sync';

		$cron->save();
	}
	
	public function uninstall() {
		parent::uninstall();
		
		$cron = \GO\Base\Cron\CronJob::model()->findSingleByAttribute('job','GO\\Dropbox\\Cron\\Sync');
		
		if(!$cron) // No cronjob found, so nothing needs to be deleted.
			return true;
		
		if(!$cron->delete()) // Try to delete the cronjob, if it's not possible then throw an exception.
			Throw new \Exception('The Dropbox systemtask could not be deleted automatically. Please try to delete it manually in the "System tasks module."');
		else
			return true;
	}
	
	
	public static function userDelete(\GO\Base\Model\User $user){
		$dbxUser = Model\User::model()->findByPk($user->id);
		
		if($dbxUser){
			$dbxUser->delete();
		}
	}

	/**
	 * 
	 * @return \Dropbox\Config
	 */
	public static function getConfig() {
		
//		if(empty(\GO::config()->dropbox_key) || empty(\GO::config()->dropbox_secret)){
//			throw new \Exception("Please set \$config['dropbox_secret'] and \$config['dropbox_key']");
//		}
			
		$appInfo = new dbx\AppInfo("8td4s44bhfen962", "p3crujbpo7emn3z", dbx\AccessType::AppFolder());
		return new dbx\Config($appInfo, "Group-Office " . \GO::config()->version);
	}

	/**
	 * 
	 * @return \Dropbox\Client
	 */
	public static function getClient(\GO\Base\Model\User $user = null) {

		if (!isset($user))
			$user = \GO::user();

		if (!isset(self::$client[$user->id])) {
			$dbxUser = Model\User::model()->findByPk($user->id);
			if(!$dbxUser){
				throw new \Exception("User isn't connected");
			}
			if(empty($dbxUser->access_token))
			{
				throw new \Exception("Access token was cleared. Please reconnect.");
			}
			self::$client[$user->id] = new dbx\Client(self::getConfig(), dbx\AccessToken::deserialize($dbxUser->access_token));
		}
		return self::$client[$user->id];
	}

	/**
	 * 
	 * @param \GO\Base\Model\User $user
	 * @return \GO\Files\Model\Folder
	 */
	public static function getHomeFolder(\GO\Base\Model\User $user = null) {
		if (!isset($user))
			$user = \GO::user();
		return \GO\Files\Model\Folder::model()->findByPath('users/' . $user->username  . DropboxModule::GO_ROOT, true);
	}

	public static function getGroupOfficeSnapShot(\GO\Files\Model\Folder $folder, $sort = true) {

		$snapshot = array();
		$stmt = $folder->files();

		foreach ($stmt as $file) {
			$snapshot[strtolower($file->path)] = array('mtime' => $file->mtime, 'path' => $file->path);
		}

		$stmt = $folder->folders();
		foreach ($stmt as $childFolder) {
			$snapshot[strtolower($childFolder->path)] = array('mtime' => $childFolder->mtime, 'path' => $childFolder->path);
			$snapshot = array_merge($snapshot, self::getGroupOfficeSnapShot($childFolder, false));
		}

		if ($sort)
			ksort($snapshot);

		return $snapshot;
	}

	public static function getDropboxSnapshot(\GO\Base\Model\User $user = null, $dropboxPath = DropboxModule::DBX_ROOT, $sort = true) {

		$snapshot = array();

		$dbxClient = DropboxModule::getClient($user);
		$folderMetaData = $dbxClient->getMetadataWithChildren($dropboxPath);

		if (!isset($folderMetaData) && $dropboxPath == DropboxModule::DBX_ROOT) {
			$folderMetaData = $dbxClient->createFolder(DropboxModule::DBX_ROOT);

			if (!isset($folderMetaData))
				throw new \Exception("Could not create root folder " . DropboxModule::DBX_ROOT);
		}


		if (isset($folderMetaData['contents'])) {
			foreach ($folderMetaData['contents'] as $item) {

				$snapshot[strtolower($item['path'])] = array('mtime' => strtotime($item['modified']), 'path' => $item['path']);

				if ($item['is_dir']) {
					$snapshot = array_merge($snapshot, self::getDropboxSnapshot($user, $item['path'], false));
				}
			}
		}

		if ($sort)
			ksort($snapshot);

		return $snapshot;
	}
	
	public static function dbxToGoPath($dbxPath, \GO\Base\Model\User $user){
		return 'users/'.$user->username.DropboxModule::GO_ROOT.'/'.substr($dbxPath, strlen(DropboxModule::DBX_ROOT));					
	}

	public static function goToDbxPath($goPath, \GO\Base\Model\User $user){
		return DropboxModule::DBX_ROOT.substr($goPath,strlen('users/'. $user->username.DropboxModule::GO_ROOT.'/'));
	}
	
//	public static function dbxPathIsInGo($dbxPath){
//		return strtolower(substr($dbxPath,0,strlen(DropboxModule::DBX_ROOT)))==strtolower(DropboxModule::DBX_ROOT);					
//	}
	
	
	
	public static function sync(\GO\Base\Model\User $user, $reset=false){
		
		\GO::session()->runAsRoot();
		\GO::setMaxExecutionTime(0);
		
		self::log("Dropbox sync for user ".$user->username);
		

		$dbxUser = Model\User::model()->findByPk($user->id);
		if($reset){
			$dbxUser->delta_cursor="";
			
			$folder = DropboxModule::getHomeFolder($user);
			$folder->removeChildren();
		}
		
		$dbxClient = DropboxModule::getClient($user);

		self::log("Getting Dropbox Changes");
		
		$hasMore=true;
		while($hasMore){
			
			$delta = $dbxClient->getDelta(empty($dbxUser->delta_cursor)?null : $dbxUser->delta_cursor	);
//			var_dump($delta);
//			exit();
			if(!isset($delta)){
				throw new \Exception("Could not get delta from Dropbox!");
			}
			$hasMore=$delta['has_more'];
			
			foreach($delta['entries'] as $entry){
				flush();

				//check if it's in the Group-Office sync root
//				if(DropboxModule::dbxPathIsInGo($entry[0])){
					
					//$entry[1]['path'] = with case. Otherwise we just have a string to lowered path for deleting
					$dbxPath = isset($entry[1]['path']) ? $entry[1]['path'] : $entry[0];
					$goPath = DropboxModule::dbxToGoPath($dbxPath, $user);
					
					if(!isset($entry[1])){
						//should be deleted
					

						$file = \GO\Files\Model\File::model()->findByPath($goPath,false);
						if($file){
							self::log("Deleting file on Group-Office ".$goPath);

							$file->delete();
						}else
						{
							$folder = \GO\Files\Model\Folder::model()->findByPath($goPath, false,array(),false);
							if($folder){
								self::log("Deleting folder on Group-Office ".$goPath);
								$folder->delete();
							}else
							{
								self::log("Could not find path for delete file on Group-Office ".$goPath);
							}
						}
					}else	if($entry[1]['is_dir']){
						self::log("Create folder on Group-Office ".$entry[1]['path']." -> ".$goPath);
						$folder = \GO\Files\Model\Folder::model()->findByPath($goPath, true);
					}else
					{
					
						self::log("Download from Dropbox ".$entry[1]['path']." -> ".$goPath);
						$folder = \GO\Files\Model\Folder::model()->findByPath(dirname($goPath), true, array(), false);

						$name = \GO\Base\Fs\File::utf8Basename($goPath);
						$path = $folder->fsFolder->createChild($name)->path();
						$f = fopen($path, "w+b");
						$fileMetadata = $dbxClient->getFile($entry[0], $f);
						fclose($f);

						touch($path, strtotime($fileMetadata['modified']));

						//todo needs optimize
						$folder->syncFilesystem();
					}
				}
			}
//		}
		
		$dbxUser->delta_cursor=$delta['cursor'];
		$dbxUser->save();
		
		
		
		self::log("Applying Group-Office changes to Dropbox");
		
		$folder = DropboxModule::getHomeFolder($user);
		$goSnapshot = DropboxModule::getGroupOfficeSnapShot($folder);
		
		
		
		$dbxSnapshot = DropboxModule::getDropboxSnapshot($user);
//		var_dump($dbxSnapshot);
		foreach($goSnapshot as $path=>$props){
			
			$dbxPath = DropboxModule::goToDbxPath($props['path'], $user);
			$dbxPathToLower = strtolower($dbxPath);
			if(!isset($dbxSnapshot[$dbxPathToLower]) || $dbxSnapshot[$dbxPathToLower]['mtime']<$props['mtime']){
				if(is_file(\GO::config()->file_storage_path.$props['path'])){
					
					self::log("Upload to Dropbox ".$path." -> ".$dbxPath);
					
					$inStream=fopen(\GO::config()->file_storage_path.$props['path'],'r');					
					$meta = $dbxClient->uploadFile($dbxPath,  dbx\WriteMode::force(), $inStream);
					
					if(!isset($meta))
						throw new \Exception("Failed to create file '".$dbxPath."' on Dropbox");
					
				}elseif(!isset($dbxSnapshot[$dbxPathToLower])){
					self::log("Create folder on Dropbox ".$path." -> ".$dbxPath);
					
					$folderMetaData = $dbxClient->createFolder($dbxPath);
					
					if(!isset($folderMetaData))
						throw new \Exception("Failed to create folder '".$dbxPath."' on Dropbox");
				}
			}
		}
		
		//reverse sort for deleting so that deeper files are deleted first.
		krsort($dbxSnapshot);
		foreach($dbxSnapshot as $path=>$props){
			$goPath = strtolower(DropboxModule::dbxToGoPath($path, $user));
			if(!isset($goSnapshot[$goPath])){
				self::log("Deleting on dropbox ".$path);			
				
				if(!$dbxClient->delete($path)){
					throw new \Exception("Failed to delete '".$path."'");
				}
			}
		}
		
		//get delta again so we won't process our own changes next sync
		$delta = $dbxClient->getDelta($dbxUser->delta_cursor);
		$dbxUser->delta_cursor=$delta['cursor'];
		$dbxUser->save();
		
		self::log("Done!");

	}
	
	public static function log($text){
		
		\GO::debug($text);
		
		if(!empty(\GO::config()->dropbox_log)){
			$user = isset(\GO::session()->values['username']) ? \GO::session()->values['username'] : 'notloggedin';

			$text = "[$user] ".str_replace("\n","\n[$user] ", $text);

			file_put_contents(\GO::config()->file_storage_path . 'log/dropbox.log', $text . "\n", FILE_APPEND);
		}
		
		
	}
}