package node

import (
	"fmt"

	"github.com/docker/docker/api/client"
	"github.com/docker/docker/cli"
	runconfigopts "github.com/docker/docker/runconfig/opts"
	"github.com/docker/engine-api/types/swarm"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"golang.org/x/net/context"
)

func newUpdateCommand(dockerCli *client.DockerCli) *cobra.Command {
	var opts nodeOptions
	var flags *pflag.FlagSet

	cmd := &cobra.Command{
		Use:   "update [OPTIONS] NODE",
		Short: "Update a node",
		Args:  cli.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			return runUpdate(dockerCli, args[0], mergeNodeUpdate(flags))
		},
	}

	flags = cmd.Flags()
	flags.StringVar(&opts.role, "role", "", "Role of the node (worker/manager)")
	flags.StringVar(&opts.membership, "membership", "", "Membership of the node (accepted/rejected)")
	flags.StringVar(&opts.availability, "availability", "", "Availability of the node (active/pause/drain)")
	return cmd
}

func runUpdate(dockerCli *client.DockerCli, nodeID string, mergeNode func(node *swarm.Node)) error {
	client := dockerCli.Client()
	ctx := context.Background()

	node, err := client.NodeInspect(ctx, nodeID)
	if err != nil {
		return err
	}

	mergeNode(&node)
	err = client.NodeUpdate(ctx, nodeID, node.Version, node.Spec)
	if err != nil {
		return err
	}

	fmt.Fprintf(dockerCli.Out(), "%s\n", nodeID)
	return nil
}

func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) {
	return func(node *swarm.Node) {
		mergeString := func(flag string, field *string) {
			if flags.Changed(flag) {
				*field, _ = flags.GetString(flag)
			}
		}

		mergeRole := func(flag string, field *swarm.NodeRole) {
			if flags.Changed(flag) {
				str, _ := flags.GetString(flag)
				*field = swarm.NodeRole(str)
			}
		}

		mergeMembership := func(flag string, field *swarm.NodeMembership) {
			if flags.Changed(flag) {
				str, _ := flags.GetString(flag)
				*field = swarm.NodeMembership(str)
			}
		}

		mergeAvailability := func(flag string, field *swarm.NodeAvailability) {
			if flags.Changed(flag) {
				str, _ := flags.GetString(flag)
				*field = swarm.NodeAvailability(str)
			}
		}

		mergeLabels := func(flag string, field *map[string]string) {
			if flags.Changed(flag) {
				values, _ := flags.GetStringSlice(flag)
				for key, value := range runconfigopts.ConvertKVStringsToMap(values) {
					(*field)[key] = value
				}
			}
		}

		spec := &node.Spec
		mergeString("name", &spec.Name)
		// TODO: setting labels is not working
		mergeLabels("label", &spec.Labels)
		mergeRole("role", &spec.Role)
		mergeMembership("membership", &spec.Membership)
		mergeAvailability("availability", &spec.Availability)
	}
}