package kubecost

import (
	"bytes"
	"fmt"
	"reflect"
	"time"

	// gojson is default golang json, required for RawMessage decoding
	gojson "encoding/json"

	"github.com/kubecost/cost-model/pkg/util/json"
)

// Encoding and decoding logic for Asset types

// Any marshal and unmarshal

// MarshalJSON implements json.Marshaler
func (a *Any) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncode(buffer, "properties", a.Properties(), ",")
	jsonEncode(buffer, "labels", a.Labels(), ",")
	jsonEncode(buffer, "window", a.Window(), ",")
	jsonEncodeString(buffer, "start", a.Start().Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", a.End().Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", a.Minutes(), ",")
	jsonEncodeFloat64(buffer, "adjustment", a.Adjustment(), ",")
	jsonEncodeFloat64(buffer, "totalCost", a.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (a *Any) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = a.InterfaceToAny(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to Any, carrying over relevant fields
func (a *Any) InterfaceToAny(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	a.properties = &properties
	a.labels = labels
	a.start = start
	a.end = end
	a.window = Window{
		start: &start,
		end:   &end,
	}

	if adjustment, err := getTypedVal(fmap["adjustment"]); err == nil {
		a.adjustment = adjustment.(float64)
	}
	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		a.Cost = Cost.(float64) - a.adjustment
	}

	return nil
}

// Cloud marshal and unmarshal

// MarshalJSON implements json.Marshaler
func (ca *Cloud) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", ca.Type().String(), ",")
	jsonEncode(buffer, "properties", ca.Properties(), ",")
	jsonEncode(buffer, "labels", ca.Labels(), ",")
	jsonEncode(buffer, "window", ca.Window(), ",")
	jsonEncodeString(buffer, "start", ca.Start().Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", ca.End().Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", ca.Minutes(), ",")
	jsonEncodeFloat64(buffer, "adjustment", ca.Adjustment(), ",")
	jsonEncodeFloat64(buffer, "credit", ca.Credit, ",")
	jsonEncodeFloat64(buffer, "totalCost", ca.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (ca *Cloud) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = ca.InterfaceToCloud(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to Cloud, carrying over relevant fields
func (ca *Cloud) InterfaceToCloud(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	ca.properties = &properties
	ca.labels = labels
	ca.start = start
	ca.end = end
	ca.window = Window{
		start: &start,
		end:   &end,
	}

	if adjustment, err := getTypedVal(fmap["adjustment"]); err == nil {
		ca.adjustment = adjustment.(float64)
	}
	if Credit, err := getTypedVal(fmap["credit"]); err == nil {
		ca.Credit = Credit.(float64)
	}
	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		ca.Cost = Cost.(float64) - ca.adjustment - ca.Credit
	}

	return nil
}

// ClusterManagement marshal and unmarshal

// MarshalJSON implements json.Marshler
func (cm *ClusterManagement) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", cm.Type().String(), ",")
	jsonEncode(buffer, "properties", cm.Properties(), ",")
	jsonEncode(buffer, "labels", cm.Labels(), ",")
	jsonEncode(buffer, "window", cm.Window(), ",")
	jsonEncodeString(buffer, "start", cm.Start().Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", cm.End().Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", cm.Minutes(), ",")
	jsonEncodeFloat64(buffer, "totalCost", cm.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (cm *ClusterManagement) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = cm.InterfaceToClusterManagement(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to ClusterManagement, carrying over relevant fields
func (cm *ClusterManagement) InterfaceToClusterManagement(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	cm.properties = &properties
	cm.labels = labels
	cm.window = Window{
		start: &start,
		end:   &end,
	}

	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		cm.Cost = Cost.(float64)
	}

	return nil
}

// Disk marshal and unmarshal

// MarshalJSON implements the json.Marshaler interface
func (d *Disk) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", d.Type().String(), ",")
	jsonEncode(buffer, "properties", d.Properties(), ",")
	jsonEncode(buffer, "labels", d.Labels(), ",")
	jsonEncode(buffer, "window", d.Window(), ",")
	jsonEncodeString(buffer, "start", d.Start().Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", d.End().Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", d.Minutes(), ",")
	jsonEncodeFloat64(buffer, "byteHours", d.ByteHours, ",")
	jsonEncodeFloat64(buffer, "bytes", d.Bytes(), ",")
	jsonEncode(buffer, "breakdown", d.Breakdown, ",")
	jsonEncodeFloat64(buffer, "adjustment", d.Adjustment(), ",")
	jsonEncodeFloat64(buffer, "totalCost", d.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (d *Disk) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = d.InterfaceToDisk(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to Disk, carrying over relevant fields
func (d *Disk) InterfaceToDisk(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	fbreakdown := fmap["breakdown"].(map[string]interface{})

	breakdown := toBreakdown(fbreakdown)

	d.properties = &properties
	d.labels = labels
	d.start = start
	d.end = end
	d.window = Window{
		start: &start,
		end:   &end,
	}
	d.Breakdown = &breakdown

	if adjustment, err := getTypedVal(fmap["adjustment"]); err == nil {
		d.adjustment = adjustment.(float64)
	}
	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		d.Cost = Cost.(float64) - d.adjustment
	}
	if ByteHours, err := getTypedVal(fmap["byteHours"]); err == nil {
		d.ByteHours = ByteHours.(float64)
	}

	// d.Local is not marhsaled, and cannot be calculated from marshaled values.
	// Currently, it is just ignored and not set in the resulting unmarshal to Disk
	//  be aware that this means a resulting Disk from an unmarshal is therefore NOT
	// equal to the originally marshaled Disk.

	return nil

}

// Network marshal and unmarshal

// MarshalJSON implements json.Marshal interface
func (n *Network) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", n.Type().String(), ",")
	jsonEncode(buffer, "properties", n.Properties(), ",")
	jsonEncode(buffer, "labels", n.Labels(), ",")
	jsonEncode(buffer, "window", n.Window(), ",")
	jsonEncodeString(buffer, "start", n.Start().Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", n.End().Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", n.Minutes(), ",")
	jsonEncodeFloat64(buffer, "adjustment", n.Adjustment(), ",")
	jsonEncodeFloat64(buffer, "totalCost", n.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (n *Network) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = n.InterfaceToNetwork(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to Network, carrying over relevant fields
func (n *Network) InterfaceToNetwork(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	n.properties = &properties
	n.labels = labels
	n.start = start
	n.end = end
	n.window = Window{
		start: &start,
		end:   &end,
	}

	if adjustment, err := getTypedVal(fmap["adjustment"]); err == nil {
		n.adjustment = adjustment.(float64)
	}
	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		n.Cost = Cost.(float64) - n.adjustment
	}

	return nil

}

// Node marshal and unmarshal

// MarshalJSON implements json.Marshal interface
func (n *Node) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", n.Type().String(), ",")
	jsonEncode(buffer, "properties", n.Properties(), ",")
	jsonEncode(buffer, "labels", n.Labels(), ",")
	jsonEncode(buffer, "window", n.Window(), ",")
	jsonEncodeString(buffer, "start", n.Start().Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", n.End().Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", n.Minutes(), ",")
	jsonEncodeString(buffer, "nodeType", n.NodeType, ",")
	jsonEncodeFloat64(buffer, "cpuCores", n.CPUCores(), ",")
	jsonEncodeFloat64(buffer, "ramBytes", n.RAMBytes(), ",")
	jsonEncodeFloat64(buffer, "cpuCoreHours", n.CPUCoreHours, ",")
	jsonEncodeFloat64(buffer, "ramByteHours", n.RAMByteHours, ",")
	jsonEncodeFloat64(buffer, "GPUHours", n.GPUHours, ",")
	jsonEncode(buffer, "cpuBreakdown", n.CPUBreakdown, ",")
	jsonEncode(buffer, "ramBreakdown", n.RAMBreakdown, ",")
	jsonEncodeFloat64(buffer, "preemptible", n.Preemptible, ",")
	jsonEncodeFloat64(buffer, "discount", n.Discount, ",")
	jsonEncodeFloat64(buffer, "cpuCost", n.CPUCost, ",")
	jsonEncodeFloat64(buffer, "gpuCost", n.GPUCost, ",")
	jsonEncodeFloat64(buffer, "gpuCount", n.GPUs(), ",")
	jsonEncodeFloat64(buffer, "ramCost", n.RAMCost, ",")
	jsonEncodeFloat64(buffer, "adjustment", n.Adjustment(), ",")
	jsonEncodeFloat64(buffer, "totalCost", n.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (n *Node) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = n.InterfaceToNode(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to Node, carrying over relevant fields
func (n *Node) InterfaceToNode(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	fcpuBreakdown := fmap["cpuBreakdown"].(map[string]interface{})
	framBreakdown := fmap["ramBreakdown"].(map[string]interface{})

	cpuBreakdown := toBreakdown(fcpuBreakdown)
	ramBreakdown := toBreakdown(framBreakdown)

	n.properties = &properties
	n.labels = labels
	n.start = start
	n.end = end
	n.window = Window{
		start: &start,
		end:   &end,
	}
	n.CPUBreakdown = &cpuBreakdown
	n.RAMBreakdown = &ramBreakdown

	if adjustment, err := getTypedVal(fmap["adjustment"]); err == nil {
		n.adjustment = adjustment.(float64)
	}
	if NodeType, err := getTypedVal(fmap["nodeType"]); err == nil {
		n.NodeType = NodeType.(string)
	}
	if CPUCoreHours, err := getTypedVal(fmap["cpuCoreHours"]); err == nil {
		n.CPUCoreHours = CPUCoreHours.(float64)
	}
	if RAMByteHours, err := getTypedVal(fmap["ramByteHours"]); err == nil {
		n.RAMByteHours = RAMByteHours.(float64)
	}
	if GPUHours, err := getTypedVal(fmap["GPUHours"]); err == nil {
		n.GPUHours = GPUHours.(float64)
	}
	if CPUCost, err := getTypedVal(fmap["cpuCost"]); err == nil {
		n.CPUCost = CPUCost.(float64)
	}
	if GPUCost, err := getTypedVal(fmap["gpuCost"]); err == nil {
		n.GPUCost = GPUCost.(float64)
	}
	if GPUCount, err := getTypedVal(fmap["gpuCount"]); err == nil {
		n.GPUCount = GPUCount.(float64)
	}
	if RAMCost, err := getTypedVal(fmap["ramCost"]); err == nil {
		n.RAMCost = RAMCost.(float64)
	}
	if Discount, err := getTypedVal(fmap["discount"]); err == nil {
		n.Discount = Discount.(float64)
	}
	if Preemptible, err := getTypedVal(fmap["preemptible"]); err == nil {
		n.Preemptible = Preemptible.(float64)
	}

	return nil
}

// Loadbalancer marshal and unmarshal

// MarshalJSON implements json.Marshal
func (lb *LoadBalancer) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", lb.Type().String(), ",")
	jsonEncode(buffer, "properties", lb.Properties(), ",")
	jsonEncode(buffer, "labels", lb.Labels(), ",")
	jsonEncode(buffer, "window", lb.Window(), ",")
	jsonEncodeString(buffer, "start", lb.Start().Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", lb.End().Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", lb.Minutes(), ",")
	jsonEncodeFloat64(buffer, "adjustment", lb.Adjustment(), ",")
	jsonEncodeFloat64(buffer, "totalCost", lb.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (lb *LoadBalancer) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = lb.InterfaceToLoadBalancer(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to LoadBalancer, carrying over relevant fields
func (lb *LoadBalancer) InterfaceToLoadBalancer(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	lb.properties = &properties
	lb.labels = labels
	lb.start = start
	lb.end = end
	lb.window = Window{
		start: &start,
		end:   &end,
	}

	if adjustment, err := getTypedVal(fmap["adjustment"]); err == nil {
		lb.adjustment = adjustment.(float64)
	}
	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		lb.Cost = Cost.(float64) - lb.adjustment
	}

	return nil

}

// SharedAsset marshal and unmarshal

// MarshalJSON implements json.Marshaler
func (sa *SharedAsset) MarshalJSON() ([]byte, error) {
	buffer := bytes.NewBufferString("{")
	jsonEncodeString(buffer, "type", sa.Type().String(), ",")
	jsonEncode(buffer, "properties", sa.Properties(), ",")
	jsonEncode(buffer, "labels", sa.Labels(), ",")
	jsonEncode(buffer, "properties", sa.Properties(), ",")
	jsonEncode(buffer, "labels", sa.Labels(), ",")
	jsonEncode(buffer, "window", sa.Window(), ",")
	jsonEncodeString(buffer, "start", sa.Start().Format(time.RFC3339), ",")
	jsonEncodeString(buffer, "end", sa.End().Format(time.RFC3339), ",")
	jsonEncodeFloat64(buffer, "minutes", sa.Minutes(), ",")
	jsonEncodeFloat64(buffer, "totalCost", sa.TotalCost(), "")
	buffer.WriteString("}")
	return buffer.Bytes(), nil
}

func (sa *SharedAsset) UnmarshalJSON(b []byte) error {

	var f interface{}

	err := json.Unmarshal(b, &f)
	if err != nil {
		return err
	}

	err = sa.InterfaceToSharedAsset(f)
	if err != nil {
		return err
	}

	return nil
}

// Converts interface{} to SharedAsset, carrying over relevant fields
func (sa *SharedAsset) InterfaceToSharedAsset(itf interface{}) error {

	fmap := itf.(map[string]interface{})

	// parse properties map to AssetProperties
	fproperties := fmap["properties"].(map[string]interface{})
	properties := toAssetProp(fproperties)

	// parse labels map to AssetLabels
	labels := make(map[string]string)
	for k, v := range fmap["labels"].(map[string]interface{}) {
		labels[k] = v.(string)
	}

	// parse start and end strings to time.Time
	start, err := time.Parse(time.RFC3339, fmap["start"].(string))
	if err != nil {
		return err
	}
	end, err := time.Parse(time.RFC3339, fmap["end"].(string))
	if err != nil {
		return err
	}

	sa.properties = &properties
	sa.labels = labels
	sa.window = Window{
		start: &start,
		end:   &end,
	}

	if Cost, err := getTypedVal(fmap["totalCost"]); err == nil {
		sa.Cost = Cost.(float64)
	}

	return nil

}

// AssetSet marshal

// MarshalJSON JSON-encodes the AssetSet
func (as *AssetSet) MarshalJSON() ([]byte, error) {
	as.RLock()
	defer as.RUnlock()
	return json.Marshal(as.assets)
}

// AssetSetResponse for unmarshaling of AssetSet.assets into AssetSet

// Unmarshals a marshaled AssetSet json into AssetSetResponse
func (asr *AssetSetResponse) UnmarshalJSON(b []byte) error {

	// gojson used here, as jsonitter UnmarshalJSON won't work with RawMessage
	var assetMap map[string]*gojson.RawMessage

	// Partial unmarshal to map of json RawMessage
	err := gojson.Unmarshal(b, &assetMap)
	if err != nil {
		return err
	}

	err = asr.RawMessageToAssetSetResponse(assetMap)
	if err != nil {
		return err
	}

	return nil
}

func (asr *AssetSetResponse) RawMessageToAssetSetResponse(assetMap map[string]*gojson.RawMessage) error {

	newAssetMap := make(map[string]Asset)

	// For each item in asset map, unmarshal to appropriate type
	for key, rawMessage := range assetMap {

		var f interface{}

		err := json.Unmarshal(*rawMessage, &f)
		if err != nil {
			return err
		}

		fmap := f.(map[string]interface{})

		switch t := fmap["type"]; t {
		case "Cloud":

			var ca Cloud
			err := ca.InterfaceToCloud(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &ca

		case "ClusterManagement":

			var cm ClusterManagement
			err := cm.InterfaceToClusterManagement(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &cm

		case "Disk":

			var d Disk
			err := d.InterfaceToDisk(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &d

		case "Network":

			var nw Network
			err := nw.InterfaceToNetwork(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &nw

		case "Node":

			var n Node
			err := n.InterfaceToNode(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &n

		case "LoadBalancer":

			var lb LoadBalancer
			err := lb.InterfaceToLoadBalancer(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &lb

		case "Shared":

			var sa SharedAsset
			err := sa.InterfaceToSharedAsset(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &sa

		default:

			var a Any
			err := a.InterfaceToAny(f)

			if err != nil {
				return err
			}

			newAssetMap[key] = &a

		}
	}

	asr.Assets = newAssetMap

	return nil
}

func (asrr *AssetSetRangeResponse) UnmarshalJSON(b []byte) error {

	// gojson used here, as jsonitter UnmarshalJSON won't work with RawMessage
	var assetMapList []map[string]*gojson.RawMessage

	// Partial unmarshal to map of json RawMessage
	err := gojson.Unmarshal(b, &assetMapList)
	if err != nil {
		return err
	}

	var assetSetList []*AssetSetResponse

	for _, rawm := range assetMapList {

		var asresp AssetSetResponse
		err = asresp.RawMessageToAssetSetResponse(rawm)
		if err != nil {
			return err
		}

		assetSetList = append(assetSetList, &asresp)

	}

	asrr.Assets = assetSetList

	return nil
}

// Extra decoding util functions, for clarity

// Creates an AssetProperties directly from map[string]interface{}
func toAssetProp(fproperties map[string]interface{}) AssetProperties {
	var properties AssetProperties

	if category, v := fproperties["category"].(string); v {
		properties.Category = category
	}
	if provider, v := fproperties["provider"].(string); v {
		properties.Provider = provider
	}
	if account, v := fproperties["account"].(string); v {
		properties.Account = account
	}
	if project, v := fproperties["project"].(string); v {
		properties.Project = project
	}
	if service, v := fproperties["service"].(string); v {
		properties.Service = service
	}
	if cluster, v := fproperties["cluster"].(string); v {
		properties.Cluster = cluster
	}
	if name, v := fproperties["name"].(string); v {
		properties.Name = name
	}
	if providerID, v := fproperties["providerID"].(string); v {
		properties.ProviderID = providerID
	}

	return properties

}

// Creates an Breakdown directly from map[string]interface{}
func toBreakdown(fproperties map[string]interface{}) Breakdown {
	var breakdown Breakdown

	if idle, v := fproperties["idle"].(float64); v {
		breakdown.Idle = idle
	}
	if other, v := fproperties["other"].(float64); v {
		breakdown.Other = other
	}
	if system, v := fproperties["system"].(float64); v {
		breakdown.System = system
	}
	if user, v := fproperties["user"].(float64); v {
		breakdown.User = user
	}

	return breakdown

}

// Not strictly nessesary, but cleans up the code and is a secondary check
// for correct types
func getTypedVal(itf interface{}) (interface{}, error) {
	switch itf := itf.(type) {
	case float64:
		return float64(itf), nil
	case string:
		return string(itf), nil
	default:
		unktype := reflect.ValueOf(itf)
		return nil, fmt.Errorf("Type %v is an invalid type", unktype)
	}
}
