package main

import (
	"io"
	"os"
)

type Generator interface {
	GenerateNamespace(writer io.Writer, namespace *Namespace) error
	GenerateCommunicationRule(writer io.Writer, app *Application, ingress *Ingress, egress *Egress) error
}

type ApplicationPeer struct {
	Application *Application
	Ports       []Port
	Rule        string
}

type NetworkPeer struct {
	Network *Network
	Ports   []Port
	Rule    string
}

type Peer struct {
	Applications []*ApplicationPeer
	Networks     []*NetworkPeer
	Predefined   []string
}

func (p *Peer) append(app *ApplicationPeer, network *NetworkPeer, predefined string) {
	if app != nil {
		p.Applications = append(p.Applications, app)
	}
	if network != nil {
		p.Networks = append(p.Networks, network)
	}
	if predefined != "" {
		p.Predefined = append(p.Predefined, predefined)
	}
}

func (p *Peer) Empty() bool {
	return len(p.Applications)+len(p.Networks)+len(p.Predefined) == 0
}

func (p Peer) String() string {
	res := ""
	for _, app := range p.Applications {
		res += "app:" + app.Application.Name + " "
	}
	for _, net := range p.Networks {
		res += "net:" + net.Network.Name + " "
	}
	for _, pre := range p.Predefined {
		res += "pre:" + pre + " "
	}
	return res
}

type Ingress struct {
	Peer
}
type Egress struct {
	Peer
}

func Generate(writer io.Writer, generator Generator, config *Config) error {
	for _, ns := range config.Namespaces {
		err := generator.GenerateNamespace(os.Stdout, ns)
		if err != nil {
			return err
		}
	}

	// Loop over all applications and gather the ingress and egress for each application
	var ingresses = make(map[string]*Ingress)
	var egresses = make(map[string]*Egress)
	for _, ns := range config.Namespaces {
		for _, app := range ns.Applications {
			if ingresses[app.Name] == nil {
				ingresses[app.Name] = &Ingress{}
			}
			if egresses[app.Name] == nil {
				egresses[app.Name] = &Egress{}
			}
		}
	}

	for _, communication := range config.Communications {
		for _, from := range communication.From {
			appFrom, networkFrom, predefinedFrom := config.GetApplication(from)
			for _, to := range communication.To {
				appTo, networkTo, predefinedTo := config.GetApplication(to)
				if appFrom != nil {
					// we have an egress from appFrom
					egress := egresses[from]
					ports := communication.Ports
					var appPeer *ApplicationPeer = nil
					var networkPeer *NetworkPeer = nil
					if appTo != nil {
						if len(ports) == 0 {
							ports = appTo.Ports
						}
						appPeer = &ApplicationPeer{
							Application: appTo,
							Ports:       ports,
							Rule:        "to",
						}
					} else if networkTo != nil {
						if len(ports) == 0 {
							ports = networkTo.Ports
						}
						networkPeer = &NetworkPeer{
							Network: networkTo,
							Ports:   ports,
							Rule:    "to",
						}
					}

					egress.append(appPeer, networkPeer, predefinedTo)
				}
				if appTo != nil {
					// we have an ingress on appTo
					ingress := ingresses[to]
					ports := communication.Ports
					var appPeer *ApplicationPeer = nil
					var networkPeer *NetworkPeer = nil
					if appFrom != nil {
						if len(ports) == 0 {
							ports = appTo.Ports
						}
						appPeer = &ApplicationPeer{
							Application: appFrom,
							Ports:       ports,
							Rule:        "from",
						}
					} else if networkFrom != nil {
						if len(ports) == 0 {
							ports = appTo.Ports
						}
						networkPeer = &NetworkPeer{
							Network: networkFrom,
							Ports:   ports,
							Rule:    "from",
						}
					}

					ingress.append(appPeer, networkPeer, predefinedFrom)
				}
			}
		}
	}

	// loop over all apps and configure them
	for _, ns := range config.Namespaces {
		for _, app := range ns.Applications {
			ingress := ingresses[app.Name]
			egress := egresses[app.Name]
			if !ingress.Empty() || !egress.Empty() {
				err := generator.GenerateCommunicationRule(writer, app, ingress, egress)
				if err != nil {
					return err
				}
			}
		}
	}

	return nil
}