package swModels

import (
	"errors"
	"fmt"
	"regexp"
	"sort"
	"strings"

	"strconv"

	"github.rackspace.com/gorax/tools/util"
)

var (
	PORTNUMRE        = regexp.MustCompile(`.*?([0-9]+)$`)
	INTCONFRE        = regexp.MustCompile(`(?i)(\ni|)nterface\s+(.*?/[0-9]+|.*?net[0-9]+|vlan[0-9]+|port-channel[0-9]+)\n((.|\n)*?)(!\n|\ni|$)`)
	TXTSTARTRE       = regexp.MustCompile(`^\S+`)
	INTDESCRE        = regexp.MustCompile(`(?i)description\s+(.*?)(\n)`)
	DUPLEXRE         = regexp.MustCompile(`(?i)duplex\s+(\S+)`)
	BPDUGUARDRE      = regexp.MustCompile(`(?i)(spanning-tree\s+bpduguard\s+enable)`)
	STPGUARDRE       = regexp.MustCompile(`(?i)spanning-tree\s+guard\s+(\S+)`)
	SPEEDRE          = regexp.MustCompile(`(?i)speed\s+(\S+)`)
	ACCESSVLANRE     = regexp.MustCompile(`(?i)switchport\s+access\s+vlan\s+(\S+)`)
	MODERE           = regexp.MustCompile(`(?i)switchport\s+mode\s+(?:private-vlan\s+)?(\S+)`)
	PHOSTVLANRE      = regexp.MustCompile(`(?i)switchport\s+private-vlan\s+host-association\s+(\d+)`)
	PORTSECRE        = regexp.MustCompile(`(?i)switchport\s+(port-security)`)
	NATIVEVLANRE     = regexp.MustCompile(`(?i)switchport\s+(?:private-vlan\s+)?trunk\s+native\s+vlan\s+([0-9]+)`)
	PORTFASTRE       = regexp.MustCompile(`(?i)(spanning-tree\s+portfast)`)
	PORTFASTTRUNKRE  = regexp.MustCompile(`(?i)(spanning-tree\s+portfast\s+trunk)`)
	PORTFASTEDGERE   = regexp.MustCompile(`(?i)(spanning-tree\s+portfast\s+edge)`)
	PORTFASTEDGENXRE = regexp.MustCompile(`(?i)(spanning-tree\s+port\s+type\s+edge\s+trunk)`)
	NONEGOTIATERE    = regexp.MustCompile(`(?i)(switchport\s+nonegotiate)`)
	VPCRE            = regexp.MustCompile(`(?i)vpc\s+([0-9]+)`)
	MLAGRE           = regexp.MustCompile(`(?i)mlag\s+([0-9]+)`)
	CHANNELGRPRE     = regexp.MustCompile(`(?i)channel-group\s+([0-9]+)\s+mode\s+\S+`)
	CHANNELMODERE    = regexp.MustCompile(`(?i)channel-group\s+[0-9]+\s+mode\s+(\S+)`)
	PROTECTEDRE      = regexp.MustCompile(`(?i)(switchport\s+protected)`)
	VRFRE            = regexp.MustCompile(`(?i)ip\s+vrf\s+forwarding\s+(\S+)`)
	TRUNKALLOWEDRE   = regexp.MustCompile(`(?i)switchport\s+(?:private-vlan\s+)?trunk\s+allowed\s+vlan(?:\s+add)?\s+(\S+)`)
	CDPBOOLRE        = regexp.MustCompile(`(?i)(no\s+cdp\s+enable)`)
	NOSHUTRE         = regexp.MustCompile(`(?i)(no[ ]+shutdown)`)
	SHUTRE           = regexp.MustCompile(`(?i)(\n[ ]+shutdown)`)
)

type (
	InterfaceConfig struct {
		InterfaceConfig []*IntConfig `json:"interfaces_configuration"`
	}

	IntConfig struct {
		Name          string    `json:"name"`
		PortNum       int       `json:"port_num"`
		ChannelGroup  *int      `json:"channel_group"`
		ChannelMode   *string   `json:"channel_group_mode"`
		Description   *string   `json:"description"`
		Duplex        *string   `json:"duplex"`
		Bpduguard     bool      `json:"bpduguard"`
		STPGuard      *string   `json:"spanning_tree_guard"`
		Speed         *string   `json:"speed"`
		AccessVlan    *int      `json:"access_vlan"`
		Mode          *string   `json:"mode"`
		PortSec       bool      `jsoN:"port_security"`
		VlanAllowed   *[]string `json:"vlan_allowed"`
		NativeVlan    *int      `json:"native_vlan"`
		PortFast      bool      `json:"port_fast"`
		PortFastTrunk bool      `json:"port_fast_trunk"`
		CdpEnable     bool      `json:"cdp_enable"`
		Nonegotiate   bool      `json:"nonegotiate"`
		Protected     bool      `json:"switchport_protected"`
		Shutdown      bool      `json:"shutdown"`
		VPC           *int      `json:"vpc"`
		MLAG          *int      `json:"mlag"`
		Full          *string   `json:"full_config"`
		Vrf           *string   `json:"forwarding_vrf"`
		vlans         *[]int
	}

	VlanMap struct {
		New int `json:"new_vlan"`
		Old int `json:"old_vlan"`
	}
)

func ParseIntConfig(s *string) (*InterfaceConfig, error) {
	ic := new(InterfaceConfig)
	ic.Parse(s)
	if len(ic.InterfaceConfig) == 0 {
		return nil, errors.New("Unable to parse config, created 0 interface items.")
	}
	return ic, nil
}

func (I *InterfaceConfig) Parse(s *string) {
	for _, i := range INTCONFRE.FindAllStringSubmatch(*s, -1) {
		if TXTSTARTRE.MatchString(i[3]) {
			continue
		}
		if i[2] != "" && i[2] != "!Command:" && i[3] != "ip tftp source-interface FastEthernet1" {
			ic := IntConfig{
				Name: i[2],
				PortNum: func() int {
					n := util.RegexpAtoiPtr(PORTNUMRE, i[2])
					if n != nil {
						return *n
					}
					return -1
				}(),
				Description: util.RegexpFindStringPtr(INTDESCRE, i[3]),
				Duplex:      util.RegexpFindStringPtr(DUPLEXRE, i[3]),
				Bpduguard:   BPDUGUARDRE.MatchString(i[3]),
				STPGuard:    util.RegexpFindStringPtr(STPGUARDRE, i[3]),
				Speed:       util.RegexpFindStringPtr(SPEEDRE, i[3]),
				Mode:        util.RegexpFindStringPtr(MODERE, i[3]),
				AccessVlan: func(conf *string) *int {
					mode := util.RegexpFindStringPtr(MODERE, *conf)
					if mode == nil {
						return util.RegexpAtoiPtr(ACCESSVLANRE, *conf)
					}
					if *mode == "host" {
						return util.RegexpAtoiPtr(PHOSTVLANRE, *conf)
					}
					return util.RegexpAtoiPtr(ACCESSVLANRE, *conf)
				}(&i[3]),
				PortSec:    PORTSECRE.MatchString(i[3]),
				NativeVlan: util.RegexpAtoiPtr(NATIVEVLANRE, i[3]),
				PortFast:   PORTFASTRE.MatchString(i[3]),
				PortFastTrunk: func(conf *string) bool {
					if PORTFASTTRUNKRE.MatchString(*conf) {
						return true
					}
					if PORTFASTEDGERE.MatchString(*conf) {
						return true
					}
					if PORTFASTEDGENXRE.MatchString(*conf) {
						return true
					}
					return false
				}(&i[3]),
				Nonegotiate:  NONEGOTIATERE.MatchString(i[3]),
				VPC:          util.RegexpAtoiPtr(VPCRE, i[3]),
				MLAG:         util.RegexpAtoiPtr(MLAGRE, i[3]),
				ChannelGroup: util.RegexpAtoiPtr(CHANNELGRPRE, i[3]),
				ChannelMode:  util.RegexpFindStringPtr(CHANNELMODERE, i[3]),
				Protected:    PROTECTEDRE.MatchString(i[3]),
				Vrf:          util.RegexpFindStringPtr(VRFRE, i[3]),
				VlanAllowed: func(s *string) *[]string {
					var vlan []string
					vmatch := TRUNKALLOWEDRE.FindAllStringSubmatch(*s, -1)
					if len(vmatch) == 0 {
						return nil
					}

					for _, v := range vmatch {
						if v[1] == "none" {
							return &vlan
						}
						vlans := strings.Split(v[1], ",")
						for _, vl := range vlans {
							if strings.Contains(vl, "-") {
								vl2 := strings.Split(vl, "-")
								vlStart, err := strconv.Atoi(vl2[0])
								if err != nil {
									return nil
								}
								vlEnd, err := strconv.Atoi(vl2[1])
								if err != nil {
									return nil
								}
								for i := vlStart; i <= vlEnd; i++ {
									vlan = append(vlan, strconv.Itoa(i))
								}
							} else {
								vlan = append(vlan, vl)
							}
						}
					}
					return &vlan
				}(&i[3]),
				CdpEnable: !CDPBOOLRE.MatchString(i[3]),
				Shutdown: func() bool {
					if NOSHUTRE.MatchString(i[3]) {
						return false
					}
					if SHUTRE.MatchString(i[3]) {
						return true
					}
					return false
				}(),
				Full: func() *string {
					f := fmt.Sprintf("interface %s\n%s", i[2], i[3])
					return &f
				}(),
			}
			I.InterfaceConfig = append(I.InterfaceConfig, &ic)
		}
	}
}

func (I *IntConfig) MissingVlans(vlans []int) (ml []int) {
	if I == nil {
		return
	}
	exvlans := I.ExpandVlan()
	for _, v := range vlans {
		found := false
		for _, ev := range exvlans {
			if ev == v {
				found = true
			}
		}
		if !found {
			ml = append(ml, v)
		}
	}
	return
}

func listDiff(v1, v2 []int) (ml []int) {
	for _, v := range v1 {
		found := false
		for _, ev := range v2 {
			if ev == v {
				found = true
			}
		}
		if !found {
			ml = append(ml, v)
		}
	}
	return
}

func (I *IntConfig) ExpandVlan() []int {
	var vlans []int
	if I.VlanAllowed == nil {
		return vlans
	}

	for _, v := range *I.VlanAllowed {
		if strings.Contains(v, "-") {
			hiLow := strings.Split(v, "-")
			for i := util.Atoi(hiLow[0]); i <= util.Atoi(hiLow[1]); i++ {
				vlans = append(vlans, i)
			}
			continue
		}
		vlans = append(vlans, util.Atoi(v))
	}
	if len(vlans) == 0 {
		return vlans
	}
	I.vlans = &vlans
	return vlans
}

func (I *IntConfig) VlanAllowedReplace(v []VlanMap) *[]int {
	if I.VlanAllowed != nil {
		if I.vlans == nil {
			I.ExpandVlan()
		}
		if I.vlans == nil {
			return nil
		}
		var vlans []int
		for _, vlan := range *I.vlans {
			for _, vu := range v {
				if vu.Old == vlan || vu.Old == 1 {
					vlans = append(vlans, vu.New)
				}
			}
		}
		if len(vlans) == 0 {
			return nil
		}

		for _, vlan := range *I.vlans {
			if vlan == 14 {
				vlans = append(vlans, 4024)
			}
		}

		if len(vlans) != 0 {
			return &vlans
		}
	}
	return nil
}

func (I *IntConfig) VlanAccessReplace(v []VlanMap) *int {
	if I.AccessVlan != nil {
		for _, vu := range v {
			if vu.Old == *I.AccessVlan || vu.Old == 1 {
				return &vu.New
			}
		}
	}
	return nil
}

func (I *InterfaceConfig) PortConfig(portNum int) *IntConfig {
	return PortConfigByNum(portNum, I.InterfaceConfig)
}

func (I *InterfaceConfig) PoConfigByPort(portNum int) *IntConfig {
	i := PortConfigByNum(portNum, I.InterfaceConfig)
	if i != nil {
		if i.ChannelGroup != nil {
			return I.PortChannelConfig(i.ChannelGroup)
		}
	}
	return i
}

func PortConfigByNum(portNum int, i []*IntConfig) *IntConfig {
	re := regexp.MustCompile(fmt.Sprintf(".*?(%d)$", portNum))

	for _, p := range i {
		if validPort(p.Name) && portNumMatch(p.Name, re) {
			//if p.Vrf == nil && *p.Full != "" {
			return p
		}
	}
	return nil
}

func (I *InterfaceConfig) FindByDesc(match string) *IntConfig {
	return FindIntByDesc(match, I.InterfaceConfig)
}

func FindIntByDesc(match string, I []*IntConfig) *IntConfig {
	re := regexp.MustCompile(fmt.Sprintf("(?i)(%s)", match))

	for _, p := range I {
		if p.Description != nil {
			if re.MatchString(*p.Description) {
				return p
			}
		}
	}
	return nil
}

func (I *InterfaceConfig) PortByName(portName string) *IntConfig {
	portName = strings.ToLower(portName)
	for _, p := range I.InterfaceConfig {
		if strings.ToLower(p.Name) == portName {
			return p
		}
	}
	return nil
}

// method to return the port channel, if no portchannel is found it returns the port configuration.
func (I *InterfaceConfig) PoByInterfaceName(portName string) *IntConfig {
	return PoByInterfaceName(portName, I.InterfaceConfig)
}

func PoByInterfaceName(portName string, I []*IntConfig) *IntConfig {
	portName = strings.ToLower(portName)

	for _, p := range I {
		if strings.ToLower(p.Name) == portName {
			if p.ChannelGroup == nil {
				return p
			}
			return PortChannelConfByNum(p.ChannelGroup, I)

		}
	}
	return nil
}

func (I *InterfaceConfig) PortChannelConfig(po *int) *IntConfig {
	return PortChannelConfByNum(po, I.InterfaceConfig)
}

func PortChannelConfByNum(po *int, I []*IntConfig) *IntConfig {
	if po == nil {
		return nil
	}
	portName := fmt.Sprintf("port-channel%d", *po)
	for _, p := range I {
		if strings.ToLower(p.Name) == portName {
			return p
		}
	}
	return nil
}

func (I *InterfaceConfig) PoByDesc(match string) *IntConfig {
	return PoByDesc(match, I.InterfaceConfig)
}

func PoByDesc(match string, I []*IntConfig) *IntConfig {
	re := regexp.MustCompile(fmt.Sprintf("(?i)(%s)", match))

	for _, p := range I {
		if p.Description != nil && strings.Contains(strings.ToLower(p.Name), "port-channel") {
			if re.MatchString(*p.Description) {
				return p
			}
		}
	}
	return nil
}

func validPort(port string) bool {
	port = strings.ToLower(port)
	if strings.Contains(port, "tengiga") {
		return false
	}
	if port == "fastethernet1" {
		return false
	}
	if strings.Contains(port, "vlan") {
		return false
	}
	if strings.Contains(port, "port-chan") {
		return false
	}
	return true
}

func portNumMatch(portName string, re *regexp.Regexp) bool {
	if re.MatchString(portName) {
		return true
	}
	return false
}

func (src *IntConfig) PortDiff(dst *IntConfig, vx *map[int]int) (*[]string, *[]int) {
	if src.ChannelGroup != nil || dst.ChannelGroup != nil {
		return nil, nil
	}

	if src.VPC != nil || dst.VPC != nil {
		return nil, nil
	}

	var cl []string
	var vl []int
	src.DuplexCompare(dst, &cl)
	src.SpeedCompare(dst, &cl)
	src.ModeCompare(dst, &cl, &vl, vx)
	src.StateCompare(dst, &cl)
	if len(cl) == 0 {
		return nil, &vl
	}

	return &cl, &vl
}

func (src *IntConfig) StateCompare(dst *IntConfig, cl *[]string) {
	if dst.Shutdown == true {
		if src.Shutdown == false {
			*cl = append(*cl, "no shutdown")
		}
	}
}

func (src *IntConfig) DuplexCompare(dst *IntConfig, cl *[]string) {
	if src.Duplex == nil && dst.Duplex == nil {
		return
	}
	if src.Duplex == nil && dst.Duplex != nil {
		*cl = append(*cl, "no duplex")
		return
	}
	if src.Duplex != nil && dst.Duplex == nil {
		*cl = append(*cl, fmt.Sprintf("duplex %s", *src.Duplex))
		return
	}
	if *src.Duplex != *dst.Duplex {
		*cl = append(*cl, fmt.Sprintf("duplex %s", *src.Duplex))
	}

}

func (src *IntConfig) SpeedCompare(dst *IntConfig, cl *[]string) {
	if src.Speed == nil && dst.Speed == nil {
		return
	}
	if src.Speed == nil && dst.Speed != nil {
		*cl = append(*cl, "no speed")
		return
	}
	if src.Speed != nil && dst.Speed == nil {
		*cl = append(*cl, fmt.Sprintf("speed %s", *src.Speed))
		return
	}
	if *src.Speed != *dst.Speed {
		*cl = append(*cl, fmt.Sprintf("speed %s", *src.Speed))
	}

}

func (src *IntConfig) ModeCompare(dst *IntConfig, cl *[]string, vl *[]int, vx *map[int]int) {
	if src.Mode == nil && dst.Mode == nil {
		return
	} else if src.Mode == nil && dst.Mode != nil {
		return
	} else if src.Mode != nil && dst.Mode == nil {
		*cl = append(*cl, fmt.Sprintf("switchport mode %s", *src.Mode))
	} else if *src.Mode != *dst.Mode {
		*cl = append(*cl, fmt.Sprintf("switchport mode %s", *src.Mode))
	}

	switch *src.Mode {
	case "access":
		src.AccessCompare(dst, cl, vl, vx)
	case "trunk":
		src.TrunkCompare(dst, cl, vl, vx)
	}
}

func (src *IntConfig) AccessCompare(dst *IntConfig, cl *[]string, vl *[]int, vx *map[int]int) {
	if src.AccessVlan != nil {
		if dst.AccessVlan == nil {
			*cl = append(*cl, fmt.Sprintf("switchport access vlan %d", vlanXlate(src.AccessVlan, vl, vx)))
			return
		}
		if dst.AccessVlan != nil {
			vlan := vlanXlate(src.AccessVlan, vl, vx)
			if vlan != *dst.AccessVlan {
				*cl = append(*cl, fmt.Sprintf("switchport access vlan %d", vlan))
			}
		}
	}

	if src.AccessVlan == nil && dst.AccessVlan != nil {
		*cl = append(*cl, fmt.Sprintf("no switchport access vlan %d", *dst.AccessVlan))
	}
}

func (src *IntConfig) TrunkCompare(dst *IntConfig, cl *[]string, vl *[]int, vx *map[int]int) {
	if dst.AccessVlan != nil {
		*cl = append(*cl, fmt.Sprintf("no switchport access vlan %d", *dst.AccessVlan))
	}
	src.NativeCompare(dst, cl, vl, vx)
	if src.VlanAllowed != nil {
		if dst.VlanAllowed == nil {
			src.TrunkAddAll(dst, cl, vl, vx)
			return
		}
		src.TrunkRemove(dst, cl, vl, vx)
		src.TrunkAdd(dst, cl, vl, vx)

	}
}

func (src *IntConfig) TrunkAddAll(dst *IntConfig, cl *[]string, vl *[]int, vx *map[int]int) {
	for _, v := range replaceVlans(src.ExpandVlan(), vl, vx) {
		*cl = append(*cl, fmt.Sprintf("switchport trunk allowed vlan add %d", v))
	}
	return
}

func (src *IntConfig) TrunkAdd(dst *IntConfig, cl *[]string, vl *[]int, vx *map[int]int) {
	srcVlans := replaceVlans(src.ExpandVlan(), vl, vx)
	dstVlans := dst.ExpandVlan()
	for _, vlan := range listDiff(srcVlans, dstVlans) {
		*cl = append(*cl, fmt.Sprintf("switchport trunk allowed vlan add %d", vlan))
	}
}

func (src *IntConfig) TrunkRemove(dst *IntConfig, cl *[]string, vl *[]int, vx *map[int]int) {
	srcVlans := replaceVlans(dst.ExpandVlan(), vl, vx)
	dstVlans := src.ExpandVlan()
	for _, vlan := range listDiff(dstVlans, srcVlans) {
		*cl = append(*cl, fmt.Sprintf("switchport trunk allowed vlan remove %d", vlan))
	}
}

func replaceVlans(ev []int, vl *[]int, vx *map[int]int) (xvl []int) {
	for _, vlan := range ev {
		xvl = append(xvl, vlanXlate(&vlan, vl, vx))
	}
	return

}

func (src *IntConfig) NativeCompare(dst *IntConfig, cl *[]string, vl *[]int, vx *map[int]int) {
	if src.NativeVlan != nil {
		if dst.NativeVlan == nil {
			*cl = append(*cl, fmt.Sprintf("switchport trunk native vlan %d", vlanXlate(src.NativeVlan, vl, vx)))
			return
		}
		if dst.NativeVlan != nil {
			vlan := vlanXlate(src.NativeVlan, vl, vx)
			if vlan != *dst.NativeVlan {
				*cl = append(*cl, fmt.Sprintf("switchport trunk native vlan %d", vlan))
				return
			}
		}
	}
	if src.NativeVlan == nil && dst.NativeVlan != nil {
		*cl = append(*cl, fmt.Sprintf("no switchport trunk native vlan %d", *dst.NativeVlan))
	}
}

//method to retrun a list of all vlans in use on all ports
func (i *InterfaceConfig) PortVlans() (vlans []int) {
	return PortVlans(i.InterfaceConfig)
}

func PortVlans(i []*IntConfig) (vlans []int) {
	for _, p := range i {
		vlans = append(vlans, p.PortVlans()...)
	}
	vlans = util.UniqueInts(vlans)
	sort.Ints(vlans)
	return
}

func (p *IntConfig) PortVlans() (vlans []int) {
	if p.Mode == nil {
		return
	}
	if *p.Mode == "access" && p.AccessVlan != nil {
		vlans = append(vlans, *p.AccessVlan)
		return
	}
	if *p.Mode == "trunk" {
		if p.NativeVlan != nil {
			vlans = append(vlans, *p.NativeVlan)
		}
		if p.VlanAllowed != nil {
			vlans = append(vlans, p.ExpandVlan()...)
		}
	}
	return
}

func (p *IntConfig) PortVlansTypes() (native int, vlans []int) {
	if p.Mode == nil {
		return
	}
	if *p.Mode == "access" && p.AccessVlan != nil {
		vlans = append(vlans, *p.AccessVlan)
		return
	}
	if *p.Mode == "trunk" {
		if p.NativeVlan != nil {
			native = *p.NativeVlan
		}
		if p.VlanAllowed != nil {
			vlans = append(vlans, p.ExpandVlan()...)
		}
	}
	return
}

func (i *InterfaceConfig) VlansByPort(ports []int) (vlans []int) {
	return VlansByPort(ports, i.InterfaceConfig)
}

func VlansByPort(ports []int, i []*IntConfig) (vlans []int) {
	for _, port := range ports {
		p := PortConfigByNum(port, i)
		vlans = append(vlans, p.PortVlans()...)
	}
	vlans = util.UniqueInts(vlans)
	sort.Ints(vlans)
	return
}

func vlanXlate(v *int, vl *[]int, vx *map[int]int) int {
	if vlan, ok := (*vx)[0]; ok {
		*vl = append(*vl, vlan)
		return vlan
	}
	*vl = append(*vl, *v)
	if vx == nil {
		*vl = append(*vl, *v)
		return *v
	}
	if vlan, ok := (*vx)[*v]; ok {
		return vlan
	}
	return *v

}

func (p *IntConfig) String() string {
	if p.Full == nil {
		return ""
	}
	return *p.Full
}

func (i *InterfaceConfig) FindPortByVlan(vlan int) []*IntConfig {
	return FindPortByVlan(vlan, i.InterfaceConfig)
}

func FindPortByVlan(vlan int, ports []*IntConfig) (pl []*IntConfig) {
	for _, p := range ports {
		if p.VlanInPort(vlan) {
			pl = append(pl, p)
		}
	}
	return
}

func (p *IntConfig) VlanInPort(vlan int) bool {
	for _, v := range p.PortVlans() {
		if v == vlan {
			return true
		}
	}
	return false
}
