/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package syscfg

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"mynewt.apache.org/newt/newt/cfgv"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"

	log "github.com/sirupsen/logrus"
	"github.com/spf13/cast"

	"mynewt.apache.org/newt/newt/flashmap"
	"mynewt.apache.org/newt/newt/interfaces"
	"mynewt.apache.org/newt/newt/newtutil"
	"mynewt.apache.org/newt/newt/parse"
	"mynewt.apache.org/newt/newt/pkg"
	"mynewt.apache.org/newt/newt/project"
	"mynewt.apache.org/newt/util"
)

const HEADER_PATH = "syscfg/syscfg.h"

const SYSCFG_PREFIX_SETTING = "MYNEWT_VAL_"

type CfgSettingType int

const (
	CFG_SETTING_TYPE_RAW CfgSettingType = iota
	CFG_SETTING_TYPE_TASK_PRIO
	CFG_SETTING_TYPE_INTERRUPT_PRIO
	CFG_SETTING_TYPE_FLASH_OWNER
)

type CfgSettingState int

const (
	CFG_SETTING_STATE_GOOD CfgSettingState = iota
	CFG_SETTING_STATE_CONST
	CFG_SETTING_STATE_DEPRECATED
	CFG_SETTING_STATE_DEFUNCT
	CFG_SETTING_STATE_EXPERIMENTAL
)

type CfgFlashConflictCode int

const (
	CFG_FLASH_CONFLICT_CODE_BAD_NAME CfgFlashConflictCode = iota
	CFG_FLASH_CONFLICT_CODE_NOT_UNIQUE
)

const SYSCFG_PRIO_ANY = "any"

// Reserve last 16 priorities for the system (sanity, idle).
const SYSCFG_TASK_PRIO_MAX = 0xef

var cfgRefRe = regexp.MustCompile("MYNEWT_VAL\\((\\w+)\\)")
var cfgChoiceValRe = regexp.MustCompile("^[A-Za-z0-9_]+$")
var cfgPkgRepoName = regexp.MustCompile("^@([A-Za-z0-9-_]+)/")
var cfgPkgIllegalChar = regexp.MustCompile("[^A-Za-z0-9_]")

type CfgPoint struct {
	Value  string
	Source *pkg.LocalPackage
}

type CfgDeprecatedPoint struct {
	Entry  *CfgEntry
	Source *pkg.LocalPackage
}

type CfgEntry struct {
	Name         string
	Value        string
	ValueRefName string
	Description  string
	SettingType  CfgSettingType
	Restrictions []CfgRestriction
	ValidChoices []string
	PackageDef   *pkg.LocalPackage
	History      []CfgPoint
	State        CfgSettingState
}

type CfgPriority struct {
	SettingName string
	PackageDef  *pkg.LocalPackage // package that define the setting.
	PackageSrc  *pkg.LocalPackage // package overriding setting value.
}

type CfgFlashConflict struct {
	SettingNames []string
	Code         CfgFlashConflictCode
}

type Cfg struct {
	Settings map[string]CfgEntry

	// Restrictions at the package level (i.e. "syscfg.restrictions").
	PackageRestrictions map[string][]CfgRestriction

	//// Errors
	// Overrides of undefined settings ([setting-name] => ...).
	Orphans map[string][]CfgPoint

	// Two packages of equal priority override a setting with different
	// values; not overridden by higher priority package.
	Ambiguities map[string][]CfgPoint

	// Setting-level restrictions not met.
	SettingViolations map[string][]CfgRestriction

	// Package-level restrictions not met.
	PackageViolations map[string][]CfgRestriction

	// Attempted override by bottom-priority packages (libraries).
	PriorityViolations []CfgPriority

	// Two or more flash areas overlap.
	FlashConflicts []CfgFlashConflict

	// Multiple packages defining the same setting.
	// [setting-name][defining-package][{}]
	Redefines map[string]map[*pkg.LocalPackage]struct{}

	// Use of deprecated settings (warning).
	Deprecated map[string]struct{}

	// Use of defunct settings (error).
	Defunct map[string]struct{}

	// Use of const settings (error).
	Consts map[string]struct{}

	// Use of experimental settings (warning).
	Experimental map[string]struct{}

	// Unresolved value references
	UnresolvedValueRefs map[string]struct{}
}

func NewCfg() Cfg {
	return Cfg{
		Settings:            map[string]CfgEntry{},
		PackageRestrictions: map[string][]CfgRestriction{},
		Orphans:             map[string][]CfgPoint{},
		Ambiguities:         map[string][]CfgPoint{},
		SettingViolations:   map[string][]CfgRestriction{},
		PackageViolations:   map[string][]CfgRestriction{},
		PriorityViolations:  []CfgPriority{},
		FlashConflicts:      []CfgFlashConflict{},
		Redefines:           map[string]map[*pkg.LocalPackage]struct{}{},
		Deprecated:          map[string]struct{}{},
		Defunct:             map[string]struct{}{},
		Consts:              map[string]struct{}{},
		Experimental:        map[string]struct{}{},
		UnresolvedValueRefs: map[string]struct{}{},
	}
}

func SettingValues(entries map[string]CfgEntry) *cfgv.Settings {
	settings := cfgv.NewSettingsPrealloc(len(entries))
	for k, v := range entries {
		settings.Set(k, v.Value)
	}

	return settings
}

func (cfg *Cfg) SettingValues() *cfgv.Settings {
	return SettingValues(cfg.Settings)
}

func ResolveValueRefName(val string) string {
	// If the value has the `MYNEWT_VAL([...])` notation, then extract the
	// parenthesized identifier.
	m := cfgRefRe.FindStringSubmatch(val)
	if m == nil {
		return ""
	} else {
		// TODO we may try to resolve nested references...
		return m[1]
	}
}

func (cfg *Cfg) ExpandRef(val string) (string, string, error) {
	refName := ResolveValueRefName(val)
	if refName == "" {
		// Not a reference.
		return "", val, nil
	}

	entry, ok := cfg.Settings[refName]
	if !ok {
		return "", "", util.FmtNewtError(
			"setting value \"%s\" references undefined setting", val)
	}

	return entry.Name, entry.Value, nil

}

func (cfg *Cfg) AddInjectedSettings() {
	for _, setting := range strings.Split(util.InjectSyscfg, ":") {
		kv := strings.SplitN(setting, "=", 2)
		if len(kv) < 2 {
			continue
		}

		k := kv[0]
		v := kv[1]

		if entry, ok := cfg.Settings[k]; ok {
			entry.appendValue(nil, v)
			cfg.Settings[k] = entry
		}
	}
}

func (cfg *Cfg) ResolveValueRefs() {
	for k, entry := range cfg.Settings {
		refName, val, err := cfg.ExpandRef(strings.TrimSpace(entry.Value))
		if err != nil {
			// Referenced setting doesn't exist.  Set unresolved setting value
			// to 0, this way restrictions can be evaluated and won't create
			// spurious warnings.
			entry.Value = "0"
			cfg.UnresolvedValueRefs[k] = struct{}{}
			cfg.Settings[k] = entry
		} else if refName != "" {
			entry.ValueRefName = refName
			entry.Value = val
			cfg.Settings[k] = entry
		}
	}
}

// If the specified package has any injected settings, returns a new map
// consisting of the union of the injected settings and the provided base
// settings.
//
// Else (no injected settings), returns the base settings unmodified.
func (cfg *Cfg) settingsForLpkg(lpkg *pkg.LocalPackage,
	baseSettings *cfgv.Settings) *cfgv.Settings {

	injected := lpkg.InjectedSettings()
	if injected.Count() == 0 {
		return baseSettings
	}

	settings := cfgv.NewSettings(baseSettings)

	for k, v := range lpkg.InjectedSettings().ToMap() {
		ok := settings.Exists(k)
		if ok {
			log.Warnf("Attempt to override syscfg setting %s with "+
				"injected feature from package %s", k, lpkg.FullName())
		} else {
			settings.Set(k, v)
		}
	}

	return settings
}

func (cfg *Cfg) AllSettingsForLpkg(lpkg *pkg.LocalPackage) *cfgv.Settings {
	return cfg.settingsForLpkg(lpkg, cfg.SettingValues())
}

func (point CfgPoint) Name() string {
	if point.Source == nil {
		return "newt"
	} else {
		return point.Source.FullName()
	}
}

func (point CfgPoint) IsInjected() bool {
	return point.Source == nil
}

func (entry *CfgEntry) IsTrue() bool {
	return parse.ValueIsTrue(entry.Value)
}

func (entry *CfgEntry) appendValue(lpkg *pkg.LocalPackage, value interface{}) {
	strval := stringValue(value)
	point := CfgPoint{Value: strval, Source: lpkg}
	entry.History = append(entry.History, point)
	entry.Value = strval
}

// Replaces the source (defining) package in a syscfg entry.
func (entry *CfgEntry) replaceSource(lpkg *pkg.LocalPackage) {
	entry.PackageDef = lpkg
	entry.History[0].Source = lpkg
}

func historyToString(history []CfgPoint) string {
	if len(history) == 0 {
		return "(undefined)"
	}

	str := "["
	for i, _ := range history {
		if i != 0 {
			str += ", "
		}
		p := history[len(history)-i-1]
		str += fmt.Sprintf("%s:%s", p.Name(), p.Value)
	}
	str += "]"

	return str
}

func (entry *CfgEntry) ambiguities() []CfgPoint {
	diffVals := false
	var points []CfgPoint

	for i := 1; i < len(entry.History)-1; i++ {
		cur := entry.History[len(entry.History)-i-1]
		next := entry.History[len(entry.History)-i]

		// If either setting is injected, there is no ambiguity
		if cur.Source == nil || next.Source == nil {
			break
		}

		// If the two package have different priorities, there is no ambiguity.
		if findPkgPriority(cur.Source) != findPkgPriority(next.Source) {
			break
		}

		if cur.Value != next.Value {
			diffVals = true
		}

		if len(points) == 0 {
			points = append(points, cur)
		}
		points = append(points, next)
	}

	// If all values are identical, there is no ambiguity
	if !diffVals {
		points = nil
	}

	return points
}

func (entry *CfgEntry) ambiguityText() string {
	points := entry.ambiguities()
	if len(points) == 0 {
		return ""
	}

	str := fmt.Sprintf("Setting: %s, Packages: [", entry.Name)
	for i, p := range points {
		if i > 0 {
			str += ", "
		}

		str += p.Source.FullName()
	}
	str += "]"

	return str
}

// Detects all priority violations in an entry and returns them in a slice.
func (entry *CfgEntry) priorityViolations() []CfgPriority {
	var violations []CfgPriority

	for i := 1; i < len(entry.History); i++ {
		p := entry.History[i]

		// Overrides must come from a higher priority package, with one
		// exception: a package can override its own setting.
		curPrio := findPkgPriority(p.Source)
		defPrio := findPkgPriority(entry.PackageDef)

		if p.Source != entry.PackageDef && curPrio <= defPrio && entry.History[0].Value != "" {
			priority := CfgPriority{
				PackageDef:  entry.PackageDef,
				PackageSrc:  p.Source,
				SettingName: entry.Name,
			}
			violations = append(violations, priority)
		}
	}

	return violations
}

func FeatureToCflag(featureName string) string {
	return fmt.Sprintf("-D%s=1", settingName(featureName))
}

func stringValue(val interface{}) string {
	return strings.TrimSpace(cast.ToString(val))
}

func boolValue(val interface{}) bool {
	if val == nil {
		return false
	}

	switch v := val.(type) {
	case bool:
		return v
	case int:
		return v != 0
	default:
		return true
	}
}

func readSetting(name string, lpkg *pkg.LocalPackage,
	vals map[interface{}]interface{}) (CfgEntry, error) {

	entry := CfgEntry{}

	entry.Name = name
	entry.PackageDef = lpkg
	entry.Description = stringValue(vals["description"])

	if boolValue(vals["defunct"]) {
		entry.State = CFG_SETTING_STATE_DEFUNCT
	} else if boolValue(vals["deprecated"]) {
		entry.State = CFG_SETTING_STATE_DEPRECATED
	} else if boolValue(vals["experimental"]) {
		entry.State = CFG_SETTING_STATE_EXPERIMENTAL
	} else {
		entry.State = CFG_SETTING_STATE_GOOD
	}

	stateVal, stateExist := vals["state"]
	if stateExist {
		if entry.State != CFG_SETTING_STATE_GOOD {
			util.OneTimeWarning("Setting %s has duplicated state definition, using \"state\".", name)
		}

		switch stringValue(stateVal) {
		case "defunct":
			entry.State = CFG_SETTING_STATE_DEFUNCT
		case "deprecated":
			entry.State = CFG_SETTING_STATE_DEPRECATED
		case "experimental":
			entry.State = CFG_SETTING_STATE_EXPERIMENTAL
		default:
			util.OneTimeWarning("Invalid \"state\" for setting %s, assuming regular state.", name)
		}
	}

	// The value field for setting definition is required.
	valueVal, valueExist := vals["value"]
	if valueExist {
		entry.Value = stringValue(valueVal)
	} else if entry.State == CFG_SETTING_STATE_DEFUNCT {
		// defunct settings do not need default value, we only care if
		// value was set anywhere and emit an error then
		entry.Value = ""
	} else {
		return entry, util.FmtNewtError(
			"setting %s does not have required value field", name)
	}

	if vals["type"] == nil {
		entry.SettingType = CFG_SETTING_TYPE_RAW
	} else {
		var ok bool
		typename := stringValue(vals["type"])
		entry.SettingType, ok = cfgSettingNameTypeMap[typename]
		if !ok {
			return entry, util.FmtNewtError(
				"setting %s specifies invalid type: %s", name, typename)
		}
	}
	entry.appendValue(lpkg, entry.Value)

	entry.Restrictions = []CfgRestriction{}
	restrictionStrings := cast.ToStringSlice(vals["restrictions"])
	for _, rstring := range restrictionStrings {
		r, err := readRestriction(name, rstring)
		if err != nil {
			return entry,
				util.PreNewtError(err, "error parsing setting %s", name)
		}
		entry.Restrictions = append(entry.Restrictions, r)
	}

	if vals["choices"] != nil && vals["range"] != nil {
		return entry, util.FmtNewtError(
			"setting %s uses both choice and range restrictions", name)
	}

	if vals["choices"] != nil {
		var choices []string
		switch vals["choices"].(type) {
		default:
			choices = cast.ToStringSlice(vals["choices"])
		case string:
			choices = strings.Split(vals["choices"].(string), ",")
		}

		entry.ValidChoices = choices

		sort.Slice(choices, func(a, b int) bool {
			return strings.ToLower(choices[a]) < strings.ToLower(choices[b])
		})

		for i, choice := range choices {
			if !cfgChoiceValRe.MatchString(choice) {
				return entry, util.FmtNewtError(
					"setting %s has invalid choice defined (%s) - "+
						"only letters, numbers and underscore are allowed", name, choice)
			}

			if i > 0 && strings.ToLower(choices[i-1]) == strings.ToLower(choice) {
				return entry, util.FmtNewtError(
					"setting %s has duplicated choice defined ('%s' and '%s')",
					name, choice, choices[i-1])
			}
		}

		r := CfgRestriction{
			BaseSetting: name,
			Code:        CFG_RESTRICTION_CODE_CHOICE,
		}

		entry.Restrictions = append(entry.Restrictions, r)
	}

	if vals["range"] != nil {
		r, err := createRangeRestriction(name, stringValue(vals["range"]))
		if err != nil {
			return entry,
				util.PreNewtError(err, "error parsing setting %s", name)
		}

		entry.Restrictions = append(entry.Restrictions, r)
	}

	return entry, nil
}

// Records a redefinition error (two packages defining the same setting).
func (cfg *Cfg) addRedefine(settingName string,
	pkg1 *pkg.LocalPackage, pkg2 *pkg.LocalPackage) {

	if cfg.Redefines[settingName] == nil {
		cfg.Redefines[settingName] = map[*pkg.LocalPackage]struct{}{}
	}

	log.Debugf("Detected multiple definitions of the same setting: "+
		"setting=%s pkg1=%s pkg2=%s",
		settingName, pkg1.FullName(), pkg2.FullName())

	cfg.Redefines[settingName][pkg1] = struct{}{}
	cfg.Redefines[settingName][pkg2] = struct{}{}
}

func (cfg *Cfg) readDefsOnce(lpkg *pkg.LocalPackage,
	settings *cfgv.Settings) error {
	yc := lpkg.SyscfgY

	lsettings := cfg.settingsForLpkg(lpkg, settings)

	defs, err := yc.GetValStringMap("syscfg.defs", lsettings)
	util.OneTimeWarningError(err)

	if defs != nil {
		for k, v := range defs {
			var entry CfgEntry
			var err error

			switch v.(type) {
			case map[interface{}]interface{}:
				vals := v.(map[interface{}]interface{})
				entry, err = readSetting(k, lpkg, vals)
				if err != nil {
					return util.FmtNewtError("Config for package %s: %s",
						lpkg.FullName(), err.Error())
				}
			case int:
				val := v.(int)
				entry = CfgEntry{
					Name:       k,
					PackageDef: lpkg,
					State:      CFG_SETTING_STATE_CONST,
					Value:      stringValue(val),
					History: []CfgPoint{{
						Value:  stringValue(val),
						Source: lpkg,
					}},
				}
			case string:
				val := v.(string)
				entry = CfgEntry{
					Name:       k,
					PackageDef: lpkg,
					State:      CFG_SETTING_STATE_CONST,
					Value:      val,
					History: []CfgPoint{{
						Value:  val,
						Source: lpkg,
					}},
				}
			default:
				return util.FmtNewtError("Package \"%s\" contains invalid "+
					"\"syscfg.defs\" for \"%s\". It should be either full definition "+
					"(configurable setting) or single value (const setting)",
					lpkg.FullName(), k)
			}

			replace := true
			if oldEntry, exists := cfg.Settings[k]; exists {
				// Setting already defined.  This is allowed for injected
				// settings (settings automatically populated by newt).  For
				// all other settings, record a redefinition error.
				point := mostRecentPoint(oldEntry)
				if point.IsInjected() {
					// Indicate that the setting came from this package, not
					// from newt.
					entry.replaceSource(lpkg)

					// Keep the injected value (override the default).
					entry.Value = oldEntry.Value
				} else {
					replace = false
					cfg.addRedefine(k, oldEntry.PackageDef, lpkg)
				}
			}

			if replace {
				// Not an illegal redefine; populate the master settings list
				// with the new entry.
				cfg.Settings[k] = entry
			}
		}
	}

	return nil
}

// Records an orphan override error (override of undefined setting).
func (cfg *Cfg) addOrphan(settingName string, value string,
	lpkg *pkg.LocalPackage) {

	cfg.Orphans[settingName] = append(cfg.Orphans[settingName], CfgPoint{
		Value:  value,
		Source: lpkg,
	})
}

func (cfg *Cfg) readRestrictions(lpkg *pkg.LocalPackage,
	settings *cfgv.Settings) error {

	yc := lpkg.SyscfgY
	lsettings := cfg.settingsForLpkg(lpkg, settings)

	restrictionStrings, err := yc.GetValStringSlice("syscfg.restrictions", lsettings)
	util.OneTimeWarningError(err)

	for _, rstring := range restrictionStrings {
		r, err := readRestriction("", rstring)
		if err != nil {
			return util.PreNewtError(err, "error parsing restriction: %s", rstring)
		}
		cfg.PackageRestrictions[lpkg.Name()] =
			append(cfg.PackageRestrictions[lpkg.Name()], r)
	}

	return nil
}

func (cfg *Cfg) readValsOnce(lpkg *pkg.LocalPackage,
	settings *cfgv.Settings) error {
	yc := lpkg.SyscfgY

	lsettings := cfg.settingsForLpkg(lpkg, settings)

	values, err := yc.GetValStringMap("syscfg.vals", lsettings)
	if err != nil {
		return err
	}

	for k, v := range values {
		switch v.(type) {
		case map[interface{}]interface{}, []interface{}:
			return util.FmtNewtError("Package \"%s\" contains invalid "+
				"\"syscfg.vals\" map; expected single value, but setting "+
				"\"%s\" specifies multiple values.  Did you mix up "+
				"\"syscfg.defs\" and \"syscfg.vals\"?",
				lpkg.FullName(), k)
		}

		entry, ok := cfg.Settings[k]
		if ok {
			entry.appendValue(lpkg, v)
			cfg.Settings[k] = entry
		} else {
			cfg.addOrphan(k, stringValue(v), lpkg)
		}

		switch entry.State {
		case CFG_SETTING_STATE_DEPRECATED:
			cfg.Deprecated[k] = struct{}{}
		case CFG_SETTING_STATE_DEFUNCT:
			cfg.Defunct[k] = struct{}{}
		case CFG_SETTING_STATE_CONST:
			cfg.Consts[k] = struct{}{}
		case CFG_SETTING_STATE_EXPERIMENTAL:
			cfg.Experimental[k] = struct{}{}
		}
	}

	return nil
}

func (cfg *Cfg) Log() {
	keys := make([]string, len(cfg.Settings))
	i := 0
	for k, _ := range cfg.Settings {
		keys[i] = k
		i++
	}
	sort.Strings(keys)

	log.Debugf("syscfg settings (%d entries):", len(cfg.Settings))
	for _, k := range keys {
		entry := cfg.Settings[k]

		log.Debugf("    %s=%s %s", k, entry.Value,
			historyToString(entry.History))
	}
}

// Removes all traces of a package from syscfg.
func (cfg *Cfg) DeletePkg(lpkg *pkg.LocalPackage) {
	log.Debugf("Deleting package from syscfg: %s", lpkg.FullName())

	// First, check if the specified package contributes to a redefinition
	// error.  If so, the deletion of this package might resolve the error.
	for name, m := range cfg.Redefines {
		delete(m, lpkg)
		if len(m) == 1 {
			// Only one package defines this setting now; the redefinition
			// error is resolved.
			delete(cfg.Redefines, name)

			//Loop through the map `m` just to get access to its only element.
			var source *pkg.LocalPackage
			for lp, _ := range m {
				source = lp
			}

			// Replace the setting's source with the one remaining package that
			// defines the setting.
			entry := cfg.Settings[name]
			entry.replaceSource(source)

			// Update the master settings map.
			cfg.Settings[name] = entry

			log.Debugf("Resolved syscfg redefine; setting=%s pkg=%s",
				name, source.FullName())
		}
	}

	// Next, delete the specified package from the master settings map.
	for name, entry := range cfg.Settings {
		if entry.PackageDef == lpkg {
			// The package-to-delete is this setting's source.  All overrides
			// become orphans.
			for i := 1; i < len(entry.History); i++ {
				p := entry.History[i]
				cfg.addOrphan(name, stringValue(p.Value), p.Source)
			}
			delete(cfg.Settings, name)
		} else {
			// Remove any overrides created by the deleted package.
			for i := 1; i < len(entry.History); /* i inc. in loop body */ {
				if entry.History[i].Source == lpkg {
					entry.History = append(
						entry.History[:i], entry.History[i+1:]...)

					cfg.Settings[name] = entry
				} else {
					i++
				}
			}
		}
	}
}

func (cfg *Cfg) settingsOfType(typ CfgSettingType) []CfgEntry {
	entries := []CfgEntry{}

	for _, entry := range cfg.Settings {
		if entry.SettingType == typ {
			entries = append(entries, entry)
		}
	}

	return entries
}

func (cfg *Cfg) detectViolations() {
	settings := cfg.SettingValues()
	for _, entry := range cfg.Settings {
		var sv []CfgRestriction
		for _, r := range entry.Restrictions {
			if !cfg.restrictionMet(r, settings) {
				sv = append(sv, r)
			}
		}

		if sv != nil {
			cfg.SettingViolations[entry.Name] = sv
		}
	}

	pkgNames := make([]string, 0, len(cfg.PackageRestrictions))
	for n, _ := range cfg.PackageRestrictions {
		pkgNames = append(pkgNames, n)
	}
	sort.Strings(pkgNames)

	for _, n := range pkgNames {
		var pv []CfgRestriction
		for _, r := range cfg.PackageRestrictions[n] {
			if !cfg.restrictionMet(r, settings) {
				pv = append(pv, r)
			}
		}

		if pv != nil {
			cfg.PackageViolations[n] = pv
		}
	}
}

// Detects all priority violations in the syscfg and records them internally.
func (cfg *Cfg) detectPriorityViolations() {
	for _, entry := range cfg.Settings {
		cfg.PriorityViolations = append(
			cfg.PriorityViolations, entry.priorityViolations()...)
	}
}

// Detects all flash conflict errors in the syscfg and records them internally.
func (cfg *Cfg) detectFlashConflicts(flashMap flashmap.FlashMap) {
	entries := cfg.settingsOfType(CFG_SETTING_TYPE_FLASH_OWNER)

	areaEntryMap := map[string][]CfgEntry{}

	for _, entry := range entries {
		if entry.Value != "" {
			area, ok := flashMap.Areas[entry.Value]
			if !ok {
				conflict := CfgFlashConflict{
					SettingNames: []string{entry.Name},
					Code:         CFG_FLASH_CONFLICT_CODE_BAD_NAME,
				}
				cfg.FlashConflicts = append(cfg.FlashConflicts, conflict)
			} else {
				areaEntryMap[area.Name] =
					append(areaEntryMap[area.Name], entry)
			}
		}
	}

	// Settings with type flash_owner must have unique values.
	for _, entries := range areaEntryMap {
		if len(entries) > 1 {
			conflict := CfgFlashConflict{
				SettingNames: []string{},
				Code:         CFG_FLASH_CONFLICT_CODE_NOT_UNIQUE,
			}

			for _, entry := range entries {
				conflict.SettingNames =
					append(conflict.SettingNames, entry.Name)
			}

			cfg.FlashConflicts = append(cfg.FlashConflicts, conflict)
		}
	}
}

func (cfg *Cfg) flashConflictErrorText(conflict CfgFlashConflict) string {
	entry := cfg.Settings[conflict.SettingNames[0]]

	switch conflict.Code {
	case CFG_FLASH_CONFLICT_CODE_BAD_NAME:
		return fmt.Sprintf("Setting %s specifies unknown flash area: %s\n",
			entry.Name, entry.Value)

	case CFG_FLASH_CONFLICT_CODE_NOT_UNIQUE:
		return fmt.Sprintf(
			"Multiple flash_owner settings specify the same flash area\n"+
				"          settings: %s\n"+
				"        flash area: %s\n",
			strings.Join(conflict.SettingNames, ", "),
			entry.Value)

	default:
		panic("Invalid flash conflict code: " + string(conflict.Code))
	}
}

func historyTextOnce(settingName string, points []CfgPoint) string {
	return fmt.Sprintf("    %s: %s\n", settingName, historyToString(points))
}

func historyText(historyMap map[string][]CfgPoint) string {
	if len(historyMap) == 0 {
		return ""
	}

	str := "Setting history (newest -> oldest):\n"
	names := make([]string, 0, len(historyMap))
	for name, _ := range historyMap {
		names = append(names, name)
	}
	sort.Strings(names)

	for _, name := range names {
		points := historyMap[name]
		str += historyTextOnce(name, points)
	}

	return str
}

func (cfg *Cfg) ErrorText() string {
	str := ""

	historyMap := map[string][]CfgPoint{}

	// Redefinition errors.
	if len(cfg.Redefines) > 0 {
		settingNames := make([]string, len(cfg.Redefines))
		i := 0
		for k, _ := range cfg.Redefines {
			settingNames[i] = k
			i++
		}
		sort.Strings(settingNames)

		str += "Settings defined by multiple packages:"
		for _, n := range settingNames {
			pkgNames := make([]string, len(cfg.Redefines[n]))
			i := 0
			for p, _ := range cfg.Redefines[n] {
				pkgNames[i] = p.FullName()
				i++
			}
			sort.Strings(pkgNames)

			str += fmt.Sprintf("\n    %s:", n)
			for _, pn := range pkgNames {
				str += " " + pn
			}
		}
	}

	// Violation errors.
	if len(cfg.SettingViolations) > 0 || len(cfg.PackageViolations) > 0 {
		str += "Syscfg restriction violations detected:\n"
		for settingName, rslice := range cfg.SettingViolations {
			baseEntry := cfg.Settings[settingName]
			historyMap[settingName] = baseEntry.History
			for _, r := range rslice {
				for _, name := range r.relevantSettingNames() {
					reqEntry := cfg.Settings[name]
					historyMap[name] = reqEntry.History
				}
				str += "    " + cfg.settingViolationText(baseEntry, r) + "\n"
			}
		}
		for pkgName, rslice := range cfg.PackageViolations {
			for _, r := range rslice {
				for _, name := range r.relevantSettingNames() {
					reqEntry := cfg.Settings[name]
					historyMap[name] = reqEntry.History
				}
				str += "    " + cfg.packageViolationText(pkgName, r) + "\n"
			}
		}
	}

	// Ambiguity errors.
	if len(cfg.Ambiguities) > 0 {
		str += "Syscfg ambiguities detected:\n"

		settingNames := make([]string, 0, len(cfg.Ambiguities))
		for k, _ := range cfg.Ambiguities {
			settingNames = append(settingNames, k)
		}
		sort.Strings(settingNames)

		for _, name := range settingNames {
			entry := cfg.Settings[name]
			historyMap[entry.Name] = entry.History
			str += "    " + entry.ambiguityText()
		}
	}

	// Priority violation errors.
	if len(cfg.PriorityViolations) > 0 {
		str += "Priority violations detected (Packages can only override " +
			"settings defined by packages of lower priority):\n"
		for _, priority := range cfg.PriorityViolations {
			entry := cfg.Settings[priority.SettingName]
			historyMap[priority.SettingName] = entry.History

			str += fmt.Sprintf(
				"    Package: %s overriding setting: %s defined by %s\n",
				priority.PackageSrc.FullName(), priority.SettingName,
				priority.PackageDef.FullName())
		}
	}

	// Flash conflicts.
	if len(cfg.FlashConflicts) > 0 {
		str += "Flash errors detected:\n"
		for _, conflict := range cfg.FlashConflicts {
			for _, name := range conflict.SettingNames {
				entry := cfg.Settings[name]
				historyMap[name] = entry.History
			}

			str += "    " + cfg.flashConflictErrorText(conflict)
		}
	}

	// Overrides of defunct settings.
	if len(cfg.Defunct) > 0 {
		str += "Override of defunct settings detected:\n"
		for name, _ := range cfg.Defunct {
			entry := cfg.Settings[name]
			str += "    " + fmt.Sprintf("%s: %s\n", name, entry.Description)
			historyMap[name] = entry.History
		}
	}

	// Overrides of const settings.
	if len(cfg.Consts) > 0 {
		str += "Override of consts settings detected:\n"
		for name, _ := range cfg.Consts {
			entry := cfg.Settings[name]
			str += "    " + fmt.Sprintf("%s\n", name)
			historyMap[name] = entry.History
		}
	}

	// Unresolved value references
	if len(cfg.UnresolvedValueRefs) > 0 {
		str += "Unresolved value references:\n"
		for name, _ := range cfg.UnresolvedValueRefs {
			entry := cfg.Settings[name]
			str += "    " + fmt.Sprintf("%s -> %s\n", name, entry.ValueRefName)
			historyMap[name] = entry.History
		}
	}

	if len(historyMap) > 0 {
		str += "\n" + historyText(historyMap)
	}

	return str
}

func (cfg *Cfg) WarningText() string {
	str := ""

	historyMap := map[string][]CfgPoint{}

	if len(cfg.Orphans) > 0 {
		settingNames := make([]string, len(cfg.Orphans))
		i := 0
		for k, _ := range cfg.Orphans {
			settingNames[i] = k
			i++
		}
		sort.Strings(settingNames)

		str += "Ignoring override of undefined settings:"
		for _, n := range settingNames {
			historyMap[n] = cfg.Orphans[n]
			str += fmt.Sprintf("\n    %s", n)
		}
	}

	if len(historyMap) > 0 {
		str += "\n" + historyText(historyMap)
	}

	return str
}

func (cfg *Cfg) DeprecatedWarning() []string {
	lines := []string{}

	for k, _ := range cfg.Deprecated {
		entry, ok := cfg.Settings[k]
		if !ok {
			log.Errorf("Internal error; deprecated setting \"%s\" not in cfg",
				k)
		}

		point := mostRecentPoint(entry)
		lines = append(lines,
			fmt.Sprintf("Use of deprecated setting %s in %s: %s", k,
				point.Source.FullName(), entry.Description))
	}

	return lines
}

func (cfg *Cfg) ExperimentalWarning() []string {
	lines := []string{}

	for k, _ := range cfg.Experimental {
		entry, ok := cfg.Settings[k]
		if !ok {
			log.Errorf("Internal error; experimental setting \"%s\" not in cfg",
				k)
		}

		point := mostRecentPoint(entry)
		lines = append(lines,
			fmt.Sprintf("Use of experimental setting %s in %s", k,
				point.Source.FullName()))
	}

	return lines
}

func settingName(setting string) string {
	return SYSCFG_PREFIX_SETTING + util.CIdentifier(setting)
}

func findPkgPriority(lpkg *pkg.LocalPackage) int {
	if lpkg == nil {
		// this is correct when doing lookup for a setting that was
		// injected from command line, so let's assume this is always
		// the case here
		return int(pkg.PACKAGE_TYPE_TARGET)*pkg.PACKAGE_SUBPRIO_NUM + 1
	}
	return int(lpkg.Type())*pkg.PACKAGE_SUBPRIO_NUM + lpkg.SubPriority()
}

func normalizePkgType(typ interfaces.PackageType) interfaces.PackageType {
	switch typ {
	case pkg.PACKAGE_TYPE_TARGET:
		return pkg.PACKAGE_TYPE_TARGET
	case pkg.PACKAGE_TYPE_APP:
		return pkg.PACKAGE_TYPE_APP
	case pkg.PACKAGE_TYPE_UNITTEST:
		return pkg.PACKAGE_TYPE_UNITTEST
	case pkg.PACKAGE_TYPE_BSP:
		return pkg.PACKAGE_TYPE_BSP
	case pkg.PACKAGE_TYPE_CONFIG:
		return pkg.PACKAGE_TYPE_CONFIG
	default:
		return pkg.PACKAGE_TYPE_LIB
	}
}

func categorizePkgs(
	lpkgs []*pkg.LocalPackage) map[interfaces.PackageType][]*pkg.LocalPackage {

	pmap := map[interfaces.PackageType][]*pkg.LocalPackage{
		pkg.PACKAGE_TYPE_TARGET:   []*pkg.LocalPackage{},
		pkg.PACKAGE_TYPE_CONFIG:   []*pkg.LocalPackage{},
		pkg.PACKAGE_TYPE_APP:      []*pkg.LocalPackage{},
		pkg.PACKAGE_TYPE_UNITTEST: []*pkg.LocalPackage{},
		pkg.PACKAGE_TYPE_BSP:      []*pkg.LocalPackage{},
		pkg.PACKAGE_TYPE_LIB:      []*pkg.LocalPackage{},
	}

	for _, lpkg := range lpkgs {
		typ := normalizePkgType(lpkg.Type())
		pmap[typ] = append(pmap[typ], lpkg)
	}

	for k, v := range pmap {
		pmap[k] = pkg.SortLclPkgs(v)
	}

	return pmap
}

func (cfg *Cfg) readDefsForPkgType(lpkgs []*pkg.LocalPackage,
	settings *cfgv.Settings) error {

	for _, lpkg := range lpkgs {
		if err := cfg.readDefsOnce(lpkg, settings); err != nil {
			return err
		}
	}
	return nil
}
func (cfg *Cfg) readValsForPkgType(lpkgs []*pkg.LocalPackage,
	settings *cfgv.Settings) error {

	for _, lpkg := range lpkgs {
		if err := cfg.readValsOnce(lpkg, settings); err != nil {
			return err
		}
	}

	return nil
}

func (cfg *Cfg) detectAmbiguities() {
	for _, entry := range cfg.Settings {
		if points := entry.ambiguities(); len(points) > 0 {
			cfg.Ambiguities[entry.Name] = points
		}
	}
}

// Detects and records errors in the build's syscfg.  This should only be
// called after APIs are resolved to avoid false positives.
func (cfg *Cfg) DetectErrors(flashMap flashmap.FlashMap) {
	cfg.detectAmbiguities()
	cfg.detectViolations()
	cfg.detectPriorityViolations()
	cfg.detectFlashConflicts(flashMap)
}

func Read(lpkgs []*pkg.LocalPackage, apis []string,
	injectedSettings *cfgv.Settings, settings *cfgv.Settings,
	flashMap flashmap.FlashMap) (Cfg, error) {

	cfg := NewCfg()
	for k, v := range injectedSettings.ToMap() {
		cfg.Settings[k] = CfgEntry{
			Name:        k,
			Description: "Injected setting",
			Value:       v,
			History: []CfgPoint{{
				Value:  v,
				Source: nil,
			}},
		}

		settings.Set(k, v)
	}

	// Read system configuration files.  In case of conflicting settings, the
	// higher priority pacakge's setting wins.  Package priorities are assigned
	// as follows (highest priority first):
	//     * target
	//     * app (if present)
	//     * unittest (if no app)
	//     * bsp
	//     * everything else (lib, sdk, compiler)

	lpkgMap := categorizePkgs(lpkgs)

	for _, ptype := range []interfaces.PackageType{
		pkg.PACKAGE_TYPE_LIB,
		pkg.PACKAGE_TYPE_BSP,
		pkg.PACKAGE_TYPE_UNITTEST,
		pkg.PACKAGE_TYPE_APP,
		pkg.PACKAGE_TYPE_CONFIG,
		pkg.PACKAGE_TYPE_TARGET,
	} {
		if err := cfg.readDefsForPkgType(lpkgMap[ptype], settings); err != nil {
			return cfg, err
		}
	}

	for _, ptype := range []interfaces.PackageType{
		pkg.PACKAGE_TYPE_LIB,
		pkg.PACKAGE_TYPE_BSP,
		pkg.PACKAGE_TYPE_UNITTEST,
		pkg.PACKAGE_TYPE_APP,
		pkg.PACKAGE_TYPE_CONFIG,
		pkg.PACKAGE_TYPE_TARGET,
	} {
		if err := cfg.readValsForPkgType(lpkgMap[ptype], settings); err != nil {
			return cfg, err
		}
	}

	for _, lpkg := range lpkgs {
		if err := cfg.readRestrictions(lpkg, settings); err != nil {
			return cfg, err
		}
	}

	return cfg, nil
}

func mostRecentPoint(entry CfgEntry) CfgPoint {
	if len(entry.History) == 0 {
		panic("invalid cfg entry; len(history) == 0")
	}

	return entry.History[len(entry.History)-1]
}

func calcPriorities(cfg Cfg, settingType CfgSettingType, max int,
	allowDups bool) error {

	// setting-name => entry
	anyEntries := map[string]CfgEntry{}

	// priority-value => entry
	valEntries := map[int]CfgEntry{}

	for name, entry := range cfg.Settings {
		if entry.SettingType == settingType {
			if entry.Value == SYSCFG_PRIO_ANY {
				anyEntries[name] = entry
			} else {
				prio, err := util.AtoiNoOct(entry.Value)
				if err != nil {
					return util.FmtNewtError(
						"invalid priority value: setting=%s value=%s pkg=%s",
						name, entry.Value, entry.History[0].Name())
				}

				if prio > max {
					return util.FmtNewtError(
						"invalid priority value: value too great (> %d); "+
							"setting=%s value=%s pkg=%s",
						max, entry.Name, entry.Value,
						mostRecentPoint(entry).Name())
				}

				if !allowDups {
					if oldEntry, ok := valEntries[prio]; ok {
						return util.FmtNewtError(
							"duplicate priority value: setting1=%s "+
								"setting2=%s pkg1=%s pkg2=%s value=%s",
							oldEntry.Name, entry.Name,
							oldEntry.History[0].Name(),
							entry.History[0].Name(), entry.Value)
					}
				}

				valEntries[prio] = entry
			}
		}
	}

	greatest := 0
	for prio, _ := range valEntries {
		if prio > greatest {
			greatest = prio
		}
	}

	anyNames := make([]string, 0, len(anyEntries))
	for name, _ := range anyEntries {
		anyNames = append(anyNames, name)
	}
	sort.Strings(anyNames)

	for _, name := range anyNames {
		entry := anyEntries[name]

		greatest++
		if greatest > max {
			return util.FmtNewtError("could not assign 'any' priority: "+
				"value too great (> %d); setting=%s value=%s pkg=%s",
				max, name, greatest,
				mostRecentPoint(entry).Name())
		}

		entry.Value = strconv.Itoa(greatest)
		cfg.Settings[name] = entry
	}

	return nil
}

func writeCheckMacros(w io.Writer) {
	s := `/**
 * This macro exists to ensure code includes this header when needed.  If code
 * checks the existence of a setting directly via ifdef without including this
 * header, the setting macro will silently evaluate to 0.  In contrast, an
 * attempt to use these macros without including this header will result in a
 * compiler error.
 */
#define MYNEWT_VAL(_name)                       MYNEWT_VAL_ ## _name
#define MYNEWT_VAL_CHOICE(_name, _val)          MYNEWT_VAL_ ## _name ## __ ## _val
`
	fmt.Fprintf(w, "%s\n", s)
}

func writeComment(entry CfgEntry, w io.Writer) {
	if len(entry.History) > 1 {
		fmt.Fprintf(w, "/* Overridden by %s (defined by %s) */\n",
			mostRecentPoint(entry).Name(),
			entry.History[0].Name())
	}
	if len(entry.ValueRefName) > 1 {
		fmt.Fprintf(w, "/* Value copied from %s */\n",
			entry.ValueRefName)
	}
}

func writeDefine(key string, value string, w io.Writer) {
	if value == "" {
		fmt.Fprintf(w, "#undef %s\n", key)
	} else {
		fmt.Fprintf(w, "#ifndef %s\n", key)
		if strings.HasPrefix(value, "\"") && strings.HasSuffix(value, "\"") {
			fmt.Fprintf(w, "#define %s %s\n", key, value)
		} else {
			fmt.Fprintf(w, "#define %s (%s)\n", key, value)
		}
		fmt.Fprintf(w, "#endif\n")
	}
}

func writeChoiceDefine(key string, value string, choices []string, w io.Writer) {
	parentVal := ""
	value = strings.ToLower(value)

	for _, choice := range choices {
		definedVal := 0
		if value == strings.ToLower(choice) {
			definedVal = 1
			parentVal = "1"
		}
		fmt.Fprintf(w, "#ifndef %s__%s\n", key, choice)
		fmt.Fprintf(w, "#define %s__%s (%d)\n", key, choice, definedVal)
		fmt.Fprintf(w, "#endif\n")
	}

	writeDefine(key, parentVal, w)
}

func EntriesByPkg(cfg Cfg) map[string][]CfgEntry {
	pkgEntries := map[string][]CfgEntry{}
	for _, v := range cfg.Settings {
		name := v.History[0].Name()
		pkgEntries[name] = append(pkgEntries[name], v)
	}
	return pkgEntries
}

func writeSettingsOnePkg(cfg Cfg, pkgName string, pkgEntries []CfgEntry,
	w io.Writer) {

	names := make([]string, len(pkgEntries), len(pkgEntries))
	for i, entry := range pkgEntries {
		names[i] = entry.Name
	}

	if len(names) == 0 {
		return
	}
	sort.Strings(names)

	fmt.Fprintf(w, "/*** %s */\n", pkgName)

	first := true
	for _, n := range names {
		entry := cfg.Settings[n]
		if first {
			first = false
		} else {
			fmt.Fprintf(w, "\n")
		}

		writeComment(entry, w)
		if entry.ValidChoices != nil {
			writeChoiceDefine(settingName(n), entry.Value, entry.ValidChoices, w)
		} else {
			writeDefine(settingName(n), entry.Value, w)
		}
	}
}

func writeSettings(cfg Cfg, w io.Writer) {
	// Group settings by package name so that the generated header file is
	// easier to read.
	pkgEntries := EntriesByPkg(cfg)

	pkgNames := make([]string, 0, len(pkgEntries))
	for name, _ := range pkgEntries {
		pkgNames = append(pkgNames, name)
	}
	sort.Strings(pkgNames)

	for _, name := range pkgNames {
		fmt.Fprintf(w, "\n")
		entries := pkgEntries[name]
		writeSettingsOnePkg(cfg, name, entries, w)
	}
}

func writeReposInfo(w io.Writer) {
	repos := make([]string, 0, len(project.GetProject().Repos()))
	for r, _ := range project.GetProject().Repos() {
		repos = append(repos, r)
	}
	sort.Strings(repos)

	for _, n := range repos {
		r := project.GetProject().Repos()[n]

		if !r.CheckExists() {
			continue
		}

		commitHash, err := r.CurrentHash()
		if err != nil {
			continue
		}

		fmt.Fprintf(w, "/*** Repository @%s info */\n", r.Name())

		k := fmt.Sprintf("%s", settingName("REPO_HASH_"+util.CIdentifier(strings.ToUpper(r.Name()))))
		v := fmt.Sprintf("\"%s", commitHash)

		dirty, err := r.DirtyState()
		if err != nil {
			dirty = "unknown"
		}

		if dirty != "" {
			v += "-dirty"
		}

		v += "\""

		writeDefine(k, v, w)
		fmt.Fprintf(w, "\n")

		ver, err := r.InstalledVersion()
		if err != nil || ver == nil {
			v = "0.0.0"
		} else {
			v = ver.String()
		}

		k = fmt.Sprintf("%s", settingName("REPO_VERSION_"+util.CIdentifier(strings.ToUpper(r.Name()))))
		v = fmt.Sprintf("\"%s\"", v)

		writeDefine(k, v, w)
		fmt.Fprintf(w, "\n")
	}
}

func sanitizePkgName(name string) string {
	name = cfgPkgRepoName.ReplaceAllString(name, "${1}__")
	name = cfgPkgIllegalChar.ReplaceAllLiteralString(name, "_")

	return name
}

func writePackages(lpkgs []*pkg.LocalPackage, w io.Writer) {
	pkgs := []string{}
	for _, lpkg := range lpkgs {
		pkgs = append(pkgs, sanitizePkgName(lpkg.NameWithRepo()))
	}

	sort.Strings(pkgs)

	fmt.Fprintf(w, "/*** Included packages */\n")
	for _, name := range pkgs {
		fmt.Fprintf(w, "#define MYNEWT_PKG_%s 1\n", name)
	}
}

func writeApis(apis []string, w io.Writer) {
	for i, api := range apis {
		apis[i] = cfgPkgIllegalChar.ReplaceAllLiteralString(api, "_")
	}
	sort.Strings(apis)

	fmt.Fprintf(w, "/*** Included APIs */\n")
	for _, name := range apis {
		fmt.Fprintf(w, "#define MYNEWT_API_%s 1\n", name)
	}
}

func write(cfg Cfg, lpkgs []*pkg.LocalPackage, apis []string, w io.Writer) {
	fmt.Fprintf(w, newtutil.GeneratedPreamble())

	fmt.Fprintf(w, "#ifndef H_MYNEWT_SYSCFG_\n")
	fmt.Fprintf(w, "#define H_MYNEWT_SYSCFG_\n\n")

	writeCheckMacros(w)
	fmt.Fprintf(w, "\n")

	if !util.SkipSyscfgRepoHash {
		writeReposInfo(w)
		fmt.Fprintf(w, "\n")
	}

	writeSettings(cfg, w)
	fmt.Fprintf(w, "\n")

	writePackages(lpkgs, w)
	fmt.Fprintf(w, "\n")

	writeApis(apis, w)
	fmt.Fprintf(w, "\n")

	fmt.Fprintf(w, "#endif\n")
}

func EnsureWritten(cfg Cfg, includeDir string, lpkgs []*pkg.LocalPackage, apis []string) error {
	// XXX: Detect these problems at error text generation time.
	if err := calcPriorities(cfg, CFG_SETTING_TYPE_TASK_PRIO,
		SYSCFG_TASK_PRIO_MAX, false); err != nil {

		return err
	}

	buf := bytes.Buffer{}
	write(cfg, lpkgs, apis, &buf)

	path := includeDir + "/" + HEADER_PATH

	writeReqd, err := util.FileContentsChanged(path, buf.Bytes())
	if err != nil {
		return err
	}
	if !writeReqd {
		log.Debugf("syscfg unchanged; not writing header file (%s).", path)
		return nil
	}

	log.Debugf("syscfg changed; writing header file (%s).", path)

	if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
		return util.NewNewtError(err.Error())
	}

	if err := ioutil.WriteFile(path, buf.Bytes(), 0644); err != nil {
		return util.NewNewtError(err.Error())
	}

	return nil
}

func KeyValueFromStr(str string) (map[string]string, error) {
	vals := map[string]string{}

	if strings.TrimSpace(str) == "" {
		return vals, nil
	}

	// Separate syscfg vals are delimited by ':'.
	fields := strings.Split(str, ":")

	// Key-value pairs are delimited by '='.  If no '=' is present, assume the
	// string is the key name and the value is 1.
	for _, f := range fields {
		if _, err := util.AtoiNoOct(f); err == nil {
			return nil, util.FmtNewtError(
				"Invalid setting name \"%s\"; must not be a number", f)
		}

		kv := strings.SplitN(f, "=", 2)
		switch len(kv) {
		case 1:
			vals[f] = "1"
		case 2:
			vals[kv[0]] = kv[1]
		}
	}

	return vals, nil
}

func KeyValueToStr(syscfgKv map[string]string) string {
	str := ""

	names := make([]string, 0, len(syscfgKv))
	for k, _ := range syscfgKv {
		names = append(names, k)
	}
	sort.Strings(names)

	for i, name := range names {
		if i != 0 {
			str += ":"
		}

		str += fmt.Sprintf("%s=%s", name, syscfgKv[name])
	}

	return str
}
