<?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 DirectAdmin extends dns\SubmoduleAbstract implements interfaces\SubmoduleIPInterface, interfaces\SubmoduleRDNSInterface, interfaces\SubmoduleImportInterface, interfaces\SubmoduleDNSSecInterface {
    public $configFields = array(
        'username' => array (
                'friendlyName'  =>  'Username',
                'validators' => array(
                    'required' => 'required',
                )
        ),
        'password' => array (
                'friendlyName'  =>  'Password',
                'type' => 'password',
                'validators' => array(
                    'required' => 'required',
                )
        ),
        'hostname' => array (
                'friendlyName'  =>  'Hostname',
                'validators' => array(
                    'required' => 'required',
                )
        ),
        'port' => array (
                'friendlyName'  =>  'Port',
                'value' => 2222,
                'validators' => array(
                    'required' => 'required',
                )
        ),
        'ssl' => array (
                'friendlyName'  =>  'SSL',
                'type' => 'yesno'
        ),
        'default_ip' =>array (
                'friendlyName'  =>  'Default IP',
                'validators' => array(
                    'required' => 'required',
                    'pattern' => Patterns::IP4_OR_IP6,
                )
        ),
        'ns1' => array (
                'friendlyName'  =>  'Nameserver 1',
        ),
        'ns2' => array (
                'friendlyName'  =>  'Nameserver 2',
        ),
        'full_mx_records' => array (
                'friendlyName'  =>  'Full MX Records',
                'type'=> 'yesno',
        ),
    );

    public $availableTypes = array('A', 'NS', 'MX', 'CNAME', 'TXT', 'AAAA', 'SRV', 'DS', 'PTR');
    
    private function get($function, $params = array()) {
        $port       = $this->config['port']?:'2222';
        $username   = urlencode($this->config['username']);
        $password   = $this->config['password'];                    
        $url        = ($this->config['ssl'] === 'on' ? 'https://'.$username.':'.urlencode($password).'@'.$this->config['hostname'].':'.$port : 'http://'.$username.':'.urlencode($password).'@'.$this->config['hostname'].':'.$port).'/'.$function;

        if( is_array($params) )
        {
            $url .= '?' . http_build_query($params);
        }
        
        $this->log('REQUEST: ' . $url);

        $ch = curl_init();
        $chOptions = array (
            CURLOPT_URL => trim($url, '&').'&urlencoded=yes',
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_TIMEOUT => 30
        );
        curl_setopt_array($ch, $chOptions);
        $out = curl_exec($ch);
        $this->log('RESPONSE: ' . $out);
        if (curl_errno($ch)) {
            throw new exceptions\DNSSubmoduleException("cURL Error: " . curl_errno($ch) . " - " . curl_error($ch), dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
        }
        curl_close($ch);

        $this->checkOutput($out);
        
        //parsujemy
        $out1 = urldecode($out);
        parse_str($out1, $parsed);
         
        if(isset($parsed['error']) && $parsed['error'] > 0) {
            throw new exceptions\DNSSubmoduleException($parsed['text'].' '.$parsed['details'], dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
        
        if($parsed === FALSE) {
            throw new exceptions\DNSSubmoduleException("Unknown error", dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        }
        
        return $out;
    }
    
    public function testConnection() {   
        $out = $this->get('CMD_API_SHOW_USER_USAGE', array('user' => $this->config['username']));
        if(strpos($out, '<html>') !== false)
            throw new exceptions\DNSSubmoduleException('Incorrect username or password', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
    }
    
    public function zoneExists() {
        try {
            $out = $this->get('CMD_API_DNS_ADMIN', array(
                'domain'    =>  $this->domain
            ));
            if(strpos($out, '<html>') !== false) {
                return false;
            }
        } catch (exceptions\DNSSubmoduleException $e) {
            if($e->getCode() == dns\SubmoduleExceptionCodes::COMMAND_ERROR) {
                return false;
            }
            throw $e;
        }
        return true;
    }

    public function getRecords($recordType = false) { 
        $out = $this->get('CMD_API_DNS_ADMIN', array(
            'domain'    => $this->domain,
            'urlencoded' =>  'yes',
            'full_mx_records' => $this->config['full_mx_records'] == 'on' ? 'yes' : 'no'
        ));
        /** TRZEBA TO ZMIENIC!!! **/
        $lines = explode("\n", $out);
        $arr = array();
        
        foreach($lines as $ex) {            
            $line = explode('=', $ex, 2); //type=name=value&name1=value1
            $data = explode('&', $line[1]);
            foreach($data as $d) {
                $x = explode('=', $d, 2); //name=value
                if(isset($x[0]) && isset($x[1])) {
                    $arr[$line[0]][][urldecode($x[0])] = urldecode($x[1]);
                }
            }
        }
        
        $out = array();
        $i = 0;

        foreach($arr as $type => $r) {
            foreach($r as $records) {
                if(in_array($type, $recordType!==false ? array(strtoupper($recordType)) : $this->getAvailableRecordTypes())) {
                    foreach($records as $name => $value) {  
                        if(empty($name)) {
                            continue;
                        }

                        $record = new dns\record\Record();
                        $record->line = $i;
                        $record->name = $name;
                        $record->type = $type;
                        $record->createRDATAObject();
                        $record->rdata->fromString($value);
                        
                        switch($type) {
                            case 'MX':
                                if($this->config['full_mx_records'] != 'on'){
                                    $record->name = $this->domain . '.';
                                    $record->rdata->exchange = $name;
                                }
                                break;
                            case 'NS':
                                $record->name = $value;
                                $record->rdata->setFirstProperty($name);
                                break;
                        }

                        $out[] = $record;
                    }
                }
                $i++;
            }
        }
        
        return $out;
    }

    public function addRecord(dns\record\Record $record) {
        $params = array(
            'action' => 'add',
            'domain' => $this->domain,
            'type' => $record->type,
        );

        switch($record->type) {
            case 'NS':
                $params['name'] = $record->rdata->toString();
                $params['value'] = $record->nameToAbsolute($this->domain);
                break;
            case 'MX':
                if($this->config['full_mx_records'] == 'on'){
                    $params['full_mx_records'] = 'yes';
                    $params['mx_value'] = $record->rdata->exchange;
                    $params['name'] = $record->nameToAbsolute($this->domain);
                } else {
                    $params['full_mx_records'] = 'no';
                    $params['name'] = $record->rdata->exchange;
                }
                $params['value'] = $record->rdata->preference;
                break;
            case 'PTR':
                $params['name'] = $record->nameToAbsolute($this->domain);
                $params['value'] = trim($record->rdata->toString(), '.').'.';
                break;
            default:
                $params['name'] = $record->nameToAbsolute($this->domain);
                $params['value'] = $record->rdata->toString();
                break;
        }
        $params['value'] = str_replace("\t", ' ', $params['value']);
        $this->get('CMD_API_DNS_ADMIN', $params);
    }
    
    /* DA nie posiada update */
    public function editRecord(dns\record\Record $record) {
        $records = $this->getRecords();
        foreach($records as $r) {
            if($r->line == $record->line) {
                $this->deleteRecord($r);
                break;
            }
        }
        
        $this->addRecord($record);
    }

    public function deleteRecord(dns\record\Record $record)
    {
        $params = [
            'domain' => $this->domain,
            'action' => 'select',
            'delete' => 'yes'
        ];

        if($record->type === 'MX' && $this->config['full_mx_records'] == 'on')
        {
            $params[strtolower($record->type) . 'recs0'] = 'full_mx_records=yes&name='.$record->nameToAbsolute($this->domain).'&value='.str_replace("\t", ' ', $record->rdata->toString());
            $params[strtolower($record->type) . 'recs1'] = 'full_mx_records=yes&name='.urlencode($record->nameToRelative($this->domain)).'&value='.str_replace("\t", ' ', $record->rdata->toString());
        } 
        else if($record->type === 'TXT')
        {
            $params[strtolower($record->type) . 'recs0'] = 'name='.urlencode($record->nameToAbsolute($this->domain)).'&value='.(str_replace("\t", ' ', $record->rdata->toString()));
            $params[strtolower($record->type) . 'recs1'] = 'name='.urlencode($record->nameToRelative($this->domain)).'&value='.(str_replace("\t", ' ', $record->rdata->toString()));
        }        
        else
        {
            $params[strtolower($record->type) . 'recs0'] = 'name='.urlencode($record->nameToAbsolute($this->domain)).'&value='.str_replace("\t", ' ', $record->rdata->toString());
            $params[strtolower($record->type) . 'recs1'] = 'name='.urlencode($record->nameToRelative($this->domain)).'&value='.str_replace("\t", ' ', $record->rdata->toString());
        }

        $this->get('CMD_API_DNS_ADMIN', $params);
    }

    
    public function activateZone() 
    {

        if($this->ip != '') {
            if(!filter_var($this->ip, FILTER_VALIDATE_IP)) {
                throw new exceptions\DNSSubmoduleException('IP is not valid!', dns\SubmoduleExceptionCodes::INVALID_PARAMETERS);
            }
        } else {
            $this->ip = $this->config['default_ip'];
        }

        global $CONFIG;

        $ns1 = !empty($this->config['ns1'])?$this->config['ns1'] : $CONFIG['DefaultNameserver1'];
        $ns2 = !empty($this->config['ns2'])?$this->config['ns2'] : $CONFIG['DefaultNameserver2'];

        $this->get('CMD_API_DNS_ADMIN', array(
            'action'    =>  'create',
            'domain'    =>  $this->domain,
            'ip'        =>  $this->ip,
            'ns1'       =>  $ns1,
            'ns2'       => $ns2
        ));
    }

    public function terminateZone() {
        $this->get('CMD_API_DNS_ADMIN', array(
            'action'    =>  'delete',
            'select0'   =>  $this->domain
        ));
    }
    
    private function checkOutput($out){
        if(strpos($out, 'You cannot execute that command') !== false) {
            throw new exceptions\DNSSubmoduleException('You cannot execute that command', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
        } elseif(strpos($out, 'Please enter your Username and Password') !== false) {
            throw new exceptions\DNSSubmoduleException('Incorrect username or password', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
        } elseif(strpos($out, 'Your IP is blacklisted') !== false) {
            throw new exceptions\DNSSubmoduleException('Your IP is blacklisted', dns\SubmoduleExceptionCodes::CONNECTION_PROBLEM);
        }
    }

    public function getZones()
    {
        $page = 1;
        $out = [];
        do
        {
            $return = $this->get('CMD_API_DNS_ADMIN', ['page' => $page, 'ipp' => 1000, 'json' => 'yes']);
            $domainsData = array_diff_key(json_decode($return, true), array_flip(['info']));
            foreach($domainsData as $data)
            {
                $out[$data['domain']] = '';
            }
            $page++;
        } while( $domainsData );

        return $out;
    }

    public function getSignKeys() 
    {
        $ret = $this->getDnsSecKeys();

        parse_str($ret, $arr);
        $arr = array_map(function($value){ 
            return preg_split('/\s+/', $value);   
        }, $arr);
        
        if(!isset($arr['DS']))
        {
            return null;
        }
         
        //KSK
        $dnskey =   new dns\record\type\DNSKEY();
        $dnskey->setProtocol($arr['ksk_DNSKEY'][1]);
        $dnskey->setFlags($arr['ksk_DNSKEY'][0]);
        $dnskey->setAlgorithm($arr['ksk_DNSKEY'][2]);
        $dnskey->setPublicKey(implode(' ', array_slice($arr['ksk_DNSKEY'], 3)));
        
        $ksk    =   new dns\dnssec\KSK();
        $ksk->setId($arr['ksk_id'][0]);
        $ksk->setDnsKey($dnskey);
        $ksk->setExpires($arr['expiry'][0]);
        
        //ZSK
        $dnskey =   new dns\record\type\DNSKEY();
        $dnskey->setProtocol($arr['zsk_DNSKEY'][1]);
        $dnskey->setFlags($arr['zsk_DNSKEY'][0]);
        $dnskey->setAlgorithm($arr['zsk_DNSKEY'][2]);
        $dnskey->setPublicKey(implode(' ', array_slice($arr['zsk_DNSKEY'], 3)));
                
        $zsk    =   new dns\dnssec\ZSK();
        $zsk->setId($arr['zsk_id'][0]);
        $zsk->setDnsKey($dnskey);
        $zsk->setExpires($arr['expiry'][0]);
        
        //DS
        $ds1    =   new dns\record\type\DS($arr['DS'][3], $arr['DS'][4], $arr['DS'][5], $arr['DS'][6]);
        $ds2    =   new dns\record\type\DS($arr['DS'][10], $arr['DS'][11], $arr['DS'][12], $arr['DS'][13]);
        
        //DNSSEC
        $dnssec =   new dns\dnssec\DnsSec();
        $dnssec->addKsk($ksk);
        $dnssec->addZsk($zsk);   
        $dnssec->addDs($ds1);
        $dnssec->addDs($ds2);
        
        return $dnssec;
    } 
    
    
    public function sign() 
    {
        $this->get('CMD_API_DNS_ADMIN', array(
            'action'            =>  'dnssec',
            'generate_keys'     =>  true,
            'domain'            =>  $this->domain
        )); 
                
        $this->get('CMD_API_DNS_ADMIN', array(
            'action'            =>  'dnssec',
            'sign_zone'         =>  true,
            'domain'            =>  $this->domain
        ));    
    }
    
    public function rectify(){
        $this->sign();
    }

    public function unsign()
    {
        throw new exceptions\DNSSubmoduleException('DirectAdmin does not support unsign command.', dns\SubmoduleExceptionCodes::COMMAND_ERROR);
    }

    public function isSigned()
    {
        return $this->getDnsSecKeys() ? true : false;
    }
    
    protected function getDnsSecKeys()
    {
        return $this->get('CMD_API_DNS_ADMIN', array(
            'action'            =>  'dnssec',
            'value'             =>  'get_keys',
            'domain'            =>  $this->domain
        )); 
    }
}