package main

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"os/signal"
	"reflect"
)

var actions Actions

func newVppContext() (context.Context, context.CancelFunc) {
	ctx, cancel := signal.NotifyContext(
		context.Background(),
		os.Interrupt,
	)
	return ctx, cancel
}

func Vppcli(runDir, command string) (string, error) {
	cmd := exec.Command("vppctl", "-s", fmt.Sprintf("%s/var/run/vpp/cli.sock", runDir), command)
	o, err := cmd.CombinedOutput()
	if err != nil {
		fmt.Printf("failed to execute command: '%v'.\n", err)
	}
	fmt.Printf("Command output %s", string(o))
	return string(o), err
}

func exitOnErrCh(ctx context.Context, cancel context.CancelFunc, errCh <-chan error) {
	// If we already have an error, log it and exit
	select {
	case err := <-errCh:
		fmt.Printf("%v", err)
	default:
	}
	go func(ctx context.Context, errCh <-chan error) {
		<-errCh
		cancel()
	}(ctx, errCh)
}

func writeSyncFile(res *ActionResult) error {
	syncFile := "/tmp/sync/rc"

	var jsonRes JsonResult

	jsonRes.ErrOutput = res.ErrOutput
	jsonRes.StdOutput = res.StdOutput
	if res.Err != nil {
		jsonRes.Code = 1
		jsonRes.Desc = fmt.Sprintf("%s :%v", res.Desc, res.Err)
	} else {
		jsonRes.Code = 0
	}

	str, err := json.Marshal(jsonRes)
	if err != nil {
		return fmt.Errorf("error marshaling json result data! %v", err)
	}

	_, err = os.Open(syncFile)
	if err != nil {
		// expecting the file does not exist
		f, e := os.Create(syncFile)
		if e != nil {
			return fmt.Errorf("failed to open sync file")
		}
		defer f.Close()
		f.Write([]byte(str))
	} else {
		return fmt.Errorf("sync file exists, delete the file first")
	}
	return nil
}

func NewActionResult(err error, opts ...ActionResultOptionFn) *ActionResult {
	res := &ActionResult{
		Err: err,
	}
	for _, o := range opts {
		o(res)
	}
	return res
}

type ActionResultOptionFn func(res *ActionResult)

func ActionResultWithDesc(s string) ActionResultOptionFn {
	return func(res *ActionResult) {
		res.Desc = s
	}
}

func ActionResultWithStderr(s string) ActionResultOptionFn {
	return func(res *ActionResult) {
		res.ErrOutput = s
	}
}

func ActionResultWithStdout(s string) ActionResultOptionFn {
	return func(res *ActionResult) {
		res.StdOutput = s
	}
}

func OkResult() *ActionResult {
	return NewActionResult(nil)
}

func processArgs() *ActionResult {
	nArgs := len(os.Args) - 1 // skip program name
	if nArgs < 1 {
		return NewActionResult(fmt.Errorf("internal: no action specified!"))
	}
	action := os.Args[1]
	methodValue := reflect.ValueOf(&actions).MethodByName(action)
	if !methodValue.IsValid() {
		return NewActionResult(fmt.Errorf("internal unknown action %s!", action))
	}
	methodIface := methodValue.Interface()
	fn := methodIface.(func([]string) *ActionResult)
	return fn(os.Args)
}

func main() {
	if len(os.Args) == 0 {
		fmt.Println("args required")
		return
	}

	if os.Args[1] == "rm" {
		topology, err := LoadTopology(TopologyDir, os.Args[2])
		if err != nil {
			fmt.Printf("falied to load topologies: %v\n", err)
			os.Exit(1)
		}
		topology.Unconfigure()
		os.Exit(0)
	}

	var err error
	res := processArgs()
	err = writeSyncFile(res)
	if err != nil {
		fmt.Printf("failed to write to sync file: %v\n", err)
	}
}