package asaModels

import (
	"regexp"
	"strconv"
	"strings"

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

var (
	accessVlanRE   = regexp.MustCompile(`(?i)switchport[ ]+access[ ]+vlan[ ]+([0-9]+)`)
	duplexRE       = regexp.MustCompile(`(?i)duplex\s+(\S+)`)
	intConfRE      = regexp.MustCompile(`(?i)interface[ ]+(vlan.*?|fast.*?|ether.*?|port-channel.*?|ten.*?|gig.*?|man.*?)\n((.|\n)*?)(!|$)`)
	intModeRE      = regexp.MustCompile(`(?i)switchport[ ]+mode[ ]+(\S+)`)
	intSplitRE     = regexp.MustCompile(`(.*?)\.([0-9]+)`)
	ipAddr         = regexp.MustCompile(`(?i)ip\s+address\s+(\S+)\s+(\S+)`)
	ipAddrFoRE     = regexp.MustCompile(`(?i)ip\s+address\s+(\S+)\s+(\S+)\s+standby\s+(\S+)`)
	nameIfRE       = regexp.MustCompile(`(?i)nameif[ ]+(\S+)`)
	secLevelRE     = regexp.MustCompile(`(?i)security-level\s+([0-9]+)`)
	shutdownRE     = regexp.MustCompile(`(?i)(shutdown)`)
	speedRE        = regexp.MustCompile(`(?i)speed[ ]+([0-9]+)`)
	trunkAllowedRE = regexp.MustCompile(`(?i)switchport\s+trunk\s+allowed\s+vlan\s+(\S+)`)
	vlanIntRE      = regexp.MustCompile(`(?i)vlan([0-9]+)`)
	vlanRE         = regexp.MustCompile(`vlan\s+([0-9]+)`)

	// 6-21-2018 - This regex var doesn't appear to be used - David Patino
	//intNumRE      = regexp.MustCompile(`(.*)\.([0-9]+)`)
)

type (
	IntConfChannelGroup struct {
		Num  int    `json:"number"`
		Mode string `json:"mode"`
	}

	IntConfPortChannel struct {
		MinBundle int `json:"min_bundle"`
	}

	IntConfLACP struct {
		MaxBundle int `json:"max_bundle"`
	}

	InterfaceConfig struct {
		AccessVlan   *int                 `json:"access_vlan"`
		Description  *string              `json:"description"`
		Duplex       *string              `json:"duplex"`
		IPAddress    *string              `json:"ip_address"`
		Mode         *string              `json:"switchport_mode"`
		ChannelGroup *IntConfChannelGroup `json:"channel_group"`
		Name         string               `json:"interface"`
		Nameif       *string              `json:"nameif"`
		Netmask      *string              `json:"netmask"`
		PortChannel  *IntConfPortChannel  `json:"port_channel,omitempty"`
		LACP         *IntConfLACP         `json:"lacp,omitempty"`
		SecLevel     *int                 `json:"security_level"`
		Shutdown     bool                 `json:"shutdown"`
		Speed        *int                 `json:"speed"`
		Standby      *string              `json:"standby"`
		SubInt       []*SubIntConfig      `json:"sub-interface"`
		TrunkAllowed []int                `json:"trunk_allowed"`
	}

	InterfacesConfig struct {
		Interfaces []*InterfaceConfig `json:"interfaces_configuration"`
		ifmap      map[string]*InterfaceConfig
	}

	SubIntConfig struct {
		IPAddress *string `json:"ip_address"`
		Name      string  `json:"name"`
		Nameif    *string `json:"nameif"`
		Netmask   *string `json:"mask"`
		Number    int     `json:"number"`
		SecLevel  *int    `json:"security_level"`
		Standby   *string `json:"standby"`
		Vlan      *int    `json:"vlan"`
	}
)

func PareInterfaceConfig(s string) *InterfacesConfig {
	i := new(InterfacesConfig)
	i.Parse(s)
	return i
}

func (i *InterfaceConfig) FindSubInterface(si int) *SubIntConfig {
	for _, s := range i.SubInt {
		if s.Number == si {
			return s
		}
	}
	return nil
}

func (i *InterfaceConfig) IsVlan() bool {
	if vlanIntRE.MatchString(i.Name) {
		return true
	}
	return false
}

func (i *InterfaceConfig) Vlan() *int {
	m := vlanIntRE.FindStringSubmatch(i.Name)
	if len(m) != 0 {
		n, _ := strconv.Atoi(m[1])
		return &n
	}
	return nil
}

func (i *InterfaceConfig) VlanInUse(vlan int) bool {
	if i.Mode == nil {
		if vlan == 1 && i.AccessVlan == nil {
			return true
		}
		if i.AccessVlan != nil {
			if *i.AccessVlan == vlan {
				return true
			}
		}
		return false
	}
	if *i.Mode == "trunk" {
		for _, v := range i.TrunkAllowed {
			if v == vlan {
				return true
			}
		}
	}
	return false
}

func (i *InterfaceConfig) VlanNumber() int {
	m := vlanIntRE.FindStringSubmatch(i.Name)
	if len(m) != 0 {
		n, _ := strconv.Atoi(m[1])
		return n
	}
	return 0
}

func (i *InterfacesConfig) Parse(s string) {
	i.ifmap = make(map[string]*InterfaceConfig)
	var order []string
	for _, v := range intConfRE.FindAllStringSubmatch(s, -1) {
		port := intSplitRE.FindStringSubmatch(v[1])
		ip, mask, fo := ipAddress(&v[2])
		if len(port) == 0 {
			order = append(order, v[1])
			i.ifmap[v[1]] = &InterfaceConfig{
				AccessVlan:   util.RegexpAtoiPtr(accessVlanRE, v[2]),
				Description:  util.RegexpFindStringPtr(DESCRE, v[2]),
				Duplex:       util.RegexpFindStringPtr(duplexRE, v[2]),
				IPAddress:    ip,
				Mode:         util.RegexpFindStringPtr(intModeRE, v[2]),
				Name:         v[1],
				Nameif:       util.RegexpFindStringPtr(nameIfRE, v[2]),
				Netmask:      mask,
				SecLevel:     util.RegexpAtoiPtr(secLevelRE, v[2]),
				Shutdown:     shutdownRE.MatchString(v[2]),
				Speed:        util.RegexpAtoiPtr(speedRE, v[2]),
				Standby:      fo,
				TrunkAllowed: VlanStringToInt(util.RegexpString(trunkAllowedRE, v[2])),
			}

			match := regexp.MustCompile(`channel-group (\d+) mode (\S+)`).FindStringSubmatch(v[2])
			if len(match) > 0 {
				in, err := strconv.Atoi(match[1])
				if err == nil {
					o := IntConfChannelGroup{
						Num:  in,
						Mode: match[2],
					}
					i.ifmap[v[1]].ChannelGroup = &o
				}
			}

			match = regexp.MustCompile(`port-channel min-bundle (\d+)`).FindStringSubmatch(v[2])
			if len(match) > 0 {
				in, err := strconv.Atoi(match[1])
				if err == nil {
					o := IntConfPortChannel{
						MinBundle: in,
					}
					i.ifmap[v[1]].PortChannel = &o
				}
			}

			match = regexp.MustCompile(`lacp max-bundle (\d+)`).FindStringSubmatch(v[2])
			if len(match) > 0 {
				in, err := strconv.Atoi(match[1])
				if err == nil {
					o := IntConfLACP{
						MaxBundle: in,
					}
					i.ifmap[v[1]].LACP = &o
				}
			}
			continue
		}
		if _, ok := i.ifmap[port[1]]; !ok {
			order = append(order, port[1])
			i.ifmap[port[1]] = &InterfaceConfig{
				Name: port[1],
			}
		}
		i.ifmap[port[1]].SubInt = append(i.ifmap[port[1]].SubInt, &SubIntConfig{
			Name:      v[1],
			Number:    util.Atoi(port[2]),
			Vlan:      util.RegexpAtoiPtr(vlanRE, v[2]),
			Nameif:    util.RegexpFindStringPtr(nameIfRE, v[2]),
			SecLevel:  util.RegexpAtoiPtr(secLevelRE, v[2]),
			IPAddress: ip,
			Netmask:   mask,
			Standby:   fo,
		})

	}
	for _, p := range order {
		i.Interfaces = append(i.Interfaces, i.ifmap[p])
	}

}

func FindInterfaceConfig(name string, il []*InterfaceConfig) *InterfaceConfig {
	name = strings.ToLower(name)
	for _, i := range il {
		if strings.ToLower(i.Name) == name {
			return i
		}
	}
	return nil
}

func ipAddress(c *string) (*string, *string, *string) {
	r := ipAddrFoRE.FindStringSubmatch(*c)
	if len(r) == 4 {
		return &r[1], &r[2], &r[3]
	}
	r = ipAddr.FindStringSubmatch(*c)
	if len(r) == 3 {
		return &r[1], &r[2], nil
	}
	return nil, nil, nil
}

func SecLevelFind(sec int, I []*InterfaceConfig) (m []string) {
	for _, i := range I {
		if i.SecLevel != nil {
			if *i.SecLevel == sec {
				if i.Nameif != nil {
					m = append(m, *i.Nameif)
				}
			}
		}
		for _, si := range i.SubInt {
			if si.SecLevel != nil {
				if si.SecLevel != nil {
					if *si.SecLevel == sec {
						if si.Nameif != nil {
							m = append(m, *si.Nameif)
						}
					}
				}
			}
		}
	}
	return
}

func VlanActiveInterfaces(vlan int, ic []*InterfaceConfig) (il []*InterfaceConfig) {
	for _, i := range ic {
		if i.IsVlan() {
			continue
		}
		if i.Shutdown == true {
			continue
		}
		if i.VlanInUse(vlan) {
			il = append(il, i)
		}
	}
	return
}

func VlanStringToInt(vlans string) (vl []int) {
	if vlans == "" {
		return
	}
	for _, v := range strings.Split(vlans, ",") {
		if n, err := strconv.Atoi(v); err == nil {
			vl = append(vl, n)
			continue
		}
		r := strings.Split(v, "-")
		if len(r) != 2 {
			continue
		}
		s, err := strconv.Atoi(r[0])
		if err != nil {
			continue
		}
		e, err := strconv.Atoi(r[1])
		if err != nil {
			continue
		}
		for x := s; x <= e; x++ {
			vl = append(vl, x)
		}
	}
	return
}

func VlanTrunkInterfaces(vlan int, ic []*InterfaceConfig) (il []*InterfaceConfig) {
	for _, i := range ic {
		if i.IsVlan() {
			continue
		}
		if i.VlanInUse(vlan) {
			il = append(il, i)
		}
	}
	return
}
