// createMetricsSubArray creates an array of metrics for map values returned by facter
func createMetricsSubArray(name string, value map[string]interface{}) ([]plugin.MetricType, error) {

	var ms []plugin.MetricType

	namespaces := []string{}

	err := utils.FromMap(value, name, &namespaces)
	if err != nil {
		return nil, err
	}

	for _, namespace := range namespaces {
		ns := strings.Split(namespace, "/")
		val := utils.GetValueByNamespace(value, ns[1:])
		m := *plugin.NewMetricType(createNamespace(ns...), time.Now(), nil, "", val)
		ms = append(ms, m)
	}

	return ms, nil
}
func (m *Mesos) CollectMetrics(mts []plugin.MetricType) ([]plugin.MetricType, error) {
	configItems, err := getConfig(mts[0])
	if err != nil {
		return nil, err
	}

	requestedMaster := []core.Namespace{}
	requestedAgent := []core.Namespace{}

	for _, metricType := range mts {
		switch metricType.Namespace().Strings()[2] {
		case "master":
			requestedMaster = append(requestedMaster, metricType.Namespace())
		case "agent":
			requestedAgent = append(requestedAgent, metricType.Namespace())
		}
	}

	// Translate Mesos metrics into Snap PluginMetrics
	now := time.Now()
	metrics := []plugin.MetricType{}

	if configItems["master"] != "" && len(requestedMaster) > 0 {
		log.Info("Collecting ", len(requestedMaster), " metrics from the master")
		isLeader, err := master.IsLeader(configItems["master"])
		if err != nil {
			log.Error(err)
			return nil, err
		}
		if isLeader {
			snapshot, err := master.GetMetricsSnapshot(configItems["master"])
			if err != nil {
				log.Error(err)
				return nil, err
			}

			frameworks, err := master.GetFrameworks(configItems["master"])
			if err != nil {
				log.Error(err)
				return nil, err
			}

			tags := map[string]string{"source": configItems["master"]}

			for _, requested := range requestedMaster {
				isDynamic, _ := requested.IsDynamic()
				if isDynamic {
					n := requested.Strings()[4:]

					// Iterate through the array of frameworks returned by GetFrameworks()
					for _, framework := range frameworks {
						val := ns.GetValueByNamespace(framework, n)
						if val == nil {
							log.Warn("Attempted to collect metric ", requested.String(), " but it returned nil!")
							continue
						}
						// substituting "framework" wildcard with particular framework id
						requested[3].Value = framework.ID
						// TODO(roger): units
						metrics = append(metrics, *plugin.NewMetricType(requested, now, tags, "", val))

					}
				} else {
					n := requested.Strings()[3:]
					val, ok := snapshot[strings.Join(n, "/")]
					if !ok {
						e := fmt.Errorf("error: requested metric %s not found", requested.String())
						log.Error(e)
						return nil, e
					}
					//TODO(kromar): is it possible to provide unit NewMetricType(ns, time, tags, unit, value)?
					// I'm leaving empty string for now...
					metrics = append(metrics, *plugin.NewMetricType(requested, now, tags, "", val))
				}
			}
		} else {
			log.Info("Attempted CollectMetrics() on ", configItems["master"], "but it isn't the leader. Skipping...")
		}
	}

	if configItems["agent"] != "" && len(requestedAgent) > 0 {
		log.Info("Collecting ", len(requestedAgent), " metrics from the agent")
		snapshot, err := agent.GetMetricsSnapshot(configItems["agent"])
		if err != nil {
			log.Error(err)
			return nil, err
		}

		executors, err := agent.GetMonitoringStatistics(configItems["agent"])
		if err != nil {
			log.Error(err)
			return nil, err
		}

		tags := map[string]string{"source": configItems["agent"]}

		for _, requested := range requestedAgent {
			n := requested.Strings()[5:]
			isDynamic, _ := requested.IsDynamic()
			if isDynamic {
				// Iterate through the array of executors returned by GetMonitoringStatistics()
				for _, exec := range executors {
					val := ns.GetValueByNamespace(exec.Statistics, n)
					if val == nil {
						log.Warn("Attempted to collect metric ", requested.String(), " but it returned nil!")
						continue
					}
					// substituting "framework" wildcard with particular framework id
					requested[3].Value = exec.Framework
					// substituting "executor" wildcard with particular executor id
					requested[4].Value = exec.ID
					// TODO(roger): units
					metrics = append(metrics, *plugin.NewMetricType(requested, now, tags, "", val))

				}
			} else {
				// Get requested metrics from the snapshot map
				n := requested.Strings()[3:]
				val, ok := snapshot[strings.Join(n, "/")]
				if !ok {
					e := fmt.Errorf("error: requested metric %v not found", requested.String())
					log.Error(e)
					return nil, e
				}

				//TODO(kromar): units here also?
				metrics = append(metrics, *plugin.NewMetricType(requested, now, tags, "", val))
			}
		}
	}

	log.Debug("Collected a total of ", len(metrics), " metrics.")
	return metrics, nil
}
// CollectMetrics retrieves values of requested metrics
func (d *docker) CollectMetrics(mts []plugin.MetricType) ([]plugin.MetricType, error) {
	var err error
	metrics := []plugin.MetricType{}
	d.list = map[string]dock.APIContainers{}

	// get list of possible network metrics
	networkMetrics := []string{}
	utils.FromCompositionTags(wrapper.NetworkInterface{}, "", &networkMetrics)

	// get list of all running containers
	d.list, err = d.client.ListContainersAsMap()
	if err != nil {
		fmt.Fprintln(os.Stderr, "The list of running containers cannot be retrived, err=", err)
		return nil, err
	}

	// retrieve requested docker ids
	rids, err := d.getRequestedIDs(mts...)
	if err != nil {
		return nil, err
	}

	// for each requested id set adequate item into docker.container struct with stats
	for _, rid := range rids {

		if contSpec, exist := d.list[rid]; !exist {
			return nil, fmt.Errorf("Docker container does not exist, container_id=%s", rid)
		} else {
			stats, err := d.client.GetStatsFromContainer(contSpec.ID, true)
			if err != nil {
				return nil, err
			}

			// set new item to docker.container structure
			d.containers[rid] = containerData{
				ID: contSpec.ID,
				Info: wrapper.Specification{
					Status:     contSpec.Status,
					Created:    time.Unix(contSpec.Created, 0).Format("2006-01-02T15:04:05Z07:00"),
					Image:      contSpec.Image,
					SizeRw:     contSpec.SizeRw,
					SizeRootFs: contSpec.SizeRootFs,
					Labels:     contSpec.Labels,
				},
				Stats: stats,
			}

		}
	}

	for _, mt := range mts {
		ids, err := d.getRequestedIDs(mt)
		if err != nil {
			return nil, err
		}

		for _, id := range ids {
			ns := make([]core.NamespaceElement, len(mt.Namespace()))
			copy(ns, mt.Namespace())
			ns[2].Value = id

			// omit "spec" metrics for root
			if id == "root" && mt.Namespace()[lengthOfNsPrefix].Value == "spec" {
				continue
			}
			isDynamic, indexes := mt.Namespace()[lengthOfNsPrefix:].IsDynamic()

			metricName := mt.Namespace().Strings()[lengthOfNsPrefix:]

			// remove added static element (`value`)
			if metricName[len(metricName)-1] == "value" {
				metricName = metricName[:len(metricName)-1]
			}

			if !isDynamic {
				metric := plugin.MetricType{
					Timestamp_: time.Now(),
					Namespace_: ns,
					Data_:      utils.GetValueByNamespace(d.containers[id], metricName),
					Tags_:      mt.Tags(),
					Config_:    mt.Config(),
					Version_:   VERSION,
				}

				metrics = append(metrics, metric)
				continue

			}

			// take the element of metricName which precedes the first dynamic element
			// e.g. {"filesystem", "*", "usage"}
			// 	-> statsType will be "filesystem",
			// 	-> scope of metricName will be decreased to {"*", "usage"}

			indexOfDynamicElement := indexes[0]
			statsType := metricName[indexOfDynamicElement-1]
			metricName = metricName[indexOfDynamicElement:]

			switch statsType {
			case "filesystem":
				// get docker filesystem statistics
				devices := []string{}

				if metricName[0] == "*" {
					// when device name is requested as as asterisk - take all available filesystem devices
					for deviceName := range d.containers[id].Stats.Filesystem {
						devices = append(devices, deviceName)
					}
				} else {
					// device name is requested explicitly
					device := metricName[0]
					if _, ok := d.containers[id].Stats.Filesystem[device]; !ok {
						return nil, fmt.Errorf("In metric %s the given device name is invalid (no stats for this device)", mt.Namespace().String())
					}

					devices = append(devices, metricName[0])
				}

				for _, device := range devices {
					rns := make([]core.NamespaceElement, len(ns))
					copy(rns, ns)

					rns[indexOfDynamicElement+lengthOfNsPrefix].Value = device

					metric := plugin.MetricType{
						Timestamp_: time.Now(),
						Namespace_: rns,
						Data_:      utils.GetValueByNamespace(d.containers[id].Stats.Filesystem[device], metricName[1:]),
						Tags_:      mt.Tags(),
						Config_:    mt.Config(),
						Version_:   VERSION,
					}
					metrics = append(metrics, metric)
				}

			case "labels":
				// get docker labels
				labelKeys := []string{}
				if metricName[0] == "*" {
					// when label key is requested as an asterisk - take all available labels
					for labelKey := range d.containers[id].Info.Labels {
						labelKeys = append(labelKeys, labelKey)
					}
				} else {
					labelKey := metricName[0]
					if _, ok := d.containers[id].Info.Labels[labelKey]; !ok {
						return nil, fmt.Errorf("In metric %s the given label is invalid (no value for this label key)", mt.Namespace().String())
					}

					labelKeys = append(labelKeys, metricName[0])
				}

				for _, labelKey := range labelKeys {
					rns := make([]core.NamespaceElement, len(ns))
					copy(rns, ns)
					rns[indexOfDynamicElement+lengthOfNsPrefix].Value = utils.ReplaceNotAllowedCharsInNamespacePart(labelKey)
					metric := plugin.MetricType{
						Timestamp_: time.Now(),
						Namespace_: rns,
						Data_:      d.containers[id].Info.Labels[labelKey],
						Tags_:      mt.Tags(),
						Config_:    mt.Config(),
						Version_:   VERSION,
					}

					metrics = append(metrics, metric)
				}

			case "network":
				//get docker network tx/rx statistics
				netInterfaces := []string{}
				ifaceMap := map[string]wrapper.NetworkInterface{}
				for _, iface := range d.containers[id].Stats.Network {
					ifaceMap[iface.Name] = iface
				}

				// support wildcard on interface name
				if metricName[0] == "*" {
					for _, netInterface := range d.containers[id].Stats.Network {
						netInterfaces = append(netInterfaces, netInterface.Name)
					}
				} else {
					netInterface := metricName[0]
					if _, ok := ifaceMap[netInterface]; !ok {
						return nil, fmt.Errorf("In metric %s the given network interface is invalid (no stats for this net interface)", mt.Namespace().String())
					}
					netInterfaces = append(netInterfaces, metricName[0])
				}

				for _, ifaceName := range netInterfaces {
					rns := make([]core.NamespaceElement, len(ns))
					copy(rns, ns)
					rns[indexOfDynamicElement+lengthOfNsPrefix].Value = ifaceName
					metric := plugin.MetricType{
						Timestamp_: time.Now(),
						Namespace_: rns,
						Data_:      utils.GetValueByNamespace(ifaceMap[ifaceName], metricName[1:]),
						Tags_:      mt.Tags(),
						Config_:    mt.Config(),
						Version_:   VERSION,
					}
					metrics = append(metrics, metric)
				}

			case "percpu_usage":
				numOfCPUs := len(d.containers[id].Stats.CgroupStats.CpuStats.CpuUsage.PercpuUsage) - 1
				if metricName[0] == "*" {
					// when cpu ID is requested as an asterisk - take all available
					for cpuID, val := range d.containers[id].Stats.CgroupStats.CpuStats.CpuUsage.PercpuUsage {
						rns := make([]core.NamespaceElement, len(ns))
						copy(rns, ns)

						rns[indexOfDynamicElement+lengthOfNsPrefix].Value = strconv.Itoa(cpuID)

						metric := plugin.MetricType{
							Timestamp_: time.Now(),
							Namespace_: rns,
							Data_:      val,
							Tags_:      mt.Tags(),
							Config_:    mt.Config(),
							Version_:   VERSION,
						}
						metrics = append(metrics, metric)
					}
				} else {
					cpuID, err := strconv.Atoi(metricName[0])
					if err != nil {
						return nil, fmt.Errorf("In metric %s the given cpu id is invalid, err=%v", mt.Namespace().String(), err)
					}
					if cpuID > numOfCPUs || cpuID < 0 {
						return nil, fmt.Errorf("In metric %s the given cpu id is invalid, expected value in range 0-%d", mt.Namespace().String(), numOfCPUs)
					}

					metric := plugin.MetricType{
						Timestamp_: time.Now(),
						Namespace_: ns,
						Data_:      d.containers[id].Stats.CgroupStats.CpuStats.CpuUsage.PercpuUsage[cpuID],
						Tags_:      mt.Tags(),
						Config_:    mt.Config(),
						Version_:   VERSION,
					}
					metrics = append(metrics, metric)
				}

			} // the end of switch statsType
		} // the end of range over ids
	}

	if len(metrics) == 0 {
		return nil, errors.New("No metric found")
	}

	return metrics, nil
}
// CollectMetrics returns list of requested metric values
// It returns error in case retrieval was not successful
func (c *collector) CollectMetrics(metricTypes []plugin.MetricType) ([]plugin.MetricType, error) {
	// get admin tenant from configuration. admin tenant is needed for gathering volumes and snapshots metrics at once
	item, err := config.GetConfigItem(metricTypes[0], "tenant")
	if err != nil {
		return nil, err
	}
	admin := item.(string)

	// populate information about all available tenants
	if len(c.allTenants) == 0 {
		c.allTenants, err = getTenants(metricTypes[0])
		if err != nil {
			return nil, err
		}
	}

	// iterate over metric types to resolve needed collection calls
	// for requested tenants
	collectTenants := str.InitSet()
	var collectLimits, collectVolumes, collectSnapshots bool
	for _, metricType := range metricTypes {
		namespace := metricType.Namespace()
		if len(namespace) < 6 {
			return nil, fmt.Errorf("Incorrect namespace lenth. Expected 6 is %d", len(namespace))
		}

		tenant := namespace[3].Value
		collectTenants.Add(tenant)

		if str.Contains(namespace.Strings(), "limits") {
			collectLimits = true
		} else if str.Contains(namespace.Strings(), "volumes") {
			collectVolumes = true
		} else {
			collectSnapshots = true
		}
	}

	allSnapshots := map[string]types.Snapshots{}
	allVolumes := map[string]types.Volumes{}

	// collect volumes and snapshots separately by authenticating to admin
	{
		if err := c.authenticate(metricTypes[0], admin); err != nil {
			return nil, err
		}
		provider := c.providers[admin]

		var done sync.WaitGroup
		errChn := make(chan error, 2)

		// Collect volumes
		if collectVolumes {
			done.Add(1)
			go func() {
				defer done.Done()
				volumes, err := c.service.GetVolumes(provider)

				if err != nil {
					errChn <- err
				}
				for tenantId, volumeCount := range volumes {
					tenantName := c.allTenants[tenantId]
					allVolumes[tenantName] = volumeCount
				}
			}()
		}
		// Collect snapshots
		if collectSnapshots {
			done.Add(1)
			go func() {
				defer done.Done()
				snapshots, err := c.service.GetSnapshots(provider)
				if err != nil {
					errChn <- err
				}

				for tenantId, snapshotCount := range snapshots {
					tenantName := c.allTenants[tenantId]
					allSnapshots[tenantName] = snapshotCount
				}
			}()
		}

		done.Wait()
		close(errChn)

		if e := <-errChn; e != nil {
			return nil, e
		}
	}

	// Collect limits per each tenant only if not already collected (plugin lifetime scope)
	{
		var done sync.WaitGroup
		errChn := make(chan error, collectTenants.Size())

		for _, tenant := range collectTenants.Elements() {
			_, found := c.allLimits[tenant]
			if collectLimits && !found {
				if err := c.authenticate(metricTypes[0], tenant); err != nil {
					return nil, err
				}

				provider := c.providers[tenant]

				done.Add(1)
				go func(p *gophercloud.ProviderClient, t string) {
					defer done.Done()
					limits, err := c.service.GetLimits(p)
					if err != nil {
						errChn <- err
					}
					c.allLimits[t] = limits
				}(provider, tenant)
			}
		}

		done.Wait()
		close(errChn)

		if e := <-errChn; e != nil {
			return nil, e
		}
	}

	metrics := []plugin.MetricType{}
	for _, metricType := range metricTypes {
		namespace := metricType.Namespace().Strings()
		tenant := namespace[3]
		// Construct temporary struct to accommodate all gathered metrics
		metricContainer := struct {
			S types.Snapshots `json:"snapshots"`
			V types.Volumes   `json:"volumes"`
			L types.Limits    `json:"limits"`
		}{
			allSnapshots[tenant],
			allVolumes[tenant],
			c.allLimits[tenant],
		}

		// Extract values by namespace from temporary struct and create metrics
		metric := plugin.MetricType{
			Timestamp_: time.Now(),
			Namespace_: metricType.Namespace(),
			Data_:      ns.GetValueByNamespace(metricContainer, namespace[4:]),
		}
		metrics = append(metrics, metric)
	}

	return metrics, nil
}