// Use of this source code is governed by a
// license that can be found in the LICENSE file.

// Jason is designed to be convenient for reading arbitrary JSON while still honoring the strictness of the language.
// Inspired by other libraries and improved to work well for common use cases.
// It currently focuses on reading JSON data rather than creating it.
//
// Examples
//
// JSON is a commonly used data transfer format, so usually the data you want to read comes either as bytes or as an io.Reader.
//
// Create an object from bytes:
//		v, err := jason.NewObjectFromBytes(b)
// .. or from a net/http response body:
//		v, err := jason.NewObjectFromReader(res.body)
//
// Read values
//
// Reading values is done with Get<Type>(keys ...) or the generic Get(keys ...).
// If the key path is invalid or the type doesn't match, it will return an error and the default value.
//
//		name, err := v.GetString("name")
//		age, err := v.GetNumber("age")
//		verified, err := v.GetBoolean("verified")
//		education, err := v.GetObject("education")
//		friends, err := v.GetObjectArray("friends")
//
// Loop through array
//
// Getting an array is done by Get<Type>Array() or the generic GetValueArray(). It returns an error if the value at that keypath is null (or something else than the type).
//
//		friends, err := person.GetObjectArray("friends")
//		for _, friend := range friends {
//			name, err := friend.GetString("name")
//			age, err := friend.GetNumber("age")
//		}
//
// Loop through keys of object
//
// Looping through an object is done by first getting it with `GetObject()` and then range on the Map().
// The GetObject() method returns an error if the value at that keypath is null (or something else than an object).
//
//		person, err := person.GetObject("person")
//		for key, value := range person.Map() {
//		  ...
//		}
package jason

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
)

// Error values returned when validation functions fail
var (
	ErrNotNull        = errors.New("is not null")
	ErrNotArray       = errors.New("Not an array")
	ErrNotNumber      = errors.New("not a number")
	ErrNotBool        = errors.New("no bool")
	ErrNotObject      = errors.New("not an object")
	ErrNotObjectArray = errors.New("not an object array")
	ErrNotString      = errors.New("not a string")
)

type KeyNotFoundError struct {
	Key string
}

func (k KeyNotFoundError) Error() string {
	if k.Key != "" {
		return fmt.Sprintf("key '%s' not found", k.Key)
	}

	return "key not found"
}

// Value represents an arbitrary JSON value.
// It may contain a bool, number, string, object, array or null.
type Value struct {
	data   interface{}
	exists bool // Used to separate nil and non-existing values
}

// Object represents an object JSON object.
// It inherets from Value but with an additional method to access
// a map representation of it's content. It's useful when iterating.
type Object struct {
	Value
	m     map[string]*Value
	valid bool
}

// Marshal into bytes.
func (v *Object) MarshalJSON() ([]byte, error) {
	return json.Marshal(v.m)
}

// Returns the golang map.
// Needed when iterating through the values of the object.
func (v *Object) Map() map[string]*Value {
	return v.m
}

// Creates a new value from an io.reader.
// Returns an error if the reader does not contain valid json.
// Useful for parsing the body of a net/http response.
// Example: NewFromReader(res.Body)
func NewValueFromReader(reader io.Reader) (*Value, error) {
	j := new(Value)
	d := json.NewDecoder(reader)
	d.UseNumber()
	err := d.Decode(&j.data)
	return j, err
}

// Creates a new value from bytes.
// Returns an error if the bytes are not valid json.
func NewValueFromBytes(b []byte) (*Value, error) {
	r := bytes.NewReader(b)
	return NewValueFromReader(r)
}

func objectFromValue(v *Value, err error) (*Object, error) {
	if err != nil {
		return nil, err
	}

	o, err := v.Object()

	if err != nil {
		return nil, err
	}

	return o, nil
}

func NewObjectFromBytes(b []byte) (*Object, error) {
	return objectFromValue(NewValueFromBytes(b))
}

func NewObjectFromReader(reader io.Reader) (*Object, error) {
	return objectFromValue(NewValueFromReader(reader))
}

// Marshal into bytes.
func (v *Value) Marshal() ([]byte, error) {
	return json.Marshal(v.data)
}

// Marshal into bytes
func (v *Value) MarshalJSON() ([]byte, error) {
	return v.Marshal()
}

// Get the interyling data as interface
func (v *Value) Interface() interface{} {
	return v.data
}

// Private Get
func (v *Value) get(key string) (*Value, error) {

	// Assume this is an object
	obj, err := v.Object()

	if err == nil {
		child, ok := obj.Map()[key]
		if ok {
			return child, nil
		} else {
			return nil, KeyNotFoundError{key}
		}
	}

	return nil, err
}

// Private get path
func (v *Value) getPath(keys []string) (*Value, error) {
	current := v
	var err error
	for _, key := range keys {
		current, err = current.get(key)

		if err != nil {
			return nil, err
		}
	}
	return current, nil
}

// Gets the value at key path.
// Returns error if the value does not exist.
// Consider using the more specific Get<Type>(..) methods instead.
// Example:
//		value, err := GetValue("address", "street")
func (v *Object) GetValue(keys ...string) (*Value, error) {
	return v.getPath(keys)
}

// Gets the value at key path and attempts to typecast the value into an object.
// Returns error if the value is not a json object.
// Example:
//		object, err := GetObject("person", "address")
func (v *Object) GetObject(keys ...string) (*Object, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return nil, err
	} else {

		obj, err := child.Object()

		if err != nil {
			return nil, err
		} else {
			return obj, nil
		}

	}
}

// Gets the value at key path and attempts to typecast the value into a string.
// Returns error if the value is not a json string.
// Example:
//		string, err := GetString("address", "street")
func (v *Object) GetString(keys ...string) (string, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return "", err
	} else {
		return child.String()
	}
}

// Gets the value at key path and attempts to typecast the value into null.
// Returns error if the value is not json null.
// Example:
//		err := GetNull("address", "street")
func (v *Object) GetNull(keys ...string) error {
	child, err := v.getPath(keys)

	if err != nil {
		return err
	}

	return child.Null()
}

// Gets the value at key path and attempts to typecast the value into a number.
// Returns error if the value is not a json number.
// Example:
//		n, err := GetNumber("address", "street_number")
func (v *Object) GetNumber(keys ...string) (json.Number, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return "", err
	} else {

		n, err := child.Number()

		if err != nil {
			return "", err
		} else {
			return n, nil
		}
	}
}

// Gets the value at key path and attempts to typecast the value into a float64.
// Returns error if the value is not a json number.
// Example:
//		n, err := GetNumber("address", "street_number")
func (v *Object) GetFloat64(keys ...string) (float64, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return 0, err
	} else {

		n, err := child.Float64()

		if err != nil {
			return 0, err
		} else {
			return n, nil
		}
	}
}

// Gets the value at key path and attempts to typecast the value into a float64.
// Returns error if the value is not a json number.
// Example:
//		n, err := GetNumber("address", "street_number")
func (v *Object) GetInt64(keys ...string) (int64, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return 0, err
	} else {

		n, err := child.Int64()

		if err != nil {
			return 0, err
		} else {
			return n, nil
		}
	}
}

// Gets the value at key path and attempts to typecast the value into a float64.
// Returns error if the value is not a json number.
// Example:
//		v, err := GetInterface("address", "anything")
func (v *Object) GetInterface(keys ...string) (interface{}, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return nil, err
	} else {
		return child.Interface(), nil
	}
}

// Gets the value at key path and attempts to typecast the value into a bool.
// Returns error if the value is not a json boolean.
// Example:
//		married, err := GetBoolean("person", "married")
func (v *Object) GetBoolean(keys ...string) (bool, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return false, err
	}

	return child.Boolean()
}

// Gets the value at key path and attempts to typecast the value into an array.
// Returns error if the value is not a json array.
// Consider using the more specific Get<Type>Array() since it may reduce later type casts.
// Example:
//		friends, err := GetValueArray("person", "friends")
//		for i, friend := range friends {
//			... // friend will be of type Value here
//		}
func (v *Object) GetValueArray(keys ...string) ([]*Value, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return nil, err
	} else {

		return child.Array()

	}
}

// Gets the value at key path and attempts to typecast the value into an array of objects.
// Returns error if the value is not a json array or if any of the contained objects are not objects.
// Example:
//		friends, err := GetObjectArray("person", "friends")
//		for i, friend := range friends {
//			... // friend will be of type Object here
//		}
func (v *Object) GetObjectArray(keys ...string) ([]*Object, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return nil, err
	} else {

		array, err := child.Array()

		if err != nil {
			return nil, err
		} else {

			typedArray := make([]*Object, len(array))

			for index, arrayItem := range array {
				typedArrayItem, err := arrayItem.
					Object()

				if err != nil {
					return nil, err
				} else {
					typedArray[index] = typedArrayItem
				}

			}
			return typedArray, nil
		}
	}
}

// Gets the value at key path and attempts to typecast the value into an array of string.
// Returns error if the value is not a json array or if any of the contained objects are not strings.
// Gets the value at key path and attempts to typecast the value into an array of objects.
// Returns error if the value is not a json array or if any of the contained objects are not objects.
// Example:
//		friendNames, err := GetStringArray("person", "friend_names")
//		for i, friendName := range friendNames {
//			... // friendName will be of type string here
//		}
func (v *Object) GetStringArray(keys ...string) ([]string, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return nil, err
	} else {

		array, err := child.Array()

		if err != nil {
			return nil, err
		} else {

			typedArray := make([]string, len(array))

			for index, arrayItem := range array {
				typedArrayItem, err := arrayItem.String()

				if err != nil {
					return nil, err
				} else {
					typedArray[index] = typedArrayItem
				}

			}
			return typedArray, nil
		}
	}
}

// Gets the value at key path and attempts to typecast the value into an array of numbers.
// Returns error if the value is not a json array or if any of the contained objects are not numbers.
// Example:
//		friendAges, err := GetNumberArray("person", "friend_ages")
//		for i, friendAge := range friendAges {
//			... // friendAge will be of type float64 here
//		}
func (v *Object) GetNumberArray(keys ...string) ([]json.Number, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return nil, err
	} else {

		array, err := child.Array()

		if err != nil {
			return nil, err
		} else {

			typedArray := make([]json.Number, len(array))

			for index, arrayItem := range array {
				typedArrayItem, err := arrayItem.Number()

				if err != nil {
					return nil, err
				} else {
					typedArray[index] = typedArrayItem
				}

			}
			return typedArray, nil
		}
	}
}

// Gets the value at key path and attempts to typecast the value into an array of floats.
// Returns error if the value is not a json array or if any of the contained objects are not numbers.
func (v *Object) GetFloat64Array(keys ...string) ([]float64, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return nil, err
	} else {

		array, err := child.Array()

		if err != nil {
			return nil, err
		} else {

			typedArray := make([]float64, len(array))

			for index, arrayItem := range array {
				typedArrayItem, err := arrayItem.Float64()

				if err != nil {
					return nil, err
				} else {
					typedArray[index] = typedArrayItem
				}

			}
			return typedArray, nil
		}
	}
}

// Gets the value at key path and attempts to typecast the value into an array of ints.
// Returns error if the value is not a json array or if any of the contained objects are not numbers.
func (v *Object) GetInt64Array(keys ...string) ([]int64, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return nil, err
	} else {

		array, err := child.Array()

		if err != nil {
			return nil, err
		} else {

			typedArray := make([]int64, len(array))

			for index, arrayItem := range array {
				typedArrayItem, err := arrayItem.Int64()

				if err != nil {
					return nil, err
				} else {
					typedArray[index] = typedArrayItem
				}

			}
			return typedArray, nil
		}
	}
}

// Gets the value at key path and attempts to typecast the value into an array of bools.
// Returns error if the value is not a json array or if any of the contained objects are not booleans.
func (v *Object) GetBooleanArray(keys ...string) ([]bool, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return nil, err
	} else {

		array, err := child.Array()

		if err != nil {
			return nil, err
		} else {

			typedArray := make([]bool, len(array))

			for index, arrayItem := range array {
				typedArrayItem, err := arrayItem.Boolean()

				if err != nil {
					return nil, err
				} else {
					typedArray[index] = typedArrayItem
				}

			}
			return typedArray, nil
		}
	}
}

// Gets the value at key path and attempts to typecast the value into an array of nulls.
// Returns length, or an error if the value is not a json array or if any of the contained objects are not nulls.
func (v *Object) GetNullArray(keys ...string) (int64, error) {
	child, err := v.getPath(keys)

	if err != nil {
		return 0, err
	} else {

		array, err := child.Array()

		if err != nil {
			return 0, err
		} else {

			var length int64 = 0

			for _, arrayItem := range array {
				err := arrayItem.Null()

				if err != nil {
					return 0, err
				} else {
					length++
				}

			}
			return length, nil
		}
	}
}

// Returns an error if the value is not actually null
func (v *Value) Null() error {
	var valid bool

	// Check the type of this data
	switch v.data.(type) {
	case nil:
		valid = v.exists // Valid only if j also exists, since other values could possibly also be nil
		break
	}

	if valid {
		return nil
	}

	return ErrNotNull

}

// Attempts to typecast the current value into an array.
// Returns error if the current value is not a json array.
// Example:
//		friendsArray, err := friendsValue.Array()
func (v *Value) Array() ([]*Value, error) {
	var valid bool

	// Check the type of this data
	switch v.data.(type) {
	case []interface{}:
		valid = true
		break
	}

	// Unsure if this is a good way to use slices, it's probably not
	var slice []*Value

	if valid {

		for _, element := range v.data.([]interface{}) {
			child := Value{element, true}
			slice = append(slice, &child)
		}

		return slice, nil
	}

	return slice, ErrNotArray

}

// Attempts to typecast the current value into a number.
// Returns error if the current value is not a json number.
// Example:
//		ageNumber, err := ageValue.Number()
func (v *Value) Number() (json.Number, error) {
	var valid bool

	// Check the type of this data
	switch v.data.(type) {
	case json.Number:
		valid = true
		break
	}

	if valid {
		return v.data.(json.Number), nil
	}

	return "", ErrNotNumber
}

// Attempts to typecast the current value into a float64.
// Returns error if the current value is not a json number.
// Example:
//		percentage, err := v.Float64()
func (v *Value) Float64() (float64, error) {
	n, err := v.Number()

	if err != nil {
		return 0, err
	}

	return n.Float64()
}

// Attempts to typecast the current value into a int64.
// Returns error if the current value is not a json number.
// Example:
//		id, err := v.Int64()
func (v *Value) Int64() (int64, error) {
	n, err := v.Number()

	if err != nil {
		return 0, err
	}

	return n.Int64()
}

// Attempts to typecast the current value into a bool.
// Returns error if the current value is not a json boolean.
// Example:
//		marriedBool, err := marriedValue.Boolean()
func (v *Value) Boolean() (bool, error) {
	var valid bool

	// Check the type of this data
	switch v.data.(type) {
	case bool:
		valid = true
		break
	}

	if valid {
		return v.data.(bool), nil
	}

	return false, ErrNotBool
}

// Attempts to typecast the current value into an object.
// Returns error if the current value is not a json object.
// Example:
//		friendObject, err := friendValue.Object()
func (v *Value) Object() (*Object, error) {

	var valid bool

	// Check the type of this data
	switch v.data.(type) {
	case map[string]interface{}:
		valid = true
		break
	}

	if valid {
		obj := new(Object)
		obj.valid = valid

		m := make(map[string]*Value)

		if valid {
			for key, element := range v.data.(map[string]interface{}) {
				m[key] = &Value{element, true}

			}
		}

		obj.data = v.data
		obj.m = m

		return obj, nil
	}

	return nil, ErrNotObject
}

// Attempts to typecast the current value into an object arrau.
// Returns error if the current value is not an array of json objects
// Example:
//		friendObjects, err := friendValues.ObjectArray()
func (v *Value) ObjectArray() ([]*Object, error) {

	var valid bool

	// Check the type of this data
	switch v.data.(type) {
	case []interface{}:
		valid = true
		break
	}

	// Unsure if this is a good way to use slices, it's probably not
	var slice []*Object

	if valid {

		for _, element := range v.data.([]interface{}) {
			childValue := Value{element, true}
			childObject, err := childValue.Object()

			if err != nil {
				return nil, ErrNotObjectArray
			}
			slice = append(slice, childObject)
		}

		return slice, nil
	}

	return nil, ErrNotObjectArray

}

// Attempts to typecast the current value into a string.
// Returns error if the current value is not a json string
// Example:
//		nameObject, err := nameValue.String()
func (v *Value) String() (string, error) {
	var valid bool

	// Check the type of this data
	switch v.data.(type) {
	case string:
		valid = true
		break
	}

	if valid {
		return v.data.(string), nil
	}

	return "", ErrNotString
}

// Returns the value a json formatted string.
// Note: The method named String() is used by golang's log method for logging.
// Example:
func (v *Object) String() string {

	f, err := json.Marshal(v.data)
	if err != nil {
		return err.Error()
	}

	return string(f)

}
