package dns

import (
	"errors"
	"fmt"
	"reflect"
	"strings"
	"sync"
	"time"

	"github.com/akamai/AkamaiOPEN-edgegrid-golang/client-v1"
)

type name struct {
	recordType string
	name       string
}

var (
	cnameNames    []name
	nonCnameNames []name
	zoneWriteLock sync.Mutex
)

// Zone represents a DNS zone
type Zone struct {
	Token string `json:"token"`
	Zone  struct {
		Name       string              `json:"name,omitempty"`
		A          []*ARecord          `json:"a,omitempty"`
		Aaaa       []*AaaaRecord       `json:"aaaa,omitempty"`
		Afsdb      []*AfsdbRecord      `json:"afsdb,omitempty"`
		Cname      []*CnameRecord      `json:"cname,omitempty"`
		Dnskey     []*DnskeyRecord     `json:"dnskey,omitempty"`
		Ds         []*DsRecord         `json:"ds,omitempty"`
		Hinfo      []*HinfoRecord      `json:"hinfo,omitempty"`
		Loc        []*LocRecord        `json:"loc,omitempty"`
		Mx         []*MxRecord         `json:"mx,omitempty"`
		Naptr      []*NaptrRecord      `json:"naptr,omitempty"`
		Ns         []*NsRecord         `json:"ns,omitempty"`
		Nsec3      []*Nsec3Record      `json:"nsec3,omitempty"`
		Nsec3param []*Nsec3paramRecord `json:"nsec3param,omitempty"`
		Ptr        []*PtrRecord        `json:"ptr,omitempty"`
		Rp         []*RpRecord         `json:"rp,omitempty"`
		Rrsig      []*RrsigRecord      `json:"rrsig,omitempty"`
		Soa        *SoaRecord          `json:"soa,omitempty"`
		Spf        []*SpfRecord        `json:"spf,omitempty"`
		Srv        []*SrvRecord        `json:"srv,omitempty"`
		Sshfp      []*SshfpRecord      `json:"sshfp,omitempty"`
		Txt        []*TxtRecord        `json:"txt,omitempty"`
	} `json:"zone"`
}

// NewZone creates a new Zone
func NewZone(hostname string) *Zone {
	zone := &Zone{Token: "new"}
	zone.Zone.Soa = NewSoaRecord()
	zone.Zone.Name = hostname
	return zone
}

// GetZone retrieves a DNS Zone for a given hostname
func GetZone(hostname string) (*Zone, error) {
	zone := NewZone(hostname)
	req, err := client.NewRequest(
		Config,
		"GET",
		"/config-dns/v1/zones/"+hostname,
		nil,
	)
	if err != nil {
		return nil, err
	}

	res, err := client.Do(Config, req)
	if err != nil {
		return nil, err
	}

	if client.IsError(res) && res.StatusCode != 404 {
		return nil, client.NewAPIError(res)
	} else if res.StatusCode == 404 {
		return nil, &ZoneError{zoneName: hostname}
	} else {
		err = client.BodyJSON(res, &zone)
		if err != nil {
			return nil, err
		}

		return zone, nil
	}
}

// Save updates the Zone
func (zone *Zone) Save() error {
	// This lock will restrict the concurrency of API calls
	// to 1 save request at a time. This is needed for the Soa.Serial value which
	// is required to be incremented for every subsequent update to a zone
	// so we have to save just one request at a time to ensure this is always
	// incremented properly
	zoneWriteLock.Lock()
	defer zoneWriteLock.Unlock()

	valid, f := zone.validateCnames()
	if valid == false {
		var msg string
		for _, v := range f {
			msg = msg + fmt.Sprintf("\n%s Record '%s' conflicts with CNAME", v.recordType, v.name)
		}
		return &ZoneError{
			zoneName:        zone.Zone.Name,
			apiErrorMessage: "All CNAMEs must be unique in the zone" + msg,
		}
	}

	req, err := client.NewJSONRequest(
		Config,
		"POST",
		"/config-dns/v1/zones/"+zone.Zone.Name,
		zone,
	)
	if err != nil {
		return err
	}

	res, err := client.Do(Config, req)

	// Network error
	if err != nil {
		return &ZoneError{
			zoneName:         zone.Zone.Name,
			httpErrorMessage: err.Error(),
			err:              err,
		}
	}

	// API error
	if client.IsError(res) {
		err := client.NewAPIError(res)
		return &ZoneError{zoneName: zone.Zone.Name, apiErrorMessage: err.Detail, err: err}
	}

	for {
		updatedZone, err := GetZone(zone.Zone.Name)
		if err != nil {
			return err
		}

		if updatedZone.Token != zone.Token {
			*zone = *updatedZone
			break
		}
		time.Sleep(time.Second)
	}

	return nil
}

func (zone *Zone) Delete() error {
	// remove all the records except for SOA
	// which is required and save the zone
	zone.Zone.A = nil
	zone.Zone.Aaaa = nil
	zone.Zone.Afsdb = nil
	zone.Zone.Cname = nil
	zone.Zone.Dnskey = nil
	zone.Zone.Ds = nil
	zone.Zone.Hinfo = nil
	zone.Zone.Loc = nil
	zone.Zone.Mx = nil
	zone.Zone.Naptr = nil
	zone.Zone.Ns = nil
	zone.Zone.Nsec3 = nil
	zone.Zone.Nsec3param = nil
	zone.Zone.Ptr = nil
	zone.Zone.Rp = nil
	zone.Zone.Rrsig = nil
	zone.Zone.Spf = nil
	zone.Zone.Srv = nil
	zone.Zone.Sshfp = nil
	zone.Zone.Txt = nil

	return zone.Save()
}

func (zone *Zone) AddRecord(recordPtr interface{}) error {
	switch recordPtr.(type) {
	case *ARecord:
		zone.addARecord(recordPtr.(*ARecord))
	case *AaaaRecord:
		zone.addAaaaRecord(recordPtr.(*AaaaRecord))
	case *AfsdbRecord:
		zone.addAfsdbRecord(recordPtr.(*AfsdbRecord))
	case *CnameRecord:
		zone.addCnameRecord(recordPtr.(*CnameRecord))
	case *DnskeyRecord:
		zone.addDnskeyRecord(recordPtr.(*DnskeyRecord))
	case *DsRecord:
		zone.addDsRecord(recordPtr.(*DsRecord))
	case *HinfoRecord:
		zone.addHinfoRecord(recordPtr.(*HinfoRecord))
	case *LocRecord:
		zone.addLocRecord(recordPtr.(*LocRecord))
	case *MxRecord:
		zone.addMxRecord(recordPtr.(*MxRecord))
	case *NaptrRecord:
		zone.addNaptrRecord(recordPtr.(*NaptrRecord))
	case *NsRecord:
		zone.addNsRecord(recordPtr.(*NsRecord))
	case *Nsec3Record:
		zone.addNsec3Record(recordPtr.(*Nsec3Record))
	case *Nsec3paramRecord:
		zone.addNsec3paramRecord(recordPtr.(*Nsec3paramRecord))
	case *PtrRecord:
		zone.addPtrRecord(recordPtr.(*PtrRecord))
	case *RpRecord:
		zone.addRpRecord(recordPtr.(*RpRecord))
	case *RrsigRecord:
		zone.addRrsigRecord(recordPtr.(*RrsigRecord))
	case *SoaRecord:
		zone.addSoaRecord(recordPtr.(*SoaRecord))
	case *SpfRecord:
		zone.addSpfRecord(recordPtr.(*SpfRecord))
	case *SrvRecord:
		zone.addSrvRecord(recordPtr.(*SrvRecord))
	case *SshfpRecord:
		zone.addSshfpRecord(recordPtr.(*SshfpRecord))
	case *TxtRecord:
		zone.addTxtRecord(recordPtr.(*TxtRecord))
	}

	return nil
}

func (zone *Zone) RemoveRecord(recordPtr interface{}) error {
	switch recordPtr.(type) {
	case *ARecord:
		return zone.removeARecord(recordPtr.(*ARecord))
	case *AaaaRecord:
		return zone.removeAaaaRecord(recordPtr.(*AaaaRecord))
	case *AfsdbRecord:
		return zone.removeAfsdbRecord(recordPtr.(*AfsdbRecord))
	case *CnameRecord:
		return zone.removeCnameRecord(recordPtr.(*CnameRecord))
	case *DnskeyRecord:
		return zone.removeDnskeyRecord(recordPtr.(*DnskeyRecord))
	case *DsRecord:
		return zone.removeDsRecord(recordPtr.(*DsRecord))
	case *HinfoRecord:
		return zone.removeHinfoRecord(recordPtr.(*HinfoRecord))
	case *LocRecord:
		return zone.removeLocRecord(recordPtr.(*LocRecord))
	case *MxRecord:
		return zone.removeMxRecord(recordPtr.(*MxRecord))
	case *NaptrRecord:
		return zone.removeNaptrRecord(recordPtr.(*NaptrRecord))
	case *NsRecord:
		return zone.removeNsRecord(recordPtr.(*NsRecord))
	case *Nsec3Record:
		return zone.removeNsec3Record(recordPtr.(*Nsec3Record))
	case *Nsec3paramRecord:
		return zone.removeNsec3paramRecord(recordPtr.(*Nsec3paramRecord))
	case *PtrRecord:
		return zone.removePtrRecord(recordPtr.(*PtrRecord))
	case *RpRecord:
		return zone.removeRpRecord(recordPtr.(*RpRecord))
	case *RrsigRecord:
		return zone.removeRrsigRecord(recordPtr.(*RrsigRecord))
	case *SoaRecord:
		return zone.removeSoaRecord(recordPtr.(*SoaRecord))
	case *SpfRecord:
		return zone.removeSpfRecord(recordPtr.(*SpfRecord))
	case *SrvRecord:
		return zone.removeSrvRecord(recordPtr.(*SrvRecord))
	case *SshfpRecord:
		return zone.removeSshfpRecord(recordPtr.(*SshfpRecord))
	case *TxtRecord:
		return zone.removeTxtRecord(recordPtr.(*TxtRecord))
	}

	return nil
}

func (zone *Zone) addARecord(record *ARecord) {
	zone.Zone.A = append(zone.Zone.A, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "A", name: record.Name})
}

func (zone *Zone) addAaaaRecord(record *AaaaRecord) {
	zone.Zone.Aaaa = append(zone.Zone.Aaaa, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "AAAA", name: record.Name})
}

func (zone *Zone) addAfsdbRecord(record *AfsdbRecord) {
	zone.Zone.Afsdb = append(zone.Zone.Afsdb, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "AFSDB", name: record.Name})
}

func (zone *Zone) addCnameRecord(record *CnameRecord) {
	zone.Zone.Cname = append(zone.Zone.Cname, record)
	cnameNames = append(cnameNames, name{recordType: "CNAME", name: record.Name})
}

func (zone *Zone) addDnskeyRecord(record *DnskeyRecord) {
	zone.Zone.Dnskey = append(zone.Zone.Dnskey, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "DNSKEY", name: record.Name})
}

func (zone *Zone) addDsRecord(record *DsRecord) {
	zone.Zone.Ds = append(zone.Zone.Ds, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "DS", name: record.Name})
}

func (zone *Zone) addHinfoRecord(record *HinfoRecord) {
	zone.Zone.Hinfo = append(zone.Zone.Hinfo, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "HINFO", name: record.Name})
}

func (zone *Zone) addLocRecord(record *LocRecord) {
	zone.Zone.Loc = append(zone.Zone.Loc, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "LOC", name: record.Name})
}

func (zone *Zone) addMxRecord(record *MxRecord) {
	zone.Zone.Mx = append(zone.Zone.Mx, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "MX", name: record.Name})
}

func (zone *Zone) addNaptrRecord(record *NaptrRecord) {
	zone.Zone.Naptr = append(zone.Zone.Naptr, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "NAPTR", name: record.Name})
}

func (zone *Zone) addNsRecord(record *NsRecord) {
	zone.Zone.Ns = append(zone.Zone.Ns, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "NS", name: record.Name})
}

func (zone *Zone) addNsec3Record(record *Nsec3Record) {
	zone.Zone.Nsec3 = append(zone.Zone.Nsec3, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "NSEC3", name: record.Name})
}

func (zone *Zone) addNsec3paramRecord(record *Nsec3paramRecord) {
	zone.Zone.Nsec3param = append(zone.Zone.Nsec3param, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "NSEC3PARAM", name: record.Name})
}

func (zone *Zone) addPtrRecord(record *PtrRecord) {
	zone.Zone.Ptr = append(zone.Zone.Ptr, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "PTR", name: record.Name})
}

func (zone *Zone) addRpRecord(record *RpRecord) {
	zone.Zone.Rp = append(zone.Zone.Rp, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "RP", name: record.Name})
}

func (zone *Zone) addRrsigRecord(record *RrsigRecord) {
	zone.Zone.Rrsig = append(zone.Zone.Rrsig, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "RRSIG", name: record.Name})
}

func (zone *Zone) addSoaRecord(record *SoaRecord) {
	// Only one SOA records is allowed
	zone.Zone.Soa = record
}

func (zone *Zone) addSpfRecord(record *SpfRecord) {
	zone.Zone.Spf = append(zone.Zone.Spf, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "SPF", name: record.Name})
}

func (zone *Zone) addSrvRecord(record *SrvRecord) {
	zone.Zone.Srv = append(zone.Zone.Srv, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "SRV", name: record.Name})
}

func (zone *Zone) addSshfpRecord(record *SshfpRecord) {
	zone.Zone.Sshfp = append(zone.Zone.Sshfp, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "SSHFP", name: record.Name})
}

func (zone *Zone) addTxtRecord(record *TxtRecord) {
	zone.Zone.Txt = append(zone.Zone.Txt, record)
	nonCnameNames = append(nonCnameNames, name{recordType: "TXT", name: record.Name})
}

func (zone *Zone) removeARecord(record *ARecord) error {
	for key, r := range zone.Zone.A {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.A[:key]
			if len(zone.Zone.A) > key {
				if len(zone.Zone.A) > key {
					zone.Zone.A = append(records, zone.Zone.A[key+1:]...)
				} else {
					zone.Zone.A = records
				}
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("A Record not found")
}

func (zone *Zone) removeAaaaRecord(record *AaaaRecord) error {
	for key, r := range zone.Zone.Aaaa {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Aaaa[:key]
			if len(zone.Zone.Aaaa) > key {
				zone.Zone.Aaaa = append(records, zone.Zone.Aaaa[key+1:]...)
			} else {
				zone.Zone.Aaaa = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("AAAA Record not found")
}

func (zone *Zone) removeAfsdbRecord(record *AfsdbRecord) error {
	for key, r := range zone.Zone.Afsdb {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Afsdb[:key]
			if len(zone.Zone.Afsdb) > key {
				zone.Zone.Afsdb = append(records, zone.Zone.Afsdb[key+1:]...)
			} else {
				zone.Zone.Afsdb = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Afsdb Record not found")
}

func (zone *Zone) removeCnameRecord(record *CnameRecord) error {
	for key, r := range zone.Zone.Cname {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Cname[:key]
			if len(zone.Zone.Cname) > key {
				zone.Zone.Cname = append(records, zone.Zone.Cname[key+1:]...)
			} else {
				zone.Zone.Cname = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Cname Record not found")

	zone.removeCnameName(record.Name)

	return nil
}

func (zone *Zone) removeDnskeyRecord(record *DnskeyRecord) error {
	for key, r := range zone.Zone.Dnskey {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Dnskey[:key]
			if len(zone.Zone.Dnskey) > key {
				zone.Zone.Dnskey = append(records, zone.Zone.Dnskey[key+1:]...)
			} else {
				zone.Zone.Dnskey = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Dnskey Record not found")
}

func (zone *Zone) removeDsRecord(record *DsRecord) error {
	for key, r := range zone.Zone.Ds {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Ds[:key]
			if len(zone.Zone.Ds) > key {
				zone.Zone.Ds = append(records, zone.Zone.Ds[key+1:]...)
			} else {
				zone.Zone.Ds = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Ds Record not found")
}

func (zone *Zone) removeHinfoRecord(record *HinfoRecord) error {
	for key, r := range zone.Zone.Hinfo {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Hinfo[:key]
			if len(zone.Zone.Hinfo) > key {
				zone.Zone.Hinfo = append(records, zone.Zone.Hinfo[key+1:]...)
			} else {
				zone.Zone.Hinfo = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Hinfo Record not found")
}

func (zone *Zone) removeLocRecord(record *LocRecord) error {
	for key, r := range zone.Zone.Loc {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Loc[:key]
			if len(zone.Zone.Loc) > key {
				zone.Zone.Loc = append(records, zone.Zone.Loc[key+1:]...)
			} else {
				zone.Zone.Loc = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Loc Record not found")
}

func (zone *Zone) removeMxRecord(record *MxRecord) error {
	for key, r := range zone.Zone.Mx {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Mx[:key]
			if len(zone.Zone.Mx) > key {
				zone.Zone.Mx = append(records, zone.Zone.Mx[key+1:]...)
			} else {
				zone.Zone.Mx = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Mx Record not found")
}

func (zone *Zone) removeNaptrRecord(record *NaptrRecord) error {
	for key, r := range zone.Zone.Naptr {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Naptr[:key]
			if len(zone.Zone.Naptr) > key {
				zone.Zone.Naptr = append(records, zone.Zone.Naptr[key+1:]...)
			} else {
				zone.Zone.Naptr = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Naptr Record not found")
}

func (zone *Zone) removeNsRecord(record *NsRecord) error {
	for key, r := range zone.Zone.Ns {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Ns[:key]
			if len(zone.Zone.Ns) > key {
				zone.Zone.Ns = append(records, zone.Zone.Ns[key+1:]...)
			} else {
				zone.Zone.Ns = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Ns Record not found")
}

func (zone *Zone) removeNsec3Record(record *Nsec3Record) error {
	for key, r := range zone.Zone.Nsec3 {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Nsec3[:key]
			if len(zone.Zone.Nsec3) > key {
				zone.Zone.Nsec3 = append(records, zone.Zone.Nsec3[key+1:]...)
			} else {
				zone.Zone.Nsec3 = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Nsec3 Record not found")
}

func (zone *Zone) removeNsec3paramRecord(record *Nsec3paramRecord) error {
	for key, r := range zone.Zone.Nsec3param {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Nsec3param[:key]
			if len(zone.Zone.Nsec3param) > key {
				zone.Zone.Nsec3param = append(records, zone.Zone.Nsec3param[key+1:]...)
			} else {
				zone.Zone.Nsec3param = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Nsec3param Record not found")
}

func (zone *Zone) removePtrRecord(record *PtrRecord) error {
	for key, r := range zone.Zone.Ptr {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Ptr[:key]
			if len(zone.Zone.Ptr) > key {
				zone.Zone.Ptr = append(records, zone.Zone.Ptr[key+1:]...)
			} else {
				zone.Zone.Ptr = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Ptr Record not found")
}

func (zone *Zone) removeRpRecord(record *RpRecord) error {
	for key, r := range zone.Zone.Rp {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Rp[:key]
			if len(zone.Zone.Rp) > key {
				zone.Zone.Rp = append(records, zone.Zone.Rp[key+1:]...)
			} else {
				zone.Zone.Rp = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Rp Record not found")
}

func (zone *Zone) removeRrsigRecord(record *RrsigRecord) error {
	for key, r := range zone.Zone.Rrsig {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Rrsig[:key]
			if len(zone.Zone.Rrsig) > key {
				zone.Zone.Rrsig = append(records, zone.Zone.Rrsig[key+1:]...)
			} else {
				zone.Zone.Rrsig = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Rrsig Record not found")
}

func (zone *Zone) removeSoaRecord(record *SoaRecord) error {
	if reflect.DeepEqual(zone.Zone.Soa, record) {
		zone.Zone.Soa = nil

		return nil
	}

	return errors.New("SOA Record does not match")
}

func (zone *Zone) removeSpfRecord(record *SpfRecord) error {
	for key, r := range zone.Zone.Spf {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Spf[:key]
			if len(zone.Zone.Spf) > key {
				zone.Zone.Spf = append(records, zone.Zone.Spf[key+1:]...)
			} else {
				zone.Zone.Spf = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Spf Record not found")
}

func (zone *Zone) removeSrvRecord(record *SrvRecord) error {
	for key, r := range zone.Zone.Srv {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Srv[:key]
			if len(zone.Zone.Srv) > key {
				zone.Zone.Srv = append(records, zone.Zone.Srv[key+1:]...)
			} else {
				zone.Zone.Srv = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Srv Record not found")
}

func (zone *Zone) removeSshfpRecord(record *SshfpRecord) error {
	for key, r := range zone.Zone.Sshfp {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Sshfp[:key]
			if len(zone.Zone.Sshfp) > key {
				zone.Zone.Sshfp = append(records, zone.Zone.Sshfp[key+1:]...)
			} else {
				zone.Zone.Sshfp = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Sshfp Record not found")
}

func (zone *Zone) removeTxtRecord(record *TxtRecord) error {
	for key, r := range zone.Zone.Txt {
		if reflect.DeepEqual(r, record) {
			records := zone.Zone.Txt[:key]
			if len(zone.Zone.Txt) > key {
				zone.Zone.Txt = append(records, zone.Zone.Txt[key+1:]...)
			} else {
				zone.Zone.Txt = records
			}
			zone.removeNonCnameName(record.Name)

			return nil
		}
	}

	return errors.New("Txt Record not found")
}

func (zone *Zone) PreMarshalJSON() error {
	if zone.Zone.Soa.Serial > 0 {
		zone.Zone.Soa.Serial = zone.Zone.Soa.Serial + 1
	} else {
		zone.Zone.Soa.Serial = uint(time.Now().Unix())
	}
	return nil
}

func (zone *Zone) validateCnames() (bool, []name) {
	var valid bool = true
	var failedRecords []name
	for _, v := range cnameNames {
		for _, vv := range nonCnameNames {
			if v.name == vv.name {
				valid = false
				failedRecords = append(failedRecords, vv)
			}
		}
	}
	return valid, failedRecords
}

func (zone *Zone) removeCnameName(host string) {
	for i, v := range cnameNames {
		if v.name == host {
			r := cnameNames[:i]
			cnameNames = append(r, cnameNames[i+1:]...)
		}
	}
}

func (zone *Zone) removeNonCnameName(host string) {
	for i, v := range nonCnameNames {
		if v.name == host {
			r := nonCnameNames[:i]
			nonCnameNames = append(r, nonCnameNames[i+1:]...)
		}
	}
}

func (zone *Zone) FindRecords(recordType string, options map[string]interface{}) []DNSRecord {
	switch strings.ToUpper(recordType) {
	case "A":
		return zone.findARecord(options)
	case "AAAA":
		return zone.findAaaaRecord(options)
	case "AFSDB":
		return zone.findAfsdbRecord(options)
	case "CNAME":
		return zone.findCnameRecord(options)
	case "DNSKEY":
		return zone.findDnskeyRecord(options)
	case "DS":
		return zone.findDsRecord(options)
	case "HINFO":
		return zone.findHinfoRecord(options)
	case "LOC":
		return zone.findLocRecord(options)
	case "MX":
		return zone.findMxRecord(options)
	case "NAPTR":
		return zone.findNaptrRecord(options)
	case "NS":
		return zone.findNsRecord(options)
	case "NSEC3":
		return zone.findNsec3Record(options)
	case "NSEC3PARAM":
		return zone.findNsec3paramRecord(options)
	case "PTR":
		return zone.findPtrRecord(options)
	case "RP":
		return zone.findRpRecord(options)
	case "RRSIG":
		return zone.findRrsigRecord(options)
	case "SPF":
		return zone.findSpfRecord(options)
	case "SRV":
		return zone.findSrvRecord(options)
	case "SSHFP":
		return zone.findSshfpRecord(options)
	case "TXT":
		return zone.findTxtRecord(options)
	}

	return make([]DNSRecord, 0)
}

func (zone *Zone) findARecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.A {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if target, ok := options["target"]; ok && record.Target == target.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}

	return found
}

func (zone *Zone) findAaaaRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Aaaa {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if target, ok := options["target"]; ok && record.Target == target.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}

	return found
}

func (zone *Zone) findAfsdbRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Afsdb {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if target, ok := options["target"]; ok && record.Target == target.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if subtype, ok := options["subtype"]; ok && record.Subtype == subtype.(int) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findCnameRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Cname {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if target, ok := options["target"]; ok && record.Target == target.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findDnskeyRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Dnskey {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if flags, ok := options["flags"]; ok && record.Flags == flags.(int) {
			matchCounter++
		}

		if protocol, ok := options["protocol"]; ok && record.Protocol == protocol.(int) {
			matchCounter++
		}

		if algorithm, ok := options["algorithm"]; ok && record.Algorithm == algorithm.(int) {
			matchCounter++
		}

		if key, ok := options["key"]; ok && record.Key == key.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findDsRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Ds {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if keytag, ok := options["keytag"]; ok && record.Keytag == keytag.(int) {
			matchCounter++
		}

		if algorithm, ok := options["algorithm"]; ok && record.Algorithm == algorithm.(int) {
			matchCounter++
		}

		if digesttype, ok := options["digesttype"]; ok && record.DigestType == digesttype.(int) {
			matchCounter++
		}

		if digest, ok := options["digest"]; ok && record.Digest == digest.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findHinfoRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Hinfo {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if hardware, ok := options["hardware"]; ok && record.Hardware == hardware.(string) {
			matchCounter++
		}

		if software, ok := options["software"]; ok && record.Software == software.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findLocRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Loc {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if target, ok := options["target"]; ok && record.Target == target.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findMxRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Mx {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if target, ok := options["target"]; ok && record.Target == target.(string) {
			matchCounter++
		}

		if priority, ok := options["priority"]; ok && record.Priority == priority.(int) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findNaptrRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Naptr {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if order, ok := options["order"]; ok && record.Order == order.(uint16) {
			matchCounter++
		}

		if preference, ok := options["preference"]; ok && record.Preference == preference.(uint16) {
			matchCounter++
		}

		if flags, ok := options["flags"]; ok && record.Flags == flags.(string) {
			matchCounter++
		}

		if service, ok := options["service"]; ok && record.Service == service.(string) {
			matchCounter++
		}

		if regexp, ok := options["regexp"]; ok && record.Regexp == regexp.(string) {
			matchCounter++
		}

		if replacement, ok := options["replacement"]; ok && record.Replacement == replacement.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findNsRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Ns {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if target, ok := options["target"]; ok && record.Target == target.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findNsec3Record(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Nsec3 {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if algorithm, ok := options["algorithm"]; ok && record.Algorithm == algorithm.(int) {
			matchCounter++
		}

		if flags, ok := options["flags"]; ok && record.Flags == flags.(int) {
			matchCounter++
		}

		if iterations, ok := options["iterations"]; ok && record.Iterations == iterations.(int) {
			matchCounter++
		}

		if salt, ok := options["salt"]; ok && record.Salt == salt.(string) {
			matchCounter++
		}

		if nextHashedOwnerName, ok := options["nextHashedOwnerName"]; ok && record.NextHashedOwnerName == nextHashedOwnerName.(string) {
			matchCounter++
		}

		if typeBitmaps, ok := options["typeBitmaps"]; ok && record.TypeBitmaps == typeBitmaps.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findNsec3paramRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Nsec3param {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if algorithm, ok := options["algorithm"]; ok && record.Algorithm == algorithm.(int) {
			matchCounter++
		}

		if flags, ok := options["flags"]; ok && record.Flags == flags.(int) {
			matchCounter++
		}

		if iterations, ok := options["iterations"]; ok && record.Iterations == iterations.(int) {
			matchCounter++
		}

		if salt, ok := options["salt"]; ok && record.Salt == salt.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findPtrRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Ptr {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if target, ok := options["target"]; ok && record.Target == target.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findRpRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Rp {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if mailbox, ok := options["mailbox"]; ok && record.Mailbox == mailbox.(string) {
			matchCounter++
		}

		if txt, ok := options["txt"]; ok && record.Txt == txt.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findRrsigRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Rrsig {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if typeCovered, ok := options["typeCovered"]; ok && record.TypeCovered == typeCovered.(string) {
			matchCounter++
		}

		if algorithm, ok := options["algorithm"]; ok && record.Algorithm == algorithm.(int) {
			matchCounter++
		}

		if originalTTL, ok := options["originalTTL"]; ok && record.OriginalTTL == originalTTL.(int) {
			matchCounter++
		}

		if expiration, ok := options["expiration"]; ok && record.Expiration == expiration.(string) {
			matchCounter++
		}

		if inception, ok := options["inception"]; ok && record.Inception == inception.(string) {
			matchCounter++
		}

		if keytag, ok := options["keytag"]; ok && record.Keytag == keytag.(int) {
			matchCounter++
		}

		if signer, ok := options["signer"]; ok && record.Signer == signer.(string) {
			matchCounter++
		}

		if signature, ok := options["signature"]; ok && record.Signature == signature.(string) {
			matchCounter++
		}

		if labels, ok := options["labels"]; ok && record.Labels == labels.(int) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findSpfRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Spf {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if target, ok := options["target"]; ok && record.Target == target.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findSrvRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Srv {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if target, ok := options["target"]; ok && record.Target == target.(string) {
			matchCounter++
		}

		if priority, ok := options["priority"]; ok && record.Priority == priority.(int) {
			matchCounter++
		}

		if weight, ok := options["weight"]; ok && record.Weight == weight.(uint16) {
			matchCounter++
		}

		if port, ok := options["port"]; ok && record.Port == port.(uint16) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findSshfpRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Sshfp {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if algorithm, ok := options["algorithm"]; ok && record.Algorithm == algorithm.(int) {
			matchCounter++
		}

		if fingerprintType, ok := options["fingerprintType"]; ok && record.FingerprintType == fingerprintType.(int) {
			matchCounter++
		}

		if fingerprint, ok := options["fingerprint"]; ok && record.Fingerprint == fingerprint.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}

func (zone *Zone) findTxtRecord(options map[string]interface{}) []DNSRecord {
	found := make([]DNSRecord, 0)
	matchesNeeded := len(options)
	for _, record := range zone.Zone.Txt {
		matchCounter := 0
		if name, ok := options["name"]; ok && record.Name == name.(string) {
			matchCounter++
		}

		if active, ok := options["active"]; ok && record.Active == active.(bool) {
			matchCounter++
		}

		if ttl, ok := options["ttl"]; ok && record.TTL == ttl.(int) {
			matchCounter++
		}

		if target, ok := options["target"]; ok && record.Target == target.(string) {
			matchCounter++
		}

		if matchCounter >= matchesNeeded {
			found = append(found, record)
		}
	}
	return found
}
