package jsonpointer

// https://tools.ietf.org/html/rfc6901

import (
	"encoding/json"
	"fmt"
	"strconv"
	"strings"
)

type Pointer []string

func NewPointer(path string) (Pointer, error) {
	parts := strings.Split(path, "/")
	for i := range parts {
		parts[i] = strings.Replace(parts[i], "~1", "/", -1)
		parts[i] = strings.Replace(parts[i], "~0", "~", -1)
	}

	if parts[0] != "" {
		return nil, fmt.Errorf("jsonpointers must be the empty string or start with a '/' (got %q)", path)
	}

	return Pointer(parts[1:]), nil
}

func (jp Pointer) String() string {
	var b []byte
	for _, s := range []string(jp) {
		s = strings.Replace(s, "~", "~0", -1)
		s = strings.Replace(s, "/", "~1", -1)
		b = append(b, "/"...)
		b = append(b, s...)
	}
	return string(b)
}

func Get(b []byte, jp Pointer, v interface{}) error {
	// TODO: benchmark, performance

	if len(jp) == 0 {
		return json.Unmarshal(b, v)
	}
	first, rest := jp[0], jp[1:]
	switch {
	case len(b) != 0 && b[0] == '{':
		var object map[string]json.RawMessage
		err := json.Unmarshal(b, &object)
		if err != nil {
			return err
		}
		msg, ok := object[first]
		if !ok {
			return fmt.Errorf("not found")
		}
		return Get([]byte(msg), rest, v)
	case len(b) != 0 && b[0] == '[':
		if first == "-" {
			return fmt.Errorf("can't decode into array element '-'")
		}
		idx, err := strconv.ParseUint(first, 10, 0)
		if err != nil {
			return err
		}
		var array []json.RawMessage
		err = json.Unmarshal(b, &array)
		if err != nil {
			return err
		}
		if idx >= uint64(len(array)) {
			return fmt.Errorf("index out of range")
		}
		msg := array[int(idx)]
		return Get([]byte(msg), rest, v)
	default:
		return fmt.Errorf("not found")
	}
}

type Value struct {
	Location Pointer
	Value    interface{}
}

type Values []Value

func (vs Values) MarshalJSON() ([]byte, error) {
	// TODO: preserve order to the extent possible
	// TODO: make it possible to create arrays?

	if len(vs) == 0 {
		return []byte(`null`), nil
	}

	var zeroes, ones []Value
	longs := make(map[string]Values)
	for _, val := range vs {
		switch len(val.Location) {
		case 0:
			zeroes = append(zeroes, val)
		case 1:
			ones = append(ones, val)
		default:
			prefix := val.Location[0]
			val.Location = val.Location[1:]
			longs[prefix] = append(longs[prefix], val)
		}
	}
	for prefix, val := range longs {
		ones = append(ones, Value{Location: Pointer{prefix}, Value: val})
	}

	switch len(zeroes) {
	case 0:
	case 1:
		if len(ones) != 0 || len(longs) != 0 {
			return nil, fmt.Errorf("value for root object conflicts with all other values")
		}
		return json.Marshal(zeroes[0].Value)
	default:
		return nil, fmt.Errorf("multiple values for root object")
	}

	var buf []byte
	buf = append(buf, "{"...)

	for i, val := range ones {
		if i != 0 {
			buf = append(buf, ","...)
		}
		k, err := json.Marshal(val.Location[0])
		if err != nil {
			return nil, err
		}
		v, err := json.Marshal(val.Value)
		if err != nil {
			return nil, err
		}
		buf = append(buf, k...)
		buf = append(buf, ":"...)
		buf = append(buf, v...)
	}
	buf = append(buf, "}"...)

	return buf, nil
}
