<?php

namespace RemotelyLiving\PHPDNS\Entities;

use RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException;

use function array_flip;
use function in_array;
use function strtoupper;

final class DNSRecordType extends EntityAbstract
{
    public const TYPE_A = 'A';
    public const TYPE_CNAME = 'CNAME';
    public const TYPE_HINFO = 'HINFO';
    public const TYPE_CAA = 'CAA';
    public const TYPE_MX = 'MX';
    public const TYPE_NS = 'NS';
    public const TYPE_PTR = 'PTR';
    public const TYPE_SOA = 'SOA';
    public const TYPE_TXT = 'TXT';
    public const TYPE_AAAA = 'AAAA';
    public const TYPE_SRV = 'SRV';
    public const TYPE_NAPTR = 'NAPTR';
    public const TYPE_A6 = 'A6';
    public const TYPE_ANY = 'ANY';

    public const VALID_TYPES = [
        self::TYPE_A,
        self::TYPE_CNAME,
        self::TYPE_HINFO,
        self::TYPE_CAA,
        self::TYPE_MX,
        self::TYPE_NS,
        self::TYPE_PTR,
        self::TYPE_SOA,
        self::TYPE_TXT,
        self::TYPE_AAAA,
        self::TYPE_SRV,
        self::TYPE_NAPTR,
        self::TYPE_A6,
        self::TYPE_ANY,
    ];

    protected const CODE_TYPE_MAP = [
        1 => DNSRecordType::TYPE_A,
        5 => DNSRecordType::TYPE_CNAME,
        13 => DNSRecordType::TYPE_HINFO,
        257 => DNSRecordType::TYPE_CAA,
        15 => DNSRecordType::TYPE_MX,
        2 => DNSRecordType::TYPE_NS,
        12 => DNSRecordType::TYPE_PTR,
        6 => DNSRecordType::TYPE_SOA,
        16 => DNSRecordType::TYPE_TXT,
        28 => DNSRecordType::TYPE_AAAA,
        33 => DNSRecordType::TYPE_SRV,
        35 => DNSRecordType::TYPE_NAPTR,
        38 => DNSRecordType::TYPE_A6,
        255 => DNSRecordType::TYPE_ANY,
    ];

    private string $type;

    /**
     * @throws \RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException
     */
    public function __construct(string $type)
    {
        if (!in_array($type, self::VALID_TYPES, true)) {
            throw new InvalidArgumentException("{$type} is not an existing DNS record type");
        }

        $this->type = $type;
    }

    public function __toString(): string
    {
        return $this->type;
    }

    /**
     * @throws \RemotelyLiving\PHPDNS\Exceptions\InvalidArgumentException
     */
    public static function createFromInt(int $code): DNSRecordType
    {
        if (!isset(self::CODE_TYPE_MAP[$code])) {
            throw new InvalidArgumentException("{$code} is not able to be mapped to an existing DNS record type");
        }

        return new self(self::CODE_TYPE_MAP[$code]);
    }

    public static function createFromString(string $type): DNSRecordType
    {
        return new self($type);
    }

    public function toInt(): int
    {
        return array_flip(self::CODE_TYPE_MAP)[$this->type];
    }

    public function isA(string $type): bool
    {
        return $this->type === strtoupper($type);
    }

    public function equals(DNSRecordType $recordType): bool
    {
        return $this->type === (string)$recordType;
    }

    public static function createA(): self
    {
        return self::createFromString(self::TYPE_A);
    }

    public static function createCNAME(): self
    {
        return self::createFromString(self::TYPE_CNAME);
    }

    public static function createHINFO(): self
    {
        return self::createFromString(self::TYPE_HINFO);
    }

    public static function createCAA(): self
    {
        return self::createFromString(self::TYPE_CAA);
    }

    public static function createMX(): self
    {
        return self::createFromString(self::TYPE_MX);
    }

    public static function createNS(): self
    {
        return self::createFromString(self::TYPE_NS);
    }

    public static function createPTR(): self
    {
        return self::createFromString(self::TYPE_PTR);
    }

    public static function createSOA(): self
    {
        return self::createFromString(self::TYPE_SOA);
    }

    public static function createTXT(): self
    {
        return self::createFromString(self::TYPE_TXT);
    }

    public static function createAAAA(): self
    {
        return self::createFromString(self::TYPE_AAAA);
    }

    public static function createSRV(): self
    {
        return self::createFromString(self::TYPE_SRV);
    }

    public static function createNAPTR(): self
    {
        return self::createFromString(self::TYPE_NAPTR);
    }

    public static function createA6(): self
    {
        return self::createFromString(self::TYPE_A6);
    }

    public static function createANY(): self
    {
        return self::createFromString(self::TYPE_ANY);
    }
}
