package yaml

import (
	"fmt"

	"github.com/drone/drone/yaml/types"
	"gopkg.in/yaml.v2"
)

// Auth defines Docker authentication credentials.
type Auth struct {
	Username string
	Password string
	Email    string
}

// Container defines a Docker container.
type Container struct {
	ID             string
	Name           string
	Image          string
	Build          string
	Pull           bool
	AuthConfig     Auth
	Detached       bool
	Disabled       bool
	Privileged     bool
	WorkingDir     string
	Environment    map[string]string
	Entrypoint     []string
	Command        []string
	Commands       []string
	ExtraHosts     []string
	Volumes        []string
	VolumesFrom    []string
	Devices        []string
	Network        string
	DNS            []string
	DNSSearch      []string
	MemSwapLimit   int64
	MemLimit       int64
	CPUQuota       int64
	CPUShares      int64
	CPUSet         string
	OomKillDisable bool
	Constraints    Constraints

	Vargs map[string]interface{}
}

// container is an intermediate type used for decoding a container in a format
// compatible with docker-compose.yml.

// this file has a bunch of custom types that are annoying to work with, which
// is why this is used for intermediate purposes and converted to something
// easier to work with.
type container struct {
	Name           string              `yaml:"name"`
	Image          string              `yaml:"image"`
	Build          string              `yaml:"build"`
	Pull           bool                `yaml:"pull"`
	Privileged     bool                `yaml:"privileged"`
	Environment    types.MapEqualSlice `yaml:"environment"`
	Entrypoint     types.StringOrSlice `yaml:"entrypoint"`
	Command        types.StringOrSlice `yaml:"command"`
	Commands       types.StringOrSlice `yaml:"commands"`
	ExtraHosts     types.StringOrSlice `yaml:"extra_hosts"`
	Volumes        types.StringOrSlice `yaml:"volumes"`
	VolumesFrom    types.StringOrSlice `yaml:"volumes_from"`
	Devices        types.StringOrSlice `yaml:"devices"`
	Network        string              `yaml:"network_mode"`
	DNS            types.StringOrSlice `yaml:"dns"`
	DNSSearch      types.StringOrSlice `yaml:"dns_search"`
	MemSwapLimit   int64               `yaml:"memswap_limit"`
	MemLimit       int64               `yaml:"mem_limit"`
	CPUQuota       int64               `yaml:"cpu_quota"`
	CPUShares      int64               `yaml:"cpu_shares"`
	CPUSet         string              `yaml:"cpuset"`
	OomKillDisable bool                `yaml:"oom_kill_disable"`

	AuthConfig struct {
		Username string `yaml:"username"`
		Password string `yaml:"password"`
		Email    string `yaml:"email"`
		Token    string `yaml:"registry_token"`
	} `yaml:"auth_config"`

	Constraints Constraints `yaml:"when"`

	Vargs map[string]interface{} `yaml:",inline"`
}

// containerList is an intermediate type used for decoding a slice of containers
// in a format compatible with docker-compose.yml
type containerList struct {
	containers []*Container
}

// UnmarshalYAML implements custom Yaml unmarshaling.
func (c *containerList) UnmarshalYAML(unmarshal func(interface{}) error) error {
	slice := yaml.MapSlice{}
	err := unmarshal(&slice)
	if err != nil {
		return err
	}

	for _, s := range slice {
		cc := container{}

		out, merr := yaml.Marshal(s.Value)
		if err != nil {
			return merr
		}

		err = yaml.Unmarshal(out, &cc)
		if err != nil {
			return err
		}
		if cc.Name == "" {
			cc.Name = fmt.Sprintf("%v", s.Key)
		}
		if cc.Image == "" {
			cc.Image = fmt.Sprintf("%v", s.Key)
		}
		c.containers = append(c.containers, &Container{
			Name:           cc.Name,
			Image:          cc.Image,
			Build:          cc.Build,
			Pull:           cc.Pull,
			Privileged:     cc.Privileged,
			Environment:    cc.Environment.Map(),
			Entrypoint:     cc.Entrypoint.Slice(),
			Command:        cc.Command.Slice(),
			Commands:       cc.Commands.Slice(),
			ExtraHosts:     cc.ExtraHosts.Slice(),
			Volumes:        cc.Volumes.Slice(),
			VolumesFrom:    cc.VolumesFrom.Slice(),
			Devices:        cc.Devices.Slice(),
			Network:        cc.Network,
			DNS:            cc.DNS.Slice(),
			DNSSearch:      cc.DNSSearch.Slice(),
			MemSwapLimit:   cc.MemSwapLimit,
			MemLimit:       cc.MemLimit,
			CPUQuota:       cc.CPUQuota,
			CPUShares:      cc.CPUShares,
			CPUSet:         cc.CPUSet,
			OomKillDisable: cc.OomKillDisable,
			Vargs:          cc.Vargs,
			AuthConfig: Auth{
				Username: cc.AuthConfig.Username,
				Password: cc.AuthConfig.Password,
				Email:    cc.AuthConfig.Email,
			},
			Constraints: cc.Constraints,
		})
	}
	return err
}
