<?php

/**
 * Performs data migration from other systems to Atmail6
 *
 * Requires an adapter object passed to it that is used
 * to pull the required information from the other system
 *
 * @author Brad Kowalczyk <brad@staff.atmail.com>
 */
 
define('MY_APP_ROOT', dirname(dirname(dirname(__FILE__))) . "/");
define('FAILED_USER_LOG', MY_APP_ROOT . "log/migration-fail.log");
        
        
class Atmail_Migrate {
 
	const USER_EXISTS = 1;
	const USER_CREATE_OK = 2;
	const USER_CREATE_FAIL = 3;
	
	protected $db;
	private $_currentUser;
	private $_usersFailed = array();
	private $_totalUsers = 0;
	private $_totalEmails = 0;
	private $_totalContacts = 0;
    private $_totalContactGroups = 0;
	private $_totalCalItems = 0;
	private $_api;
	private $_migrateAdapter;
    private $_contactModel;
	private $_mode;
	
	private $_systemContactGroups = array("shared", "remembered", "favourites");
    
    
	public function __construct($adapter, $mode)
	{
		if (!($adapter instanceof Atmail_Migrate_Adapter_Interface)) {
			throw Exception("Adapter must implement Atmail_Migrate_Adapter_Interface");
		}

        $this->_mode = $mode;
        
		// Get database configuration
		try {
			$dbConfig = new Zend_Config_Ini(MY_APP_ROOT . "config/dbconfig.ini", 'production');
		} catch (Exception $e) {
			print_r($e);
			exit;
		}
		
		$this->db = Zend_Db::Factory($dbConfig->database);
		$this->_migrateAdapter = $adapter;
		$adminInfo = $this->db->select()
								 ->from('AdminUsers')
								 ->where("UMasterAdmin = 1")
								 ->query()
								 ->fetchAll();
	
		$this->_api = new api( array('directApi' => 1, 'Username' => $adminInfo[0]['Username']) );
		//$this->_calendar = new calendar(array('TimeZone' => 'GMT', 'URL' => $url, 'Account' => $username, 'Password' => $password, 'Calendar' => "calendar", 'home', 'largeInstall' => 0));
	}
	
	public function start()
	{
		if ($this->_mode == "server" || $this->_mode == "webmail") {
		    while (false !== $user = $this->_migrateAdapter->fetchUser()) {
                
                $this->_currentUser = $user;
                
			    if ($this->_addUser($user) == self::USER_CREATE_OK) {
                    
				    if ($this->_mode == "server") {
				        // Now fetch the email for the user
				        $this->_migrateEmail();
				    }
                    
				    // Now fetch their contacts
				    $this->_migrateContacts();
                
					// Now fetch Contact Groups
                    $this->_migrateContactGroups();
                    
				    // Now fetch their calendar data
				    $this->_migrateCalItems();
			    }
			}
		} elseif ($this->_mode == "login") {
		    $this->_currentUser = $this->_migrateAdapter->fetchUser();
            // Create the contacts model object for this user
            $user = $this->_currentUser->name();
            $this->_p("Migrating data only for $user\n");
            $this->_migrateSettings($user);
            $this->_contactsModel = new contacts(array("Account" => $user));
            $this->_migrateContactGroups($user);
		    $this->_migrateContacts($user);
		    $this->_migrateCalItems($user);
		}
		
		$this->_p("\nMigration Complete.\nTotal Users: {$this->_totalUsers}\nTotal Emails: {$this->_totalEmails}\nTotal Contacts: {$this->_totalContacts}\nTotal Contact Groups: {$this->_totalContactGroups}\nTotal Calendar Items: {$this->_totalCalItems}\n\n");
		if ($count = count($this->_usersFailed)) {
			if (file_exists(FAILED_USER_LOG)) {
				unlink(FAILED_USER_LOG);
			}
			file_put_contents(FAILED_USER_LOG, join("\n", $this->_usersFailed));
			
			$this->_p("$count users could not be migrated. A list of these users can be found at " . FAILED_USER_LOG . "\n");
		}
	}
	
	private function _migrateEmail()
	{
		$this->_p("Migrating email for " . $this->_currentUser->name() . " ");
		
		while (false !== $email = $this->_migrateAdapter->fetchEmail()) {
			$this->_addEmail($email);
		}
		$this->_p("DONE\n");
	}
	
    /**
     * This method is only used in "live" mode, users are not being migrated
     * so we call this to fetch the user settings. Normally these are returned
     * as part of the $user object.
     */
    private function _migrateSettings($user=null)
    {
        if (is_null($user)) {
            $user = $this->_currentUser->name();
        }
        
		$this->_p("Migrating settings for $user ");
        $settings = $this->_migrateAdapter->fetchSettings();
        users::saveAllUserData($user, $settings->get());
        $this->_p("DONE\n");
        
    }
        
	private function _migrateContacts($user=null)
	{
        if (is_null($user)) {
            $user = $this->_currentUser->name();
        }
        
		$this->_p("Migrating contacts for $user ");
		
		while (false !== $contact = $this->_migrateAdapter->fetchContact($user)) {
            if (!is_null($contact)) {
                $this->_addContact($contact);
            }
		}
		$this->_p("DONE\n");
	}
	
	private function _migrateContactGroups($user=null)
	{
        if (is_null($user)) {
            $user = $this->_currentUser->name();
        }
        
		$this->_p("Migrating contact groups for $user ");
		
		while (false !== $group = $this->_migrateAdapter->fetchContactGroup($user)) {
            
            // Don't migrate if the name is blank
            if (!is_null($group) && $group->name()) {
                $this->_addContactGroup($group);
            }
		}
		$this->_p("DONE\n");
	}
    
	private function _migrateCalItems($user=null)
	{
        if (is_null($user)) {
            $user = $this->_currentUser->name();
        }
        
		$this->_p("Migrating calendar events for $user ");
		
		while (false !== $calItem = $this->_migrateAdapter->fetchCalEvent($user)) {
            if (!is_null($calItem)) {
                $this->_addCalendarEvent($calItem);
            }
		}
		$this->_p("DONE\n");
	}
	
	private function _addUser(Atmail_Migrate_Data_User $user)
	{
		list(,$user->Domain) = explode("@", $user->name());
        
        if ($this->_mode == "server") {
            $domainCheckResponse = $this->_api->domainCheck( $user->Domain );

            if( strpos( $domainCheckResponse['response']['message'], 'exists' ) === false ) {
                $this->_api->domainCreate( $user->Domain );
                $this->_p( "Created new domain: $user->Domain\n" );
            }
        }
		
		// If successful
		try {
			$userArray = (array)$user;
			if ($this->_mode != "server") {
				$userArray['isExternalUser'] = 1;
			} else {
				$this->_usersMailDir = $this->_getMailDir();
			}
			
			$this->_p("Adding " . $user->name());
			$res = $this->_api->userCreate($userArray);
			if ($res['status'] == "failed") {
				if (strpos($res['response']['message'], "already exists") === false) {
					throw new Exception($res['response']['message']);
				}
				
				$this->_p(" ALREADY EXISTS\n");
			}
            
            $data = array(
                "UserSettings" => array(
                    "ReplyTo"   => $user->replyTo(),
                    "Signature" => $user->signature(),
                    "RealName"  => $user->realName(),
                    "TimeZone"  => $user->timeZone(),
                    "Language"  => $user->language(),
                    "MsgNum"    => $user->msgNum()
                ),
            );
            
            if (empty($data['UserSettings']['ReplyTo'])) {
                unset($data['UserSettings']['ReplyTo']);
            }
            
            if (empty($data['UserSettings']['Signature'])) {
                unset($data['UserSettings']['Signature']);
            }
            
            if (empty($data['UserSettings']['RealName'])) {
                unset($data['UserSettings']['RealName']);
            }
            
            if (empty($data['UserSettings']['TimeZone'])) {
                unset($data['UserSettings']['TimeZone']);
            }
            
            if (empty($data['UserSettings']['Language'])) {
                unset($data['UserSettings']['Language']);
            }
            
            if (empty($data['UserSettings']['MsgNum'])) {
                unset($data['UserSettings']['MsgNum']);
            }
            
			users::saveAllUserData($user->name(), $data);
            $this->_totalUsers++;
                        
            // Create the contacts model object for this user
            $this->_contactsModel = new contacts(array("Account" => $user->name()));
            
			return self::USER_CREATE_OK;
		} catch (Exception $e) {
		
			// If fail
			$this->_usersFailed[] = $user->name() . ": " . $e->getMessage();
			$this->_p(" FAILED: " . $e->getMessage() . "\n");
		}
	}
	
	
	private function _addEmail(Atmail_Migrate_Data_Email $email)
	{
		$folder = $email->getFolder();
		if (strtoupper($folder) == ".INBOX") {
			$folder = "";
		}
		
		if (!is_dir("$this->_usersMailDir/$folder")) {
            $this->_createFolder($folder);
		}
		
		$id = $this->_generateMaildirId("$this->_usersMailDir/$folder/new/", $email->getTimestamp());
		$size = strlen($email->getContent());
		$flags = $email->getFlags();
		
		$filename = "$this->_usersMailDir/$folder/new/$id,S=$size:2,$flags";
		file_put_contents($filename, $email->getContent());
		chown($filename, "atmail");
		
		$this->_totalEmails++;
		$this->_p(".");
	}
	
	private function _addContact(Atmail_Migrate_Data_Contact $contact)
	{
		$this->_totalContacts++;
		$this->_p(".");
        $id = $this->_contactsModel->addContact($contact->get());
        
        // If the groupName is set then assign the contact to that group.
        if ($groupName = $contact->groupName()) {
			
			if (strtolower($groupName) == "remembered") {
				 $this->_contactsModel->addContactToGroup(array("GroupID" => GROUP_AUTO, "id" => $id));
			} else {
				$gid = $this->db->fetchOne("select id from AbookGroupNames where Account = ? and GroupName = ?", array($this->_currentUser->name(), $groupName));
				$this->_contactsModel->addContactToGroup(array("GroupID" => $gid, "id" => $id));
			}
        }
	}
	
	private function _addContactGroup(Atmail_Migrate_Data_ContactGroup $group)
	{
		if ($this->_matchesSystemGroup($group->name())) {
			return;
		}
		
		$this->_totalContactGroups++;
		$this->_p(".");
        $gid = $this->_contactsModel->createGroup(array("newGroupName" => $group->name()));
        foreach ($group->members() as $m) {
            $id = $this->db->fetchOne("select id from Abook where UserEmail = ? and Global != 1", $m);
            $this->_contactsModel->addContactToGroup(array("GroupID" => $gid, "id" => $id));
        }
	}
    
	private function _addCalendarEvent(Atmail_Migrate_Data_CalendarEvent $calEvent)
	{
		$this->_totalCalItems++;
		$this->_p(".");
		$account = $calEvent->account();
		$_calendar = new calendar(array('migration' => true, 'URL' => '/calendars/' . $calEvent->account() . '/' . $calEvent->calendarName(), 'Account' => $account, 'Password' => '', 'Calendar' => "calendar", 'home', 'largeInstall' => 0));
		$uid = $_calendar->add_raw_record($calEvent->ics(), 'rw', '/calendars/' . $calEvent->account() . '/' . $calEvent->calendarName());
	}
	
	private function _createFolder($folder)
	{
        $this->_p(" creating folder \"$folder\" ");
        mkdir("$this->_usersMailDir/$folder", 0700, true);
        chown("$this->_usersMailDir/$folder", "atmail");
        
        foreach( array('cur', 'tmp', 'new') as $subFolder ) {
            mkdir("$this->_usersMailDir/$folder/$subFolder", 0700, true);
			chown("$this->_usersMailDir/$folder/$subFolder", "atmail");
		}
	}
        
	private function _p($msg)
	{
		fwrite(STDOUT, $msg);
	}

	private function _generateMaildirId($folder, $time)
	{
		$id = false;
	    $h = "H";
	    $pid = "P" . getmypid();

		
	    // Generate key
		for(;;) {

		    for ($i=0; $i<6; $i++) {
		        $h .= intval(rand(0, 9));
            }
            
			$id = "$time.$h$pid.localhost";

		    // Generate another key if it already exists
		    if (!file_exists("$folder/$id")) {
				return $id;
            }
		}
	}

	private function _getMailDir()
	{
		$base = "/usr/local/atmail/users";
		
		$valid = range('a', 'z');
		$a = substr($this->_currentUser->name(), 0, 1);
		if (!in_array($a, $valid)) {
			$a = "other";
		}
		
		$b = substr($this->_currentUser->name(), 1, 1);
		if (!in_array($b, $valid)) {
			$b = "other";
		}
		
		return "$base/$a/$b/" . $this->_currentUser->name();		
	}
	
	private function _matchesSystemGroup($name)
	{
		return in_array(strtolower($name), $this->_systemContactGroups);
	}
	
}
 
 
