package p9p

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"reflect"
	"strings"
	"time"
)

// Codec defines the interface for encoding and decoding of 9p types.
// Unsupported types will throw an error.
type Codec interface {
	// Unmarshal from data into the value pointed to by v.
	Unmarshal(data []byte, v interface{}) error

	// Marshal the value v into a byte slice.
	Marshal(v interface{}) ([]byte, error)

	// Size returns the encoded size for the target of v.
	Size(v interface{}) int
}

// NewCodec returns a new, standard 9P2000 codec, ready for use.
func NewCodec() Codec {
	return codec9p{}
}

type codec9p struct{}

func (c codec9p) Unmarshal(data []byte, v interface{}) error {
	dec := &decoder{bytes.NewReader(data)}
	return dec.decode(v)
}

func (c codec9p) Marshal(v interface{}) ([]byte, error) {
	var b bytes.Buffer
	enc := &encoder{&b}

	if err := enc.encode(v); err != nil {
		return nil, err
	}

	return b.Bytes(), nil
}

func (c codec9p) Size(v interface{}) int {
	return int(size9p(v))
}

// DecodeDir decodes a directory entry from rd using the provided codec.
func DecodeDir(codec Codec, rd io.Reader, d *Dir) error {
	var ll uint16

	// pull the size off the wire
	if err := binary.Read(rd, binary.LittleEndian, &ll); err != nil {
		return err
	}

	p := make([]byte, ll+2)
	binary.LittleEndian.PutUint16(p, ll) // must have size at start

	// read out the rest of the record
	if _, err := io.ReadFull(rd, p[2:]); err != nil {
		return err
	}

	return codec.Unmarshal(p, d)
}

// EncodeDir writes the directory to wr.
func EncodeDir(codec Codec, wr io.Writer, d *Dir) error {
	p, err := codec.Marshal(d)
	if err != nil {
		return err
	}

	_, err = wr.Write(p)
	return err
}

type encoder struct {
	wr io.Writer
}

func (e *encoder) encode(vs ...interface{}) error {
	for _, v := range vs {
		switch v := v.(type) {
		case uint8, uint16, uint32, uint64, FcallType, Tag, QType, Fid, Flag,
			*uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid, *Flag:
			if err := binary.Write(e.wr, binary.LittleEndian, v); err != nil {
				return err
			}
		case []byte:
			if err := e.encode(uint32(len(v))); err != nil {
				return err
			}

			if err := binary.Write(e.wr, binary.LittleEndian, v); err != nil {
				return err
			}

		case *[]byte:
			if err := e.encode(*v); err != nil {
				return err
			}
		case string:
			if err := binary.Write(e.wr, binary.LittleEndian, uint16(len(v))); err != nil {
				return err
			}

			_, err := io.WriteString(e.wr, v)
			if err != nil {
				return err
			}
		case *string:
			if err := e.encode(*v); err != nil {
				return err
			}

		case []string:
			if err := e.encode(uint16(len(v))); err != nil {
				return err
			}

			for _, m := range v {
				if err := e.encode(m); err != nil {
					return err
				}
			}
		case *[]string:
			if err := e.encode(*v); err != nil {
				return err
			}
		case time.Time:
			if err := e.encode(uint32(v.Unix())); err != nil {
				return err
			}
		case *time.Time:
			if err := e.encode(*v); err != nil {
				return err
			}
		case Qid:
			if err := e.encode(v.Type, v.Version, v.Path); err != nil {
				return err
			}
		case *Qid:
			if err := e.encode(*v); err != nil {
				return err
			}
		case []Qid:
			if err := e.encode(uint16(len(v))); err != nil {
				return err
			}

			elements := make([]interface{}, len(v))
			for i := range v {
				elements[i] = &v[i]
			}

			if err := e.encode(elements...); err != nil {
				return err
			}
		case *[]Qid:
			if err := e.encode(*v); err != nil {
				return err
			}
		case Dir:
			elements, err := fields9p(v)
			if err != nil {
				return err
			}

			if err := e.encode(uint16(size9p(elements...))); err != nil {
				return err
			}

			if err := e.encode(elements...); err != nil {
				return err
			}
		case *Dir:
			if err := e.encode(*v); err != nil {
				return err
			}
		case []Dir:
			elements := make([]interface{}, len(v))
			for i := range v {
				elements[i] = &v[i]
			}

			if err := e.encode(elements...); err != nil {
				return err
			}
		case *[]Dir:
			if err := e.encode(*v); err != nil {
				return err
			}
		case Fcall:
			if err := e.encode(v.Type, v.Tag, v.Message); err != nil {
				return err
			}
		case *Fcall:
			if err := e.encode(*v); err != nil {
				return err
			}
		case Message:
			elements, err := fields9p(v)
			if err != nil {
				return err
			}

			switch v.(type) {
			case MessageRstat, *MessageRstat:
				// NOTE(stevvooe): Prepend size preceeding Dir. See bugs in
				// http://man.cat-v.org/plan_9/5/stat to make sense of this.
				// The field has been included here but we need to make sure
				// to double emit it for Rstat.
				if err := e.encode(uint16(size9p(elements...))); err != nil {
					return err
				}
			}

			if err := e.encode(elements...); err != nil {
				return err
			}
		}
	}

	return nil
}

type decoder struct {
	rd io.Reader
}

// read9p extracts values from rd and unmarshals them to the targets of vs.
func (d *decoder) decode(vs ...interface{}) error {
	for _, v := range vs {
		switch v := v.(type) {
		case *uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid, *Flag:
			if err := binary.Read(d.rd, binary.LittleEndian, v); err != nil {
				return err
			}
		case *[]byte:
			var ll uint32

			if err := d.decode(&ll); err != nil {
				return err
			}

			if ll > 0 {
				*v = make([]byte, int(ll))
			}

			if err := binary.Read(d.rd, binary.LittleEndian, v); err != nil {
				return err
			}
		case *string:
			var ll uint16

			// implement string[s] encoding
			if err := d.decode(&ll); err != nil {
				return err
			}

			b := make([]byte, ll)

			n, err := io.ReadFull(d.rd, b)
			if err != nil {
				return err
			}

			if n != int(ll) {
				return fmt.Errorf("unexpected string length")
			}

			*v = string(b)
		case *[]string:
			var ll uint16

			if err := d.decode(&ll); err != nil {
				return err
			}

			elements := make([]interface{}, int(ll))
			*v = make([]string, int(ll))
			for i := range elements {
				elements[i] = &(*v)[i]
			}

			if err := d.decode(elements...); err != nil {
				return err
			}
		case *time.Time:
			var epoch uint32
			if err := d.decode(&epoch); err != nil {
				return err
			}

			*v = time.Unix(int64(epoch), 0).UTC()
		case *Qid:
			if err := d.decode(&v.Type, &v.Version, &v.Path); err != nil {
				return err
			}
		case *[]Qid:
			var ll uint16

			if err := d.decode(&ll); err != nil {
				return err
			}

			elements := make([]interface{}, int(ll))
			*v = make([]Qid, int(ll))
			for i := range elements {
				elements[i] = &(*v)[i]
			}

			if err := d.decode(elements...); err != nil {
				return err
			}
		case *Dir:
			var ll uint16

			if err := d.decode(&ll); err != nil {
				return err
			}

			b := make([]byte, ll)
			// must consume entire dir entry.
			if _, err := io.ReadFull(d.rd, b); err != nil {
				return err
			}

			elements, err := fields9p(v)
			if err != nil {
				return err
			}

			dec := &decoder{bytes.NewReader(b)}

			if err := dec.decode(elements...); err != nil {
				return err
			}
		case *[]Dir:
			*v = make([]Dir, 0)
			for {
				element := Dir{}
				if err := d.decode(&element); err != nil {
					if err == io.EOF {
						return nil
					}
					return err
				}
				*v = append(*v, element)
			}
		case *Fcall:
			if err := d.decode(&v.Type, &v.Tag); err != nil {
				return err
			}

			message, err := newMessage(v.Type)
			if err != nil {
				return err
			}

			// NOTE(stevvooe): We do a little pointer dance to allocate the
			// new type, write to it, then assign it back to the interface as
			// a concrete type, avoiding a pointer (the interface) to a
			// pointer.
			rv := reflect.New(reflect.TypeOf(message))
			if err := d.decode(rv.Interface()); err != nil {
				return err
			}

			v.Message = rv.Elem().Interface().(Message)
		case Message:
			elements, err := fields9p(v)
			if err != nil {
				return err
			}

			switch v.(type) {
			case *MessageRstat, MessageRstat:
				// NOTE(stevvooe): Consume extra size preceeding Dir. See bugs
				// in http://man.cat-v.org/plan_9/5/stat to make sense of
				// this. The field has been included here but we need to make
				// sure to double emit it for Rstat. decode extra size header
				// for stat structure.
				var ll uint16
				if err := d.decode(&ll); err != nil {
					return err
				}
			}

			if err := d.decode(elements...); err != nil {
				return err
			}
		}
	}

	return nil
}

// size9p calculates the projected size of the values in vs when encoded into
// 9p binary protocol. If an element or elements are not valid for 9p encoded,
// the value 0 will be used for the size. The error will be detected when
// encoding.
func size9p(vs ...interface{}) uint32 {
	var s uint32
	for _, v := range vs {
		if v == nil {
			continue
		}

		switch v := v.(type) {
		case uint8, uint16, uint32, uint64, FcallType, Tag, QType, Fid, Flag,
			*uint8, *uint16, *uint32, *uint64, *FcallType, *Tag, *QType, *Fid, *Flag:
			s += uint32(binary.Size(v))
		case []byte:
			s += uint32(binary.Size(uint32(0)) + len(v))
		case *[]byte:
			s += size9p(uint32(0), *v)
		case string:
			s += uint32(binary.Size(uint16(0)) + len(v))
		case *string:
			s += size9p(*v)
		case []string:
			s += size9p(uint16(0))

			for _, sv := range v {
				s += size9p(sv)
			}
		case *[]string:
			s += size9p(*v)
		case time.Time, *time.Time:
			// BUG(stevvooe): Y2038 is coming.
			s += size9p(uint32(0))
		case Qid:
			s += size9p(v.Type, v.Version, v.Path)
		case *Qid:
			s += size9p(*v)
		case []Qid:
			s += size9p(uint16(0))
			elements := make([]interface{}, len(v))
			for i := range elements {
				elements[i] = &v[i]
			}
			s += size9p(elements...)
		case *[]Qid:
			s += size9p(*v)

		case Dir:
			// walk the fields of the message to get the total size. we just
			// use the field order from the message struct. We may add tag
			// ignoring if needed.
			elements, err := fields9p(v)
			if err != nil {
				// BUG(stevvooe): The options here are to return 0, panic or
				// make this return an error. Ideally, we make it safe to
				// return 0 and have the rest of the package do the right
				// thing. For now, we do this, but may want to panic until
				// things are stable.
				panic(err)
			}

			s += size9p(elements...) + size9p(uint16(0))
		case *Dir:
			s += size9p(*v)
		case []Dir:
			elements := make([]interface{}, len(v))
			for i := range elements {
				elements[i] = &v[i]
			}
			s += size9p(elements...)
		case *[]Dir:
			s += size9p(*v)
		case Fcall:
			s += size9p(v.Type, v.Tag, v.Message)
		case *Fcall:
			s += size9p(*v)
		case Message:
			// special case twstat and rstat for size fields. See bugs in
			// http://man.cat-v.org/plan_9/5/stat to make sense of this.
			switch v.(type) {
			case *MessageRstat, MessageRstat:
				s += size9p(uint16(0)) // for extra size field before dir
			}

			// walk the fields of the message to get the total size. we just
			// use the field order from the message struct. We may add tag
			// ignoring if needed.
			elements, err := fields9p(v)
			if err != nil {
				// BUG(stevvooe): The options here are to return 0, panic or
				// make this return an error. Ideally, we make it safe to
				// return 0 and have the rest of the package do the right
				// thing. For now, we do this, but may want to panic until
				// things are stable.
				panic(err)
			}

			s += size9p(elements...)
		}
	}

	return s
}

// fields9p lists the settable fields from a struct type for reading and
// writing. We are using a lot of reflection here for fairly static
// serialization but we can replace this in the future with generated code if
// performance is an issue.
func fields9p(v interface{}) ([]interface{}, error) {
	rv := reflect.Indirect(reflect.ValueOf(v))

	if rv.Kind() != reflect.Struct {
		return nil, fmt.Errorf("cannot extract fields from non-struct: %v", rv)
	}

	var elements []interface{}
	for i := 0; i < rv.NumField(); i++ {
		f := rv.Field(i)

		if !f.CanInterface() {
			// unexported field, skip it.
			continue
		}

		if f.CanAddr() {
			f = f.Addr()
		}

		elements = append(elements, f.Interface())
	}

	return elements, nil
}

func string9p(v interface{}) string {
	if v == nil {
		return "nil"
	}

	rv := reflect.Indirect(reflect.ValueOf(v))

	if rv.Kind() != reflect.Struct {
		panic("not a struct")
	}

	var s string

	for i := 0; i < rv.NumField(); i++ {
		f := rv.Field(i)

		s += fmt.Sprintf(" %v=%v", strings.ToLower(rv.Type().Field(i).Name), f.Interface())
	}

	return s
}
