package mapstructure

import (
	"encoding/json"
	"fmt"
)

func ExampleDecode() {
	type Person struct {
		Name   string
		Age    int
		Emails []string
		Extra  map[string]string
	}

	// This input can come from anywhere, but typically comes from
	// something like decoding JSON where we're not quite sure of the
	// struct initially.
	input := map[string]interface{}{
		"name":   "Mitchell",
		"age":    91,
		"emails": []string{"one", "two", "three"},
		"extra": map[string]string{
			"twitter": "mitchellh",
		},
	}

	var result Person
	err := Decode(input, &result)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%#v", result)
	// Output:
	// mapstructure.Person{Name:"Mitchell", Age:91, Emails:[]string{"one", "two", "three"}, Extra:map[string]string{"twitter":"mitchellh"}}
}

func ExampleDecode_errors() {
	type Person struct {
		Name   string
		Age    int
		Emails []string
		Extra  map[string]string
	}

	// This input can come from anywhere, but typically comes from
	// something like decoding JSON where we're not quite sure of the
	// struct initially.
	input := map[string]interface{}{
		"name":   123,
		"age":    "bad value",
		"emails": []int{1, 2, 3},
	}

	var result Person
	err := Decode(input, &result)
	if err == nil {
		panic("should have an error")
	}

	fmt.Println(err.Error())
	// MAPS are Random - Output:
	// 5 error(s) decoding:
	//
	// * 'Name' expected type 'string', got unconvertible type 'int'
	// * 'Age' expected type 'int', got unconvertible type 'string'
	// * 'Emails[0]' expected type 'string', got unconvertible type 'int'
	// * 'Emails[1]' expected type 'string', got unconvertible type 'int'
	// * 'Emails[2]' expected type 'string', got unconvertible type 'int'
}

func ExampleDecode_metadata() {
	type Person struct {
		Name string
		Age  int
	}

	// This input can come from anywhere, but typically comes from
	// something like decoding JSON where we're not quite sure of the
	// struct initially.
	input := map[string]interface{}{
		"name":  "Mitchell",
		"age":   91,
		"email": "foo@bar.com",
	}

	// For metadata, we make a more advanced DecoderConfig so we can
	// more finely configure the decoder that is used. In this case, we
	// just tell the decoder we want to track metadata.
	var md Metadata
	var result Person
	config := &DecoderConfig{
		Metadata: &md,
		Result:   &result,
	}

	decoder, err := NewDecoder(config)
	if err != nil {
		panic(err)
	}

	if err := decoder.Decode(input); err != nil {
		panic(err)
	}

	fmt.Printf("Unused keys: %#v", md.Unused)
	// Output:
	// Unused keys: []string{"email"}
}

func ExampleDecode_weaklyTypedInput() {
	type Person struct {
		Name   string
		Age    int
		Emails []string
	}

	// This input can come from anywhere, but typically comes from
	// something like decoding JSON, generated by a weakly typed language
	// such as PHP.
	input := map[string]interface{}{
		"name":   123,                      // number => string
		"age":    "42",                     // string => number
		"emails": map[string]interface{}{}, // empty map => empty array
	}

	var result Person
	config := &DecoderConfig{
		WeaklyTypedInput: true,
		Result:           &result,
	}

	decoder, err := NewDecoder(config)
	if err != nil {
		panic(err)
	}

	err = decoder.Decode(input)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%#v", result)
	// Output: mapstructure.Person{Name:"123", Age:42, Emails:[]string{}}
}

func ExampleDecodePath() {
	document := `{
    "userContext": {
        "conversationCredentials": {
            "sessionToken": "06142010_1:75bf6a413327dd71ebe8f3f30c5a4210a9b11e93c028d6e11abfca7ff"
        },
        "valid": true,
        "isPasswordExpired": false,
        "cobrandId": 10000004,
        "channelId": -1,
        "locale": "en_US",
        "tncVersion": 2,
        "applicationId": "17CBE222A42161A3FF450E47CF4C1A00",
        "cobrandConversationCredentials": {
            "sessionToken": "06142010_1:b8d011fefbab8bf1753391b074ffedf9578612d676ed2b7f073b5785b"
        },
        "preferenceInfo": {
            "currencyCode": "USD",
            "timeZone": "PST",
            "dateFormat": "MM/dd/yyyy",
            "currencyNotationType": {
                "currencyNotationType": "SYMBOL"
            },
            "numberFormat": {
                "decimalSeparator": ".",
                "groupingSeparator": ",",
                "groupPattern": "###,##0.##"
            }
        }
    },
    "lastLoginTime": 1375686841,
    "loginCount": 299,
    "passwordRecovered": false,
    "emailAddress": "johndoe@email.com",
    "loginName": "sptest1",
    "userId": 10483860,
    "userType":
        {
        "userTypeId": 1,
        "userTypeName": "normal_user"
        }
}`

	type UserType struct {
		UserTypeID   int
		UserTypeName string
	}

	type NumberFormat struct {
		DecimalSeparator  string `jpath:"userContext.preferenceInfo.numberFormat.decimalSeparator"`
		GroupingSeparator string `jpath:"userContext.preferenceInfo.numberFormat.groupingSeparator"`
		GroupPattern      string `jpath:"userContext.preferenceInfo.numberFormat.groupPattern"`
	}

	type User struct {
		Session      string   `jpath:"userContext.cobrandConversationCredentials.sessionToken"`
		CobrandID    int      `jpath:"userContext.cobrandId"`
		UserType     UserType `jpath:"userType"`
		LoginName    string   `jpath:"loginName"`
		NumberFormat          // This can also be a pointer to the struct (*NumberFormat)
	}

	docScript := []byte(document)
	var docMap map[string]interface{}
	json.Unmarshal(docScript, &docMap)

	var user User
	DecodePath(docMap, &user)

	fmt.Printf("%v", user)
	// Output:
	// {06142010_1:b8d011fefbab8bf1753391b074ffedf9578612d676ed2b7f073b5785b 10000004 {1 normal_user} sptest1 {. , ###,##0.##}}
}

func ExampleDecodeSlicePath() {
	var document = `[{"name":"bill"},{"name":"lisa"}]`

	type NameDoc struct {
		Name string `jpath:"name"`
	}

	sliceScript := []byte(document)
	var sliceMap []map[string]interface{}
	json.Unmarshal(sliceScript, &sliceMap)

	var myslice []NameDoc
	DecodeSlicePath(sliceMap, &myslice)

	fmt.Printf("%#v", myslice)
	// Output:
	// []mapstructure.NameDoc{mapstructure.NameDoc{Name:"bill"}, mapstructure.NameDoc{Name:"lisa"}}
}

func ExampleDecode_embeddedSlice() {
	document := `{
	  "cobrandId": 10010352,
	  "channelId": -1,
	  "locale": "en_US",
	  "tncVersion": 2,
	  "categories":["rabbit","bunny","frog"],
	  "people": [
	 	{
			"name": "jack",
			"age": {
				"birth":10,
				"year":2000,
				"animals": [
					{
						"barks":"yes",
						"tail":"yes"
					},
					{
						"barks":"no",
						"tail":"yes"
					}
				]
			}
		},
		{
			"name": "jill",
			"age": {
				"birth":11,
				"year":2001
			}
		}
	  ]
}`

	type Animal struct {
		Barks string `jpath:"barks"`
	}

	type People struct {
		Age     int      `jpath:"age.birth"` // jpath is relative to the array
		Animals []Animal `jpath:"age.animals"`
	}

	type Items struct {
		Categories []string `jpath:"categories"`
		Peoples    []People `jpath:"people"` // Specify the location of the array
	}

	docScript := []byte(document)
	var docMap map[string]interface{}
	json.Unmarshal(docScript, &docMap)

	var items Items
	DecodePath(docMap, &items)

	fmt.Printf("%#v", items)
	// Output:
	// mapstructure.Items{Categories:[]string{"rabbit", "bunny", "frog"}, Peoples:[]mapstructure.People{mapstructure.People{Age:10, Animals:[]mapstructure.Animal{mapstructure.Animal{Barks:"yes"}, mapstructure.Animal{Barks:"no"}}}, mapstructure.People{Age:11, Animals:[]mapstructure.Animal(nil)}}}
}

func ExampleDecode_abstractField() {
	var document = `{"Error":[{"errorDetail":"Invalid Cobrand Credentials"}]}`

	type YodleeError struct {
		Error []map[string]interface{} `jpath:"Error"`
	}

	type CobrandContext struct {
		YodleeError
	}

	docScript := []byte(document)
	var docMap map[string]interface{}
	json.Unmarshal(docScript, &docMap)

	var cobrandContext CobrandContext
	DecodePath(docMap, &cobrandContext)

	fmt.Printf("%#v", cobrandContext)
	// Output:
	// mapstructure.CobrandContext{YodleeError:mapstructure.YodleeError{Error:[]map[string]interface {}{map[string]interface {}{"errorDetail":"Invalid Cobrand Credentials"}}}}
}
