<?php

/*
 * TODO
 *  Better logging instantiation/customization
 *  Config handling class
 *  Make it easier to set response code and output error
 *  Check user<->game permissions
 *  should login care about roles? or is that job for getUserInfo?
 */

require 'Outspark/3rdparty/Restafarian/restafarian_common.php';
require_once 'Outspark/Common/Ares/Ares.php';
require_once 'Outspark/Common/KeyMaster/KeyMaster.php';
require_once 'Outspark/Platform/NetDB/NetDB.php';
require_once 'Outspark/Common/GameInfo/GameInfo.php';
require_once 'Outspark/Platform/Invites/GameInvite.php';
require_once 'Outspark/Platform/OutsparkUser/OSKUser.php';
require '../OSKSignedRestResource.inc';
require '../OSKUserModel.inc';


// Set up logging
if (!class_exists('Log')) {
    include 'Log.php';
}
$GLOBALS['logger'] = Log::factory('file', '/tmp/osk_error.log', 'UserResource');
if (is_null($GLOBALS['logger'])) {
    exit("Unable to initiate Logging");
}
$GLOBALS['logger']->setMask(PEAR_LOG_ALL);

$GLOBALS['conf'] = parse_ini_file('/var/www/conf/restconf.ini', true);

class UserResource extends OSKRestResource
{
    public function dispatch ($req)
    {
        $next = $this;
        $version = $this->getNextRawSegment();

        /*
         * If version doesn't match, return /user resource
         */
        if ($version != 'v1') {
            return $this;
        }

        /*
         * Get the portion after version
         */
        $this->incrementNextIndex();
        $segment = $this->getNextRawSegment();

        switch ($segment) {
            case 'login':
                $next = new UserLoginResource();
                break;

            case 'getInfo':
                $next = new UserGetInfoResource();
                break;

            case 'getSparkCashBalance':
                $next = new UserGetSparkCashBalanceResource();
                break;
            
            case 'giveInfraction':
                $next = new UserGiveInfractionResource();
                break;
            case 'loginHistory':
                $next = new UserLoginHistoryResource();
                break;
            default:
                // no default action
                break;
        }

        return $next;
    }

    public function GET ($req, $resp)
    {
        /*
         * Anything not handled under /user is an error.
         */
        $resp->setStatusCode(REST_STATUS_BAD_REQUEST);
        $resp->makeError($this, $resp->getStatusCode(), "Bad Request");
    }
}

class UserLoginResource extends OSKRestResource {

    public function GET ($req, $resp)
    {
        $conf = $GLOBALS['conf'];
        
        $params = $req->getUri()->getQueryParams();

        $user = $params->getFirst('user');
        $pass = $params->getFirst('password');
        $realm = $params->getFirst('realm');
        $version = $params->getFirst('version');

        if($realm == "solstice"){
            $realm = "sos"; // hack until we globally change "sos" to "solstice"
        }else if($realm == "projectpowder"){
	    $realm = "propow"; // hack until we globally change this for propow, too
	}
        
        $output = $params->getFirst('output');
        if (empty($output)) {
            $output = 'json';
        }

        if (empty($user) || empty($realm) || empty($pass)) {
            $resp->setStatusCode(REST_STATUS_UNAUTHORIZED);
/*            $resp->makeError($this, $resp->getStatusCode(), 'Unauthorized access'); */
            $resp->makeError($this, $resp->getStatusCode(), 'Please check that your email and password are correct.');
             return;
        }



        $realmVersionStr = $realm . ".client";

        if (!isset($conf['versions'][$realmVersionStr])
            ||
            empty($version)
            ||
            (int)$version < (int)$conf['versions'][$realmVersionStr]) {
            $GLOBALS['logger']->debug("{$user}: version = {$version}, min = {$conf['versions'][$realmVersionStr]}, disallowing");
            $resp->setStatusCode(REST_STATUS_PRECONDITION_FAILED);
            $resp->makeError($this, $resp->getStatusCode(), 'Obsolete version detected. Please update your Outspark software to the latest supported version in order to login.');
            return;
        }

        try {
            $model = new OSKUserModel();
        } catch (Exception $e) {
            $resp->setStatusCode(REST_STATUS_INTERNAL_SERVER_ERROR);
            $resp->makeError($this, $resp->getStatusCode(), 'Internal server error');
            return;
        }

        $userInfo = $model->getUserInfo($user);
        if (!$userInfo) {
			$GLOBALS['logger']->debug("{$user}: !userInfo");
			$resp->setStatusCode(REST_STATUS_UNAUTHORIZED);
/*			$resp->makeError($this, $resp->getStatusCode(), 'Unauthorized access'); */
			$resp->makeError($this, $resp->getStatusCode(), 'Please check that your email and password are correct.');
			return;
		}

		// Deny access to users who are unverified or banned
		if (empty($userInfo['status'])) {
			$GLOBALS['logger']->debug("{$user}: banned");
			$resp->setStatusCode(REST_STATUS_UNAUTHORIZED);
/*			$resp->makeError($this, $resp->getStatusCode(), 'Unauthorized access'); */
			$resp->makeError($this, $resp->getStatusCode(), 'You account has been banned.');
			return;
		}
                
		if (empty($userInfo['verified'])) {
			$GLOBALS['logger']->debug("{$user}: not verified");
			$resp->setStatusCode(REST_STATUS_UNAUTHORIZED);
/*			$resp->makeError($this, $resp->getStatusCode(), 'Unauthorized access'); */
			$resp->makeError($this, $resp->getStatusCode(), 'You account has not been verified');
			return;
		}
                

        $forbidden = false;
        $message = "";
        /**
         * check for any type of IP ban
         */
        $netdb = new OSKNetDB();
        $ip_result = $netdb->inGroup($_SERVER['REMOTE_ADDR'], $realm . '.denied');
        if ($ip_result) {

			$ip_realm = array_shift($ip_result);

            if ($realm == 'fiesta') {
                if (($ip_realm == 'germany')) { 
					// For users created after July 14
					if ($userInfo['created'] >= 1216018800) {
						$GLOBALS['logger']->debug("german user {$user}, {$userInfo['created']}: IP {$_SERVER['REMOTE_ADDR']}");
						$message = "This version of Fiesta is not available for Germany, Switzerland and Austria for Outspark users registered after July 14th, 2008. Please visit http://fiesta-online.gamigo.de/ to download a local copy.";
						$forbidden = true;
					}
				} else if ($ip_realm == 'china') {
                    $message = "This version of Fiesta is not available for use in your region.\n\nPlease visit http://shine.gtgame.com.cn/ to download a local copy.";
                    $forbidden = true;
                } else if ($ip_realm == 'taiwan') {
                    $message = "This version of Fiesta is not available for use in your region.\n\nPlease visit http://fiesta.runup.com.tw/ to download a local copy.";
                    $forbidden = true;
                } else {
                    $message = "This version of Fiesta is not available for use in your region.";
                    $forbidden = true;
                }

			} else if ($realm == 'blackshot') {
				$GLOBALS['logger']->debug("blackshot user {$user} is in country {$ip_realm}: IP {$_SERVER['REMOTE_ADDR']}");
				if (in_array($ip_realm, array('singapore', 'malaysia', 'philippines'))) {
					$message = "This version of Blackshot is not available for use in your region.";
					$forbidden = true;
				}
			}

			if($forbidden){
				$GLOBALS['logger']->debug("{$user}: IP {$_SERVER['REMOTE_ADDR']} is in realm {$ip_realm}, denied");
				$resp->setStatusCode(REST_STATUS_FORBIDDEN);
				$resp->makeError($this, $resp->getStatusCode(), $message);
				return; 
			}

        }


		if ( ($userInfo['password'] != $pass) && ($userInfo['password'] != md5($pass)) ) { // check to work around reg issue where 54k users had their passwords double hashed
            $GLOBALS['logger']->debug("{$user}: password mismatch");
            $resp->setStatusCode(REST_STATUS_UNAUTHORIZED);
/*            $resp->makeError($this, $resp->getStatusCode(), 'Unauthorized access'); */
            $resp->makeError($this, $resp->getStatusCode(), 'Please check that your email and password are correct.');
            return;
        }
        

        $gameinfo = GameInfo::fetch();
        $gameOpen = false;
        $gameInviteOnly = false;
        $internal = !empty($userInfo['internal']);
        if(isset($realm, $gameinfo[$realm])){
            $gameOpen = $gameinfo[$realm]['isopen'];
            $gameInviteOnly = $gameinfo[$realm]['inviteonly'];
        }
        
        if (!$internal && !$gameOpen) {
            $resp->setStatusCode(REST_STATUS_FORBIDDEN);
 /*           $resp->makeError($this, $resp->getStatusCode(), 'Unauthorized access'); */
            $resp->makeError($this, $resp->getStatusCode(), 'The game is either closed for maintenance or you have not been invited to play yet.');
            return;
        }
        
        $gameinvite = new GameInvite($realm);
        if (!$internal && $gameInviteOnly && !$gameinvite->isInvited($userInfo['userid'])) {
            $GLOBALS['logger']->debug("{$user}: not in invite-only list");
            $resp->setStatusCode(REST_STATUS_FORBIDDEN);
 /*           $resp->makeError($this, $resp->getStatusCode(), 'Unauthorized access'); */
            $resp->makeError($this, $resp->getStatusCode(), 'You are not in the invite list');
            return;
        }

        $key = OSKKeyMaster::getInstance()->getKey($realm . '.token');
        if (!$key) {
            $GLOBALS['logger']->err("no token key for realm '{$realm}'");
            $resp->setStatusCode(REST_STATUS_INTERNAL_SERVER_ERROR);
            $resp->makeError($this, $resp->getStatusCode(), 'Internal server error');
  
              
            return;
        }

		// At this point we can return a success response
        $GLOBALS['logger']->debug("{$user}: success - generating a token");

		// Bump the user's last login time and last login IP
/*		if ($user = OSKUser::getByUserID($user)) {
			$ip = isset($_SERVER['HTTP_X_CLIENT_IP']) ? $_SERVER['HTTP_X_CLIENT_IP'] : $_SERVER['REMOTE_ADDR'];
			$user->bump($ip);
		}
*/

        // attempt to add this login to the last login history queue
        try{
            $conf = $GLOBALS['conf'];
            $cache = new Memcache();
            $cache_conf = false;
            if (!$cache_conf = ConfigRegistry::load("/var/www/conf/common.cache.memcache.ini")) {
                    error_log("LoginHistoryResource: Could not load common.cache.memcache.ini");
            }
            foreach ($cache_conf as $index => $value) {
                    if (strpos($index, 'server.') !== false) {
                            $server = $value;
                            if (empty($server['host']) || empty($server['port'])) {
                                    throw new Exception('Invalid server connection info at config index '.$index);
                            }
                            $cache->addServer($server['host'], $server['port']);
                    }
            }
            // get existing data out of memory if it exists
            $data = $cache->get("API.UserResource.LoginHistory");
            $data = is_array($data)?$data : array();
            if(is_array($data)){
                $this_login = array("userid" => $userInfo['userid'], "realm" => $realm, "time" => time());
                $i = 0;
                foreach($data as $one_login){
                    if($userInfo['userid'] == $one_login['userid']){
                        // this user is logging in again, so remove any existing entries for this user
                        array_splice($data, $i, 1);
                        $i--;
                    }
                    $i++;
                }
                if(is_numeric($conf['history_count'])){ // only add rows if there is a count set. this way we can avoid neverending inserts
                    while(count($data) > $conf['history_count']){
                        array_pop($data);
                    }
                    array_unshift($data, $this_login);
                }
                $cache->set("API.UserResource.LoginHistory", $data);
            }
            
            // now try to store this into realm specific list
            $data = $cache->get("API.UserResource.LoginHistory.{$realm}");
            $data = is_array($data)?$data : array();
            if(is_array($data)){
                $this_login = array("userid" => $userInfo['userid'], "realm" => $realm, "time" => time());
                $i = 0;
                foreach($data as $one_login){
                    if($userInfo['userid'] == $one_login['userid']){
                        // this user is logging in again, so remove any existing entries for this user
                        array_splice($data, $i, 1);
                        $i--;
                    }
                    $i++;
                }
                if(is_numeric($conf['history_count'])){ // only add rows if there is a count set. this way we can avoid neverending inserts
                    while(count($data) > $conf['history_count']){
                        array_pop($data);
                    }
                    array_unshift($data, $this_login);
                }
                $cache->set("API.UserResource.LoginHistory.{$realm}", $data);
            }
        }catch(Exception $e){
            // bury because we'll still allow the login if something broke here
        }
        
        $content = $userInfo['sparkid'] . '|' . time();
        $content_hash = sprintf("%u", crc32($content));
        $payload = $content . '/' . $content_hash;

        $iv_size = mcrypt_get_iv_size(MCRYPT_TWOFISH, MCRYPT_MODE_CBC);
        $iv = str_repeat("\0", $iv_size);

        $enc_token = mcrypt_encrypt(MCRYPT_TWOFISH, $key, $payload, MCRYPT_MODE_CBC, $iv);
        $token = str_replace(array('+','/','='),array('-','_','.'), base64_encode($enc_token));
        $json = json_encode(array('token' => $token));

        if ($output == 'text') {
            $resp->setContent(CTYPE_TEXT, $json);
        } else {
            $resp->setContent(CTYPE_JSON, $json);
        }
        
        $GLOBALS['logger']->debug("{$user}: token: {$token}");
        
        // since the token has been verified, let's notify the user's friends that game is on
        // make XMPP notification call here.
        $ares = new Ares();
        $ares->sendXMPPStatus($userInfo['sparkid']
            ,$userInfo['sparkid'] . " has just started playing " . $gameinfo[$realm]['displayName']);

        $resp->setStatusCode(REST_STATUS_OK);
    }

}

class UserGetInfoResource extends OSKSignedRestResource {

    public function signedGET ($req, $resp)
    {
        $conf = $GLOBALS['conf'];

        $params = $req->getUri()->getQueryParams();

        $realm = $params->getFirst('realm');
        $token = $params->getFirst('token');
        $output = $params->getFirst('output');

        if (empty($token) || empty($realm)) {
            $resp->setStatusCode(REST_STATUS_BAD_REQUEST);
            $resp->makeError($this, $resp->getStatusCode(), 'Bad request');
            return;
        }

        if (empty($output)) {
            $output = 'json';
        }

        $GLOBALS['logger']->debug("{$token} token request for realm '{$realm}'");

        $key = OSKKeyMaster::getInstance()->getKey($realm . '.token');
        if (!$key) {
            $GLOBALS['logger']->err("no token key for realm '{$realm}'");
            $resp->setStatusCode(REST_STATUS_INTERNAL_SERVER_ERROR);
            $resp->makeError($this, $resp->getStatusCode(), 'Internal server error');
            return;
        }

        $iv_size = mcrypt_get_iv_size(MCRYPT_TWOFISH, MCRYPT_MODE_CBC);
        $iv = str_repeat("\0", $iv_size);

        $raw_token = base64_decode(str_replace(array('-','_','.'), array('+','/','='), $token));
        $payload = mcrypt_decrypt(MCRYPT_TWOFISH, $key, $raw_token, MCRYPT_MODE_CBC, $iv);
        $payload = rtrim($payload, "\0");

        @list($content, $content_hash) = explode('/', $payload);
        if (!isset($content_hash)
            ||
            (sprintf("%u", crc32($content)) != $content_hash)) {

            $GLOBALS['logger']->info("corrupt token {$token}");
            $resp->setStatusCode(REST_STATUS_UNAUTHORIZED);
//            $resp->makeError($this, $resp->getStatusCode(), 'Unauthorized access');
            $resp->makeError($this, $resp->getStatusCode(), 'Please check that your email and password are correct.');
            return;
        }

        list($user, $token_time) = explode('|', $content);

        $token_age = (int)((time() - $token_time)/60);

        if ($token_age > $conf['max_token_age']) {
            $GLOBALS['logger']->debug("{$token} expired");
            $resp->setStatusCode(REST_STATUS_UNAUTHORIZED);
/*            $resp->makeError($this, $resp->getStatusCode(), 'Unauthorized access'); */
            $resp->makeError($this, $resp->getStatusCode(), 'Please check that your email and password are correct.');
          
            return;
        }

        try {
            $model = new OSKUserModel();
        } catch (Exception $e) {
            $resp->setStatusCode(REST_STATUS_INTERNAL_SERVER_ERROR);
            $resp->makeError($this, $resp->getStatusCode(), 'Internal server error');
            return;
        }

        $userInfo = $model->getUserInfo($user);
        if (!$userInfo) {
            $GLOBALS['logger']->debug("{$user}: !userInfo");
            $resp->setStatusCode(REST_STATUS_UNAUTHORIZED);
//            $resp->makeError($this, $resp->getStatusCode(), 'Unauthorized access');
            $resp->makeError($this, $resp->getStatusCode(), 'Please check that your email and password are correct.');
            return;
        }

        $GLOBALS['logger']->debug("{$token}:{$user} success");

        // 'blocked' is taken from 'status' and 'verified' field for the user
        // it does not differentiate between games currently
		$json = json_encode(array(
			'token_age' => $token_age,
			'user_id'   => $userInfo['userid'],
			'login'     => $user,
			'user_role' => "user",
			'blocked'   => empty($userInfo['status']) || empty($userInfo['verified'])
		));

        if ($output == 'text') {
            $resp->setContent(CTYPE_TEXT, $json);
        } else {
            $resp->setContent(CTYPE_JSON, $json);
        }
        $resp->setStatusCode(REST_STATUS_OK);
    }

}

class UserGiveInfractionResource extends OSKSignedRestResource {

    public function signedGET ($req, $resp)
    {
        $conf = $GLOBALS['conf'];

        $params = $req->getUri()->getQueryParams();
        $realm = $params->getFirst('realm');
        $userID = $params->getFirst('outsparkID');
        $charID = $params->getFirst('characterID');
        $worldID = $params->getFirst('worldID');
        $level = $params->getFirst('level');
        $output = $params->getFirst('output');

        if ( empty($realm) || !isset($userID) || !isset($charID) || !isset($worldID) || empty($level) ) {
            $resp->setStatusCode(REST_STATUS_BAD_REQUEST);
            $resp->makeError($this, $resp->getStatusCode(), 'Bad request');
            return;
        }

        $json = json_encode(array('result' => true));

        if ($output == 'text') {
            $resp->setContent(CTYPE_TEXT, $json);
        } else {
            $resp->setContent(CTYPE_JSON, $json);
        }
        $resp->setStatusCode(REST_STATUS_OK);
    }

}

class UserLoginHistoryResource extends OSKRestResource {
    
    public function GET($req, $resp){
        
        $params = $req->getUri()->getQueryParams();
        $output = $params->getFirst('output');

        $cache = new Memcache();
        $conf = false;
        if (!$conf = ConfigRegistry::load("/var/www/conf/common.cache.memcache.ini")) {
                error_log("LoginHistoryResource: Could not load common.cache.memcache.ini");
        }
        foreach ($conf as $index => $value) {
                if (strpos($index, 'server.') !== false) {
                        $server = $value;
                        if (empty($server['host']) || empty($server['port'])) {
                                throw new Exception('Invalid server connection info at config index '.$index);
                        }
                        $cache->addServer($server['host'], $server['port']);
                }
        }
        $result = $cache->get("API.UserResource.LoginHistory");

        $json = json_encode($result);
        if ($output == 'text') {
            $resp->setContent(CTYPE_TEXT, $json);
        } else {
            $resp->setContent(CTYPE_JSON, $json);
        }
        $resp->setStatusCode(REST_STATUS_OK);
        
    }
}

class UserGetSparkCashBalanceResource extends OSKSignedRestResource {

    public function signedGET ($req, $resp)
    {
        require_once 'Outspark/Platform/Greenspan/Greenspan.php';
        require_once 'Outspark/Platform/Greenspan/UserInfo.php';

        $conf = $GLOBALS['conf'];

        $params = $req->getUri()->getQueryParams();

        $userID = $params->getFirst('userID');
        $output = $params->getFirst('output');

        if (empty($userID)) {
            $resp->setStatusCode(REST_STATUS_BAD_REQUEST);
            $resp->makeError($this, $resp->getStatusCode(), 'Bad request');
            return;
        }

        if (empty($output)) {
            $output = 'json';
        }

        try {
            GSBase::init();
        } catch (GSException $e) {
            $GLOBALS['logger']->err("could not init GSBase");
            $resp->setStatusCode(REST_STATUS_INTERNAL_SERVER_ERROR);
            $resp->makeError($this, $resp->getStatusCode(), 'Internal server error');
            return;
        }

        try {
            $userBalance = GSUserInfo::getWalletBalances($userID);
        } catch (GSException $e) {
            $GLOBALS['logger']->err("error exceuting getWalletBalances() query");
            $resp->setStatusCode(REST_STATUS_INTERNAL_SERVER_ERROR);
            $resp->makeError($this, $resp->getStatusCode(), 'Internal server error');
            return;
        }

        if ($userBalance) {
            $balance = $userBalance['total'];
        } else {
            $balance = 0;
        }

        $GLOBALS['logger']->debug("getSparkCashBalance: userID = {$userID}, balance = {$balance}");
        $json = json_encode(array('balance' => $balance));

        if ($output == 'text') {
            $resp->setContent(CTYPE_TEXT, $json);
        } else {
            $resp->setContent(CTYPE_JSON, $json);
        }
        $resp->setStatusCode(REST_STATUS_OK);

    }
}

?>
