// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package network

import (
	"fmt"
	"net"
	"sort"
	"strconv"
	"strings"
)

type InterfaceGenerator interface {
	Name() string
	Filename() string
	Netdev() string
	Link() string
	Network() string
	Type() string
	ModprobeParams() string
}

type networkInterface interface {
	InterfaceGenerator
	Children() []networkInterface
	setConfigDepth(int)
}

type logicalInterface struct {
	name        string
	hwaddr      net.HardwareAddr
	config      configMethod
	children    []networkInterface
	configDepth int
}

func (i *logicalInterface) Name() string {
	return i.name
}

func (i *logicalInterface) Network() string {
	config := fmt.Sprintln("[Match]")
	if i.name != "" {
		config += fmt.Sprintf("Name=%s\n", i.name)
	}
	if i.hwaddr != nil {
		config += fmt.Sprintf("MACAddress=%s\n", i.hwaddr)
	}
	config += "\n[Network]\n"

	for _, child := range i.children {
		switch iface := child.(type) {
		case *vlanInterface:
			config += fmt.Sprintf("VLAN=%s\n", iface.name)
		case *bondInterface:
			config += fmt.Sprintf("Bond=%s\n", iface.name)
		}
	}

	switch conf := i.config.(type) {
	case configMethodStatic:
		if len(conf.domains) > 0 {
			config += fmt.Sprintf("Domains=%s\n", strings.Join(conf.domains, " "))
		}
		for _, nameserver := range conf.nameservers {
			config += fmt.Sprintf("DNS=%s\n", nameserver)
		}
		for _, addr := range conf.addresses {
			config += fmt.Sprintf("\n[Address]\nAddress=%s\n", addr.String())
		}
		for _, route := range conf.routes {
			config += fmt.Sprintf("\n[Route]\nDestination=%s\nGateway=%s\n", route.destination.String(), route.gateway)
		}
	case configMethodDHCP:
		config += "DHCP=true\n"
	}

	return config
}

func (i *logicalInterface) Link() string {
	return ""
}

func (i *logicalInterface) Netdev() string {
	return ""
}

func (i *logicalInterface) Filename() string {
	name := i.name
	if name == "" {
		name = i.hwaddr.String()
	}
	return fmt.Sprintf("%02x-%s", i.configDepth, name)
}

func (i *logicalInterface) Children() []networkInterface {
	return i.children
}

func (i *logicalInterface) ModprobeParams() string {
	return ""
}

func (i *logicalInterface) setConfigDepth(depth int) {
	i.configDepth = depth
}

type physicalInterface struct {
	logicalInterface
}

func (p *physicalInterface) Type() string {
	return "physical"
}

type bondInterface struct {
	logicalInterface
	slaves  []string
	options map[string]string
}

func (b *bondInterface) Netdev() string {
	config := fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
	if b.hwaddr != nil {
		config += fmt.Sprintf("MACAddress=%s\n", b.hwaddr.String())
	}

	config += fmt.Sprintf("\n[Bond]\n")
	for _, name := range sortedKeys(b.options) {
		config += fmt.Sprintf("%s=%s\n", name, b.options[name])
	}

	return config
}

func (b *bondInterface) Type() string {
	return "bond"
}

func (b *bondInterface) ModprobeParams() string {
	params := ""
	for _, name := range sortedKeys(b.options) {
		params += fmt.Sprintf("%s=%s ", name, b.options[name])
	}
	params = strings.TrimSuffix(params, " ")
	return params
}

type vlanInterface struct {
	logicalInterface
	id        int
	rawDevice string
}

func (v *vlanInterface) Netdev() string {
	config := fmt.Sprintf("[NetDev]\nKind=vlan\nName=%s\n", v.name)
	switch c := v.config.(type) {
	case configMethodStatic:
		if c.hwaddress != nil {
			config += fmt.Sprintf("MACAddress=%s\n", c.hwaddress)
		}
	case configMethodDHCP:
		if c.hwaddress != nil {
			config += fmt.Sprintf("MACAddress=%s\n", c.hwaddress)
		}
	}
	config += fmt.Sprintf("\n[VLAN]\nId=%d\n", v.id)
	return config
}

func (v *vlanInterface) Type() string {
	return "vlan"
}

func buildInterfaces(stanzas []*stanzaInterface) []InterfaceGenerator {
	interfaceMap := createInterfaces(stanzas)
	linkAncestors(interfaceMap)
	markConfigDepths(interfaceMap)

	interfaces := make([]InterfaceGenerator, 0, len(interfaceMap))
	for _, name := range sortedInterfaces(interfaceMap) {
		interfaces = append(interfaces, interfaceMap[name])
	}

	return interfaces
}

func createInterfaces(stanzas []*stanzaInterface) map[string]networkInterface {
	interfaceMap := make(map[string]networkInterface)
	for _, iface := range stanzas {
		switch iface.kind {
		case interfaceBond:
			bondOptions := make(map[string]string)
			for _, k := range []string{"mode", "miimon", "lacp-rate"} {
				if v, ok := iface.options["bond-"+k]; ok && len(v) > 0 {
					bondOptions[k] = v[0]
				}
			}
			interfaceMap[iface.name] = &bondInterface{
				logicalInterface{
					name:     iface.name,
					config:   iface.configMethod,
					children: []networkInterface{},
				},
				iface.options["bond-slaves"],
				bondOptions,
			}
			for _, slave := range iface.options["bond-slaves"] {
				if _, ok := interfaceMap[slave]; !ok {
					interfaceMap[slave] = &physicalInterface{
						logicalInterface{
							name:     slave,
							config:   configMethodManual{},
							children: []networkInterface{},
						},
					}
				}
			}

		case interfacePhysical:
			if _, ok := iface.configMethod.(configMethodLoopback); ok {
				continue
			}
			interfaceMap[iface.name] = &physicalInterface{
				logicalInterface{
					name:     iface.name,
					config:   iface.configMethod,
					children: []networkInterface{},
				},
			}

		case interfaceVLAN:
			var rawDevice string
			id, _ := strconv.Atoi(iface.options["id"][0])
			if device := iface.options["raw_device"]; len(device) == 1 {
				rawDevice = device[0]
				if _, ok := interfaceMap[rawDevice]; !ok {
					interfaceMap[rawDevice] = &physicalInterface{
						logicalInterface{
							name:     rawDevice,
							config:   configMethodManual{},
							children: []networkInterface{},
						},
					}
				}
			}
			interfaceMap[iface.name] = &vlanInterface{
				logicalInterface{
					name:     iface.name,
					config:   iface.configMethod,
					children: []networkInterface{},
				},
				id,
				rawDevice,
			}
		}
	}
	return interfaceMap
}

func linkAncestors(interfaceMap map[string]networkInterface) {
	for _, name := range sortedInterfaces(interfaceMap) {
		iface := interfaceMap[name]
		switch i := iface.(type) {
		case *vlanInterface:
			if parent, ok := interfaceMap[i.rawDevice]; ok {
				switch p := parent.(type) {
				case *physicalInterface:
					p.children = append(p.children, iface)
				case *bondInterface:
					p.children = append(p.children, iface)
				}
			}
		case *bondInterface:
			for _, slave := range i.slaves {
				if parent, ok := interfaceMap[slave]; ok {
					switch p := parent.(type) {
					case *physicalInterface:
						p.children = append(p.children, iface)
					case *bondInterface:
						p.children = append(p.children, iface)
					}
				}
			}
		}
	}
}

func markConfigDepths(interfaceMap map[string]networkInterface) {
	rootInterfaceMap := make(map[string]networkInterface)
	for k, v := range interfaceMap {
		rootInterfaceMap[k] = v
	}

	for _, iface := range interfaceMap {
		for _, child := range iface.Children() {
			delete(rootInterfaceMap, child.Name())
		}
	}
	for _, iface := range rootInterfaceMap {
		setDepth(iface)
	}
}

func setDepth(iface networkInterface) int {
	maxDepth := 0
	for _, child := range iface.Children() {
		if depth := setDepth(child); depth > maxDepth {
			maxDepth = depth
		}
	}
	iface.setConfigDepth(maxDepth)
	return (maxDepth + 1)
}

func sortedKeys(m map[string]string) (keys []string) {
	for key := range m {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return
}

func sortedInterfaces(m map[string]networkInterface) (keys []string) {
	for key := range m {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	return
}
