//
// Copyright 2015 flannel authors
// 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 flannel

import (
	"fmt"
	"net"
	"os"
	"path/filepath"
	"sync"

	"github.com/coreos/flannel/backend"
	"github.com/coreos/flannel/network"
	"github.com/coreos/flannel/pkg/ip"
	"github.com/coreos/flannel/subnet/kube"
	"golang.org/x/net/context"
	log "k8s.io/klog"

	// Backends need to be imported for their init() to get executed and them to register
	_ "github.com/coreos/flannel/backend/vxlan"
)

const (
	subnetFile = "/run/flannel/subnet.env"
)

func flannel(ctx context.Context, flannelIface *net.Interface, flannelConf, kubeConfigFile string) error {
	extIface, err := LookupExtIface(flannelIface)
	if err != nil {
		return err
	}

	sm, err := kube.NewSubnetManager("", flannelConf, kubeConfigFile, "flannel.alpha.coreos.com")
	if err != nil {
		return err
	}

	config, err := sm.GetNetworkConfig(ctx)
	if err != nil {
		return err
	}

	// Create a backend manager then use it to create the backend and register the network with it.
	bm := backend.NewManager(ctx, sm, extIface)

	be, err := bm.GetBackend(config.BackendType)
	if err != nil {
		return err
	}

	bn, err := be.RegisterNetwork(ctx, sync.WaitGroup{}, config)
	if err != nil {
		return err
	}

	go network.SetupAndEnsureIPTables(network.MasqRules(config.Network, bn.Lease()), 60)
	go network.SetupAndEnsureIPTables(network.ForwardRules(config.Network.String()), 50)

	if err := WriteSubnetFile(subnetFile, config.Network, true, bn); err != nil {
		// Continue, even though it failed.
		log.Warningf("Failed to write subnet file: %s", err)
	} else {
		log.Infof("Wrote subnet file to %s", subnetFile)
	}

	// Start "Running" the backend network. This will block until the context is done so run in another goroutine.
	log.Info("Running backend.")
	bn.Run(ctx)
	return nil
}

func LookupExtIface(iface *net.Interface) (*backend.ExternalInterface, error) {
	var ifaceAddr net.IP
	var err error

	if iface == nil {
		log.Info("Determining IP address of default interface")
		if iface, err = ip.GetDefaultGatewayIface(); err != nil {
			return nil, fmt.Errorf("failed to get default interface: %s", err)
		}
	} else {
		log.Info("Determining IP address of specified interface: ", iface.Name)
	}

	ifaceAddr, err = ip.GetIfaceIP4Addr(iface)
	if err != nil {
		return nil, fmt.Errorf("failed to find IPv4 address for interface %s", iface.Name)
	}

	log.Infof("Using interface with name %s and address %s", iface.Name, ifaceAddr)

	if iface.MTU == 0 {
		return nil, fmt.Errorf("failed to determine MTU for %s interface", ifaceAddr)
	}

	return &backend.ExternalInterface{
		Iface:     iface,
		IfaceAddr: ifaceAddr,
		ExtAddr:   ifaceAddr,
	}, nil
}

func WriteSubnetFile(path string, nw ip.IP4Net, ipMasq bool, bn backend.Network) error {
	dir, name := filepath.Split(path)
	os.MkdirAll(dir, 0755)

	tempFile := filepath.Join(dir, "."+name)
	f, err := os.Create(tempFile)
	if err != nil {
		return err
	}

	// Write out the first usable IP by incrementing
	// sn.IP by one
	sn := bn.Lease().Subnet
	sn.IP++

	fmt.Fprintf(f, "FLANNEL_NETWORK=%s\n", nw)
	fmt.Fprintf(f, "FLANNEL_SUBNET=%s\n", sn)
	fmt.Fprintf(f, "FLANNEL_MTU=%d\n", bn.MTU())
	_, err = fmt.Fprintf(f, "FLANNEL_IPMASQ=%v\n", ipMasq)
	f.Close()
	if err != nil {
		return err
	}

	// rename(2) the temporary file to the desired location so that it becomes
	// atomically visible with the contents
	return os.Rename(tempFile, path)
	//TODO - is this safe? What if it's not on the same FS?
}
