<?php

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

use MGModule\DNSManager2\mgLibs\custom\dns\record\type\RecordTypeAbstract;
use MGModule\DNSManager2\mgLibs\custom\dns\record\validators\NameValidator;
use \MGModule\DNSManager2\mgLibs\custom\dns\utils\IDNAConvert;
use MGModule\DNSManager2\mgLibs\custom\dns\record\interfaces\ArrayPrinter;
use MGModule\DNSManager2\mgLibs\custom\helpers\IdnaHelper;

/**
 * rfc1035 (http://tools.ietf.org/html/rfc1035)
 */
class Record implements \ArrayAccess, ArrayPrinter
{
    public $line;

    public $name;
    public $type;
    public $class = 'IN';
    public $ttl;
    public $rdlength;
    /** @var type\RecordTypeAbstract */
    public $rdata;
    public $absoluteName = true;  #display record name as absolute name (using the domain name)
    public $customData;

    public function toArray($uppercase = true)
    {
        $out = [];
        foreach (get_object_vars($this) as $k => $v)
        {
            $out[$uppercase ? strtoupper($k) : strtolower($k)] = ($k == 'rdata') ? $v->toArray($uppercase) : $v;
        }
        return $out;
    }

    public function toMergedArray($uppercase = true)
    {
        $out   = $this->toArray($uppercase);
        $rdata = $out[$uppercase ? 'RDATA' : 'rdata'];
        unset($out[$uppercase ? 'RDATA' : 'rdata']);
        return array_merge($out, $rdata);
    }

    public function createRDATAObject($recordType = false)
    {
        $class = __NAMESPACE__ . '\\type\\' . ($recordType !== false ? $recordType : $this->type);

        if (class_exists($class))
        {
            $this->rdata = new $class();
        }
        else
        {
            $this->rdata = new type\Def();
        }
    }

    public static function tryToCreateFromArray($array, $old = false)
    { //TODO: WYWALIC TO...
        $record = new self();
        $record->setDataFromArray($array);
        $class = __NAMESPACE__ . '\\type\\' . $array['type'];

        if ($old)
        {
            $rdata = new type\OldDNS();
        }
        elseif (class_exists($class))
        {
            $rdata = new $class();
        }
        else
        {
            $rdata = new type\Def();
        }

        if (isset($array['value']) && !$old)
        {
            $rdata->setFirstProperty($array['value']);
        }
        $rdata->setDataFromarray($array);
        if (isset($array['rdata']) && is_array($array['rdata']))
        {
            $rdata->setDataFromArray($array['rdata']);
        }
        $record->rdata = $rdata;
        return $record;
    }

    public function setDataFromArray($array)
    {
        foreach ($array as $k => $value)
        {
            $k = strtolower($k);
            if (property_exists($this, $k))
            {
                $this->$k = $value;
            }
        }
    }

    public function isEqualTo(Record $record)
    {
        return $this->toArray() == $record->toArray();
    }

    public function compare(Record $record, $domain)
    {
        $this->unify($domain);

        $first = $this->toArray();
        unset($first['LINE']);
        unset($first['TTL']);

        $record->unify($domain);
        $second = $record->toArray();
        unset($second['LINE']);
        unset($second['TTL']);
        return $first == $second && ($first['RDATA'] === $second['RDATA']);
    }

    public function unify($domain)
    {
        $this->rdata->unify();
        $this->name = str_replace("." . $domain . ".", "", $this->name);

        //@todo - this should be fixed in submodule!
        if ($this->name == $domain)
        {
            $this->name .= '.';
        }
    }


    public function encode()
    {
        foreach (get_object_vars($this) as $k => $v)
        {
            if ($k == 'rdata')
            {
                continue;
            }
            $this->$k = htmlspecialchars($v);
        }
        if (is_object($this->rdata))
        {
            $this->rdata->encode();
        }
    }

    public function decode()
    {
        foreach (get_object_vars($this) as $k => $v)
        {
            if ($k == 'rdata')
            {
                continue;
            }
            $this->$k = htmlspecialchars_decode($v);
        }
        if (is_object($this->rdata))
        {
            $this->rdata->decode();
        }
    }

    private function idnaEncode($domain)
    {
        $idna = new IDNAConvert();
        return $idna->encode($domain);
    }

    private function idnaDecode($domain)
    {
        $idna = new IDNAConvert();
        return $idna->decode($domain);
    }

    /**
     * Returns the domain with domain attached
     * @param string $zoneName Should be a valid zone name
     * @param bool $with_dot if true function will attach the dot at the end
     * @return string
     */
    public function nameToAbsolute($zoneName, $with_dot = true)
    {
        //Make sure we don't have dot at the end
        $zoneName = rtrim(IdnaHelper::idnaDecode(IdnaHelper::idnaEncode($zoneName)), '.');

        $this->name = str_replace('@', $zoneName . '.', $this->name);
        //Match string that has :
        //0 or 1 dot at the beggingng (handles subdomains and domains)
        //Domain name with one or more dots at the END of the string
        $domainRegex = '/\.*' . preg_quote($zoneName,'/') . '\.+$/';
        //Try to remove the domain
        $newRecordName = preg_replace($domainRegex, '', IdnaHelper::idnaDecode($this->name), 1);
        $newRecordName = preg_replace('/\.{2,}/', '.', $newRecordName);
        //If record without zone name has no length we just attach the zone name
        //Else we attach recordname with dot
        $newRecordName = $newRecordName === '' ? $zoneName : rtrim($newRecordName, '.') . '.' . $zoneName;
        $this->name    = $with_dot ? rtrim($newRecordName, '.') . '.' : $newRecordName;

        return $this->name;
    }

    /**
     * Returns record name without zone name attached
     * @param $zoneName
     * @return string
     */
    public function nameToRelative($zoneName)
    {
        //Make sure we don't have dot at the end
        $zoneName = rtrim(IdnaHelper::idnaDecode(IdnaHelper::idnaEncode($zoneName)), '.');

        //Match string that has :
        //0 or 1 dot at the beggingng (handles subdomains and domains)
        //Domain name with one or more dots at the END of the string
        $domainRegex = '/\.?' . preg_quote($zoneName,'/') . '\.+$/';
        $newName     = preg_replace($domainRegex, '', IdnaHelper::idnaDecode($this->name), 1);
        $this->name  = $newName ?: '@';
        return $this->name;
    }

    //IMPLEMENTS
    public function offsetExists($offset)
    {
        return isset($this->$offset);
    }

    public function offsetGet($offset)
    {
        return $this->$offset;
    }

    public function offsetSet($offset, $value)
    {
        if ($this->offsetExists($offset))
        {
            $this->$offset = $value;
        }
    }

    public function offsetUnset($offset)
    {
        if ($this->offsetExists($offset))
        {
            $this->$offset = '';
        }
    }

    public function __toString()
    {
        $out = '';
        foreach (get_object_vars($this) as $key => $value)
        {
            if ($value instanceof RecordTypeAbstract)
            {
                $out .= $key . ': ' . $value->__toString() . ' ';
                continue;
            }
            $out .= $key . ': ' . $value . ' ';
        }
        return $out;
    }
}
