<?php

namespace MGModule\DNSManager2\mgLibs\custom\dns\submodules;

use \MGModule\DNSManager2\mgLibs\custom\dns;
use \MGModule\DNSManager2\mgLibs\custom\dns\exceptions;
use \MGModule\DNSManager2\mgLibs\custom\dns\interfaces;
use \MGModule\DNSManager2\mgLibs\custom\dns\utils\Patterns;

class Rackspace extends dns\SubmoduleAbstract implements interfaces\SubmoduleImportInterface, interfaces\SubmoduleTTLInterface { 
    public $configFields = array(
        'username' => array(
            'friendlyName' => 'Username',
            'validators' => array(
                'required' => 'required',
            )
        ),
        'apikey' => array(
            'friendlyName' => 'API Access Key',
            'type' => 'password',
            'validators' => array(
                'required' => 'required',
            )
        ),
        'account_id' => array(
            'friendlyName' => 'Account ID',
            'validators' => array(
                'required' => 'required',
            )
        ),
        'endpoint' => array(
            'friendlyName' => 'Account Is',
            'type' => 'select',
            'options' => array('US' => 'US-Based', 'UK' => 'UK-Based')
        ),
        'default_ip' => array(
            'friendlyName' => 'Default IP',
            'validators' => array(
                'pattern' => Patterns::IP4_OR_IP6,
            )
        ),
    );
    
    private $account_id;
    private $auth_token;
    private $api_end_point;
    private $auth_end_point;
    public $error;
    
    private $timeout = 10;
    
    public $results = array();
    
    const API_VERSION = '1.0';
    const US_AUTHURL = 'https://auth.api.rackspacecloud.com';
    const UK_AUTHURL = 'https://lon.auth.api.rackspacecloud.com';
    const UK_DNS_ENDPOINT = 'https://lon.dns.api.rackspacecloud.com';
    const US_DNS_ENDPOINT = 'https://dns.api.rackspacecloud.com';
    
    /** available records types **/
    public $availableTypes = array('A', 'AAAA', 'NS', 'MX', 'CNAME', 'TXT', 'SRV');
    
    public function login() { 
        $auth_options = array (
            'X-Auth-User: '.$this->config['username'],
            'X-Auth-Key: '.$this->config['apikey'] 
        ); 
        $ch = curl_init ();
        $url = $this->auth_end_point . '/' . rawurlencode ( "v" . self::API_VERSION ); 
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($ch, CURLOPT_MAXREDIRS, 4);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $auth_options);
        curl_setopt($ch, CURLOPT_HEADER, true);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout);
        curl_setopt($ch, CURLOPT_URL, $url);
        $response = curl_exec ($ch);
        curl_close ($ch); 
        preg_match ("/^HTTP\/1\.[01] (\d{3}) (.*)/", $response, $matches); 
        if(isset($matches[1])){
            if($matches[1] == "204") {
                preg_match ( "/X-Server-Management-Url: (.*)/", $response, $matches );
                //$account = explode('/', trim($matches[1])); 
                //$this->account_id = array_pop($account);
                $this->account_id = $this->config['account_id'];
                preg_match ("/X-Auth-Token: (.*)/", $response, $matches);
                $this->auth_token = trim($matches[1]);
                return true;
            }
        }
        
        throw new exceptions\DNSSubmoduleException($this->getErrorCodeDescription($matches[1]), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
    }
    
    public function testConnection(){
       return $this->login();
    }
    
    private function get($ch, $url_add) {
        $url = $this->api_end_point . '/' . rawurlencode ( "v" . self::API_VERSION ) . '/' . $this->account_id . $url_add;
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_USERAGENT, 'Rackspace DNS PHP Binding');
        curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $json_response = curl_exec($ch); 
        
        if (curl_errno($ch)) {
            throw new exceptions\DNSSubmoduleException("cURL Error: " . curl_errno($ch) . " - " . curl_error($ch), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
        }
        
        curl_close($ch);  
        $response =  json_decode($json_response, true);
        if(!empty($response['overLimit'])){
            throw new exceptions\DNSSubmoduleException('Rackspace requests limit reached. Check your account limits.', dns\SubmoduleExceptionCodes::REQUEST_LIMIT);
        }

        return $response;
    }
    
    private function sendGETRequest($get_params) {
        if(!$this->login())
            return false;
        
        $parts = explode('?', $get_params); 
        $url = $parts[0] . ".json";
        if(isset($parts[1])) 
            $url .= '?' . $parts[1];
        
        //$json_url = $this->api_end_point . '/' . rawurlencode ( "v" . self::API_VERSION ) . '/' . $this->account_id . $url;
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
        curl_setopt($ch, CURLOPT_HTTPGET, true );
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'Accept: application/json',
            'X-Auth-Token: '.$this->auth_token
        ));
        return $this->get($ch, $url);
    }
    
    private function sendPOSTRequest($post_params, $json_params) { 
        if(!$this->login())
            return false;
        
        //$json_url = $this->api_end_point . '/' . rawurlencode ( "v" . self::API_VERSION ) . '/' . $this->account_id . $post_params;
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
        curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($json_params));
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'Content-Type: application/json',
            'Accept: application/json',
            'X-Auth-Token: '.$this->auth_token
        ));
        return $this->get($ch, $post_params);
        
    }
    
    private function sendPUTRequest($put_params, $json_params) {
        if(!$this->login())
            return false;
        
        //$json_url = $this->api_end_point . '/' . rawurlencode ( "v" . self::API_VERSION ) . '/' . $this->account_id . $put_params; 
        $json_data = json_encode($json_params);
        $ch = curl_init();
        curl_setopt($this->ch, CURLOPT_POST, true );
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
        curl_setopt($ch, CURLOPT_POSTFIELDS, $json_data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'Content-Length: '.strlen($json_data),
            'Content-Type: application/json',
            'Accept: application/json',
            'X-Auth-Token: '.$this->auth_token
        ));
        return $this->get($ch, $put_params);
    }
    
    private function sendDELETERequest($delete_params) {
        if(!$this->login())
            return false;
        
        $parts = explode('?', $delete_params); 
        $url = $parts[0] . ".json";
        if(isset($parts[1])) 
            $url .= '?' . $parts[1];
        
        //$json_url = $this->api_end_point . '/' . rawurlencode ( "v" . self::API_VERSION ) . '/' . $this->account_id . $url;
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
        curl_setopt($ch, CURLOPT_HTTPHEADER, array(
            'Accept: application/json',
            'X-Auth-Token: '.$this->auth_token
        ));
        return $this->get($ch, $url);
    }
    
    public function setConfiguration($config) {
        parent::setConfiguration($config);
        $this->auth_end_point = $config['endpoint'] == 'US' ? self::US_AUTHURL : self::UK_AUTHURL;
        $this->api_end_point = $config['endpoint'] == 'US' ? self::US_DNS_ENDPOINT : self::UK_DNS_ENDPOINT;
    }

    public function zoneExists() {
       // $this->getDomainID();
        try {
            $ret = $this->sendGETRequest('/domains?name=' . rawurlencode($this->domain));
        } catch (exceptions\DNSSubmoduleException $e) {
            if($e->getCode() == dns\SubmoduleExceptionCodes::COMMAND_ERROR) {
                return false;
            }
            throw $e;
        }

        if($ret['totalEntries'] == 0)
            return false;
        else
            return true;
    }
    
    private function getDomainID() {
        $ret = $this->sendGETRequest('/domains?name=' . rawurlencode($this->domain)); 
        $id = $ret['domains'][0]['id'];
        if(empty($id)) {
            throw new exceptions\DNSSubmoduleException('Zone does not exist', dns\SubmoduleExceptionCodes::INVALID_RESPONSE);
        }
        return $id;
    }

    public function getRecords($recordType = false) {
        $id = $this->getDomainID();

        $ret = $this->sendGETRequest('/domains/'.$id.'/records');
        
        $out = array();
        foreach($ret['records'] as $r) {
            $type = strtoupper((string)$r['type']);
            if(in_array($type, $recordType!==false ? array(strtoupper($recordType)) : $this->getAvailableRecordTypes())) {
                $record = new dns\record\Record();
                $record->line = (string)$r['id'];
                $record->name = (string)$r['name'];
                $record->ttl = $r['ttl'];
                $record->type = $type;
                $record->createRDATAObject($type);
                
                switch($type) {
                    case 'SRV': case 'MX':
                        $record->rdata->fromString($r['priority'] . ' ' . $r['data']);
                        break;
                    case 'TXT':
                        $record->rdata->setFirstProperty(html_entity_decode($r['data']));
                        break;
                    default:
                        $record->rdata->setFirstProperty($r['data']);
                        break;
                }
                
                $out[] = $record;
            }
        }
        
        return $out;
    }

    public function addRecord(dns\record\Record $record) {
        $id = $this->getDomainID();
        
        $input = $this->recordToInputArray($record);

        $json_data =  array('records' => array($input));
        $url = '/domains/'.$id.'/records';
        return $this->callbackLoop($this->sendPOSTRequest($url, $json_data));     
    }
    
    private function recordToInputArray($record) {
        switch($record->type) {                            
            case 'MX':
                $data = $record->rdata->exchange;
                $priority = $record->rdata->preference;
                break;
            case 'SRV':
                $priority = $record->rdata->priority;
                $data = $record->rdata->toString(array('weight' => '', 'port' => '', 'target' => ''));
                break;
            case 'TXT':
                $data = $record->rdata->toString(); //trim( . '"');
                break;
            default:
                $data = $record->rdata->toString();
                break;
        }
        
        $input = array(
            'name' => $record->nameToAbsolute($this->domain, false),
            'type' => $record->type,
            'ttl' => $record->ttl,
            'data' => $data
        );
        
        if(isset($priority)) {
            $input['priority'] = $priority;
        }
        
        return $input;
    }
    
    private function callbackLoop($ret) {
        $this->checkErrors($ret);
        
        $timeout = time() + 20;
        while(isset($ret['callbackUrl']) && $timeout > time()) {
            $this->callbacks [] = $ret;
            usleep(500000);
            $url = explode('status', $ret['callbackUrl']);
            $ret = $this->sendGETRequest('/status' . array_pop($url).'?showDetails=true');
            
            if(isset($ret['error'])) {
                throw new exceptions\DNSSubmoduleException($ret['error']['details'], dns\SubmoduleExceptionCodes::COMMAND_ERROR);
            }
            
            if($ret['status'] == 'COMPLETED') {
                break;
            }
        }
        
        if($ret['status'] == 'COMPLETED') {
            return true;
        }
        
        throw new exceptions\DNSSubmoduleException("Unknown Error", dns\SubmoduleExceptionCodes::COMMAND_ERROR);
    }

    public function editRecord(dns\record\Record $record) {
        $id = $this->getDomainID();

        $input = $this->recordToInputArray($record);
        $input['id'] = $record->line;
        
        $json_data =  array('records' => array($input)); 
        $url = '/domains/'.$id.'/records';
        return $this->callbackLoop($this->sendPUTRequest($url, $json_data));
    }

    public function deleteRecord(dns\record\Record $record) {        
        $id = $this->getDomainID();
        
        return $this->callbackLoop($this->sendDELETERequest('/domains/'.$id.'/records/'.$record->line));  
    }

    public function activateZone() {  
        if($this->zoneExists()) {
            throw new exceptions\DNSSubmoduleException("Zone already exist", dns\SubmoduleExceptionCodes::COMMAND_ERROR); 
        }
     
        $json_data =  array(
            'domains' => array(
                array(
                    'name' => $this->domain, 
                    'emailAddress' => 'sample@rackspace.com', 
                    'recordsList'   =>  array('records' =>  array())
                )
            )
        );
        
        return $this->callbackLoop($this->sendPOSTRequest('/domains', $json_data));
    }

    public function terminateZone() {
        $id = $this->getDomainID();
        
        return $this->callbackLoop($this->sendDELETERequest('/domains/'.$id.'?deleteSubdomains=true'));        
    }
    
    private function getErrorCodeDescription($error_code = "0") {
        $errors = array (
                    "400" => "Bad request",
                    "401" => "Bad username or password",
                    "403" => "Resize not allowed",
                    "404" => "Item not found",
                    "409" => "Item already exists",
                    "413" => "Over API limit (check limits())",
                    "415" => "Bad media type",
                    "500" => "Cloud server issue",
                    "503" => "API service in unavailable, or capacity is not available",
                    "0"   => "UNKNOWN ERROR"
            );
        return $errors[$error_code];
      
    }
    
    private function checkErrors($ret) {
        if($ret['code'] > 203 && isset($ret['message'])) {
            throw new exceptions\DNSSubmoduleException($ret['message'] . (isset($ret['details'])?' Details: ' . $ret['details']:''), dns\SubmoduleExceptionCodes::COMMAND_ERROR); 
        }
        
        if(isset($ret['validationErrors']['messages']) && count($ret['validationErrors']['messages'])) {
            throw new exceptions\DNSSubmoduleException(implode('; ', $ret['validationErrors']['messages']), dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
    }

    public function getZones($params = false) {
        $params = $params ? $params : '/domains';

        $ret = $this->sendGETRequest($params);


        $out = array();
        foreach($ret['domains'] as $domain) {
            $out[$domain['name']] = '';
        }

        $nextStep = $ret['links'][0];
        if($nextStep['rel'] == 'next')
        {
            $fullUrl = $nextStep['href'];
            list($base, $url) = explode($this->account_id, $fullUrl);
            $out += $this->getZones($url);

        }

        return $out;
    }
    
}
