// Copyright 2012-2018 Charles Banning. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file

//	x2j_valuesFrom.go: Extract values from an arbitrary XML doc. Tag path can include wildcard characters.

package x2j

import (
	"strings"

	"github.com/clbanning/mxj"
)

// ------------------- sweep up everything for some point in the node tree ---------------------

// ValuesFromTagPath - deliver all values for a path node from a XML doc
// If there are no values for the path 'nil' is returned.
// A return value of (nil, nil) means that there were no values and no errors parsing the doc.
//   'doc' is the XML document
//   'path' is a dot-separated path of tag nodes
//   'getAttrs' can be set 'true' to return attribute values for "*"-terminated path
//          If a node is '*', then everything beyond is scanned for values.
//          E.g., "doc.books' might return a single value 'book' of type []interface{}, but
//                "doc.books.*" could return all the 'book' entries as []map[string]interface{}.
//                "doc.books.*.author" might return all the 'author' tag values as []string - or
//            		"doc.books.*.author.lastname" might be required, depending on he schema.
func ValuesFromTagPath(doc, path string, getAttrs ...bool) ([]interface{}, error) {
	var a bool
	if len(getAttrs) == 1 {
		a = getAttrs[0]
	}
	m, err := mxj.NewMapXml([]byte(doc))
	if err != nil {
		return nil, err
	}

	v := ValuesFromKeyPath(m, path, a)
	return v, nil
}

// ValuesFromKeyPath - deliver all values for a path node from a map[string]interface{}
// If there are no values for the path 'nil' is returned.
//   'm' is the map to be walked
//   'path' is a dot-separated path of key values
//   'getAttrs' can be set 'true' to return attribute values for "*"-terminated path
//          If a node is '*', then everything beyond is walked.
//          E.g., see ValuesFromTagPath documentation.
func ValuesFromKeyPath(m map[string]interface{}, path string, getAttrs ...bool) []interface{} {
	var a bool
	if len(getAttrs) == 1 {
		a = getAttrs[0]
	}
	keys := strings.Split(path, ".")
	ret := make([]interface{}, 0)
	valuesFromKeyPath(&ret, m, keys, a)
	if len(ret) == 0 {
		return nil
	}
	return ret
}

func valuesFromKeyPath(ret *[]interface{}, m interface{}, keys []string, getAttrs bool) {
	lenKeys := len(keys)

	// load 'm' values into 'ret'
	// expand any lists
	if lenKeys == 0 {
		switch m.(type) {
		case map[string]interface{}:
			*ret = append(*ret, m)
		case []interface{}:
			for _, v := range m.([]interface{}) {
				*ret = append(*ret, v)
			}
		default:
			*ret = append(*ret, m)
		}
		return
	}

	// key of interest
	key := keys[0]
	switch key {
	case "*": // wildcard - scan all values
		switch m.(type) {
		case map[string]interface{}:
			for k, v := range m.(map[string]interface{}) {
				if string(k[:1]) == "-" && !getAttrs { // skip attributes?
					continue
				}
				valuesFromKeyPath(ret, v, keys[1:], getAttrs)
			}
		case []interface{}:
			for _, v := range m.([]interface{}) {
				switch v.(type) {
				// flatten out a list of maps - keys are processed
				case map[string]interface{}:
					for kk, vv := range v.(map[string]interface{}) {
						if string(kk[:1]) == "-" && !getAttrs { // skip attributes?
							continue
						}
						valuesFromKeyPath(ret, vv, keys[1:], getAttrs)
					}
				default:
					valuesFromKeyPath(ret, v, keys[1:], getAttrs)
				}
			}
		}
	default: // key - must be map[string]interface{}
		switch m.(type) {
		case map[string]interface{}:
			if v, ok := m.(map[string]interface{})[key]; ok {
				valuesFromKeyPath(ret, v, keys[1:], getAttrs)
			}
		case []interface{}: // may be buried in list
			for _, v := range m.([]interface{}) {
				switch v.(type) {
				case map[string]interface{}:
					if vv, ok := v.(map[string]interface{})[key]; ok {
						valuesFromKeyPath(ret, vv, keys[1:], getAttrs)
					}
				}
			}
		}
	}
}
