package state

import (
	"bytes"
	"github.com/mesos/mesos-go/upid"
	"net"
	"strconv"
	"strings"
)

// Resources holds resources as defined in the /state.json Mesos HTTP endpoint.
type Resources struct {
	PortRanges string `json:"ports"`
}

// Ports returns a slice of individual ports expanded from PortRanges.
func (r Resources) Ports() []string {
	if r.PortRanges == "" || r.PortRanges == "[]" {
		return []string{}
	}

	rhs := strings.Split(r.PortRanges, "[")[1]
	lhs := strings.Split(rhs, "]")[0]

	yports := []string{}

	mports := strings.Split(lhs, ",")
	for _, port := range mports {
		tmp := strings.TrimSpace(port)
		pz := strings.Split(tmp, "-")
		lo, _ := strconv.Atoi(pz[0])
		hi, _ := strconv.Atoi(pz[1])

		for t := lo; t <= hi; t++ {
			yports = append(yports, strconv.Itoa(t))
		}
	}
	return yports
}

// Label holds a label as defined in the /state.json Mesos HTTP endpoint.
type Label struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

// Status holds a task status as defined in the /state.json Mesos HTTP endpoint.
type Status struct {
	Timestamp       float64         `json:"timestamp"`
	State           string          `json:"state"`
	Labels          []Label         `json:"labels,omitempty"`
	ContainerStatus ContainerStatus `json:"container_status,omitempty"`
	Healthy         *bool           `json:"healthy"`
}

// ContainerStatus holds container metadata as defined in the /state.json
// Mesos HTTP endpoint.
type ContainerStatus struct {
	NetworkInfos []NetworkInfo `json:"network_infos,omitempty"`
}

// NetworkInfo holds the network configuration for a single interface
// as defined in the /state.json Mesos HTTP endpoint.
type NetworkInfo struct {
	IPAddresses []IPAddress `json:"ip_addresses,omitempty"`
	// back-compat with 0.25 IPAddress format
	IPAddress string `json:"ip_address,omitempty"`
}

// IPAddress holds a single IP address configured on an interface,
// as defined in the /state.json Mesos HTTP endpoint.
type IPAddress struct {
	IPAddress string `json:"ip_address,omitempty"`
}

// Task holds a task as defined in the /state.json Mesos HTTP endpoint.
type Task struct {
	FrameworkID   string   `json:"framework_id"`
	ID            string   `json:"id"`
	Name          string   `json:"name"`
	SlaveID       string   `json:"slave_id"`
	State         string   `json:"state"`
	Statuses      []Status `json:"statuses"`
	Resources     `json:"resources"`
	DiscoveryInfo DiscoveryInfo `json:"discovery"`

	SlaveIP string  `json:"-"`
	Labels  []Label `json:"labels,omitempty"`
}

// HasDiscoveryInfo return whether the DiscoveryInfo was provided in the state.json
func (t *Task) HasDiscoveryInfo() bool {
	return t.DiscoveryInfo.Name != ""
}

// IP returns the first Task IP found in the given sources.
func (t *Task) IP(srcs ...string) string {
	if ips := t.IPs(srcs...); len(ips) > 0 {
		return ips[0].String()
	}
	return ""
}

// IPs returns a slice of IPs sourced from the given sources with ascending
// priority.
func (t *Task) IPs(srcs ...string) (ips []net.IP) {
	if t == nil {
		return nil
	}
	for i := range srcs {
		if src, ok := sources[srcs[i]]; ok {
			for _, srcIP := range src(t) {
				if ip := net.ParseIP(srcIP); len(ip) > 0 {
					ips = append(ips, ip)
				}
			}
		}
	}
	return ips
}

// sources maps the string representation of IP sources to their functions.
var sources = map[string]func(*Task) []string{
	"host":    hostIPs,
	"mesos":   mesosIPs,
	"docker":  dockerIPs,
	"netinfo": networkInfoIPs,
}

// hostIPs is an IPSource which returns the IP addresses of the slave a Task
// runs on.
func hostIPs(t *Task) []string { return []string{t.SlaveIP} }

// networkInfoIPs returns IP addresses from a given Task's
// []Status.ContainerStatus.[]NetworkInfos.[]IPAddresses.IPAddress
func networkInfoIPs(t *Task) []string {
	return statusIPs(t.Statuses, func(s *Status) []string {
		ips := make([]string, len(s.ContainerStatus.NetworkInfos))
		for _, netinfo := range s.ContainerStatus.NetworkInfos {
			if len(netinfo.IPAddresses) > 0 {
				// In v0.26, we use the IPAddresses field.
				for _, ipAddress := range netinfo.IPAddresses {
					ips = append(ips, ipAddress.IPAddress)
				}
			} else {
				// Fall back to v0.25 syntax of single IPAddress if that's being used.
				if netinfo.IPAddress != "" {
					ips = append(ips, netinfo.IPAddress)
				}
			}
		}
		return ips
	})
}

const (
	// DockerIPLabel is the key of the Label which holds the Docker containerizer IP value.
	DockerIPLabel = "Docker.NetworkSettings.IPAddress"
	// MesosIPLabel is the key of the label which holds the Mesos containerizer IP value.
	MesosIPLabel = "MesosContainerizer.NetworkSettings.IPAddress"
)

// dockerIPs returns IP addresses from the values of all
// Task.[]Status.[]Labels whose keys are equal to "Docker.NetworkSettings.IPAddress".
func dockerIPs(t *Task) []string {
	return statusIPs(t.Statuses, labels(DockerIPLabel))
}

// mesosIPs returns IP addresses from the values of all
// Task.[]Status.[]Labels whose keys are equal to
// "MesosContainerizer.NetworkSettings.IPAddress".
func mesosIPs(t *Task) []string {
	return statusIPs(t.Statuses, labels(MesosIPLabel))
}

// statusIPs returns the latest running status IPs extracted with the given src
func statusIPs(st []Status, src func(*Status) []string) []string {
	// the state.json we extract from mesos makes no guarantees re: the order
	// of the task statuses so we should check the timestamps to avoid problems
	// down the line. we can't rely on seeing the same sequence. (@joris)
	// https://github.com/apache/mesos/blob/0.24.0/src/slave/slave.cpp#L5226-L5238
	ts, j := -1.0, -1
	for i := range st {
		if st[i].State == "TASK_RUNNING" && st[i].Timestamp > ts {
			ts, j = st[i].Timestamp, i
		}
	}
	if j >= 0 {
		return src(&st[j])
	}
	return nil
}

// labels returns all given Status.[]Labels' values whose keys are equal
// to the given key
func labels(key string) func(*Status) []string {
	return func(s *Status) []string {
		vs := make([]string, 0, len(s.Labels))
		for _, l := range s.Labels {
			if l.Key == key {
				vs = append(vs, l.Value)
			}
		}
		return vs
	}
}

// Framework holds a framework as defined in the /state.json Mesos HTTP endpoint.
type Framework struct {
	Tasks    []Task `json:"tasks"`
	PID      PID    `json:"pid"`
	Name     string `json:"name"`
	Hostname string `json:"hostname"`
}

// HostPort returns the hostname and port where a framework's scheduler is
// listening on.
func (f Framework) HostPort() (string, string) {
	if f.PID.UPID != nil {
		return f.PID.Host, f.PID.Port
	}
	return f.Hostname, ""
}

// Slave holds a slave as defined in the /state.json Mesos HTTP endpoint.
type Slave struct {
	ID       string `json:"id"`
	Hostname string `json:"hostname"`
	PID      PID    `json:"pid"`
}

// PID holds a Mesos PID and implements the json.Unmarshaler interface.
type PID struct{ *upid.UPID }

// UnmarshalJSON implements the json.Unmarshaler interface for PIDs.
func (p *PID) UnmarshalJSON(data []byte) (err error) {
	p.UPID, err = upid.Parse(string(bytes.Trim(data, `" `)))
	return err
}

// State holds the state defined in the /state.json Mesos HTTP endpoint.
type State struct {
	Frameworks []Framework `json:"frameworks"`
	Slaves     []Slave     `json:"slaves"`
	Leader     string      `json:"leader"`
}

// DiscoveryInfo holds the discovery meta data for a task defined in the /state.json Mesos HTTP endpoint.
type DiscoveryInfo struct {
	Visibilty   string `json:"visibility"`
	Version     string `json:"version,omitempty"`
	Name        string `json:"name,omitempty"`
	Location    string `json:"location,omitempty"`
	Environment string `json:"environment,omitempty"`
	Labels      struct {
		Labels []Label `json:"labels"`
	} `json:"labels"`
	Ports Ports `json:"ports"`
}

// Ports holds a list of DiscoveryPort
type Ports struct {
	DiscoveryPorts []DiscoveryPort `json:"ports"`
}

// DiscoveryPort holds a port for a task defined in the /state.json Mesos HTTP endpoint.
type DiscoveryPort struct {
	Protocol string `json:"protocol"`
	Number   int    `json:"number"`
	Name     string `json:"name"`
}
