// Copyright 2009 The Go9p Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package go9p

import (
	"fmt"
)

// Creates a Fcall value from the on-the-wire representation. If
// dotu is true, reads 9P2000.u messages. Returns the unpacked message,
// error and how many bytes from the buffer were used by the message.
func Unpack(buf []byte, dotu bool) (fc *Fcall, err error, fcsz int) {
	var m uint16

	if len(buf) < 7 {
		return nil, &Error{"buffer too short", EINVAL}, 0
	}

	fc = new(Fcall)
	fc.Fid = NOFID
	fc.Afid = NOFID
	fc.Newfid = NOFID

	p := buf
	fc.Size, p = gint32(p)
	fc.Type, p = gint8(p)
	fc.Tag, p = gint16(p)

	if int(fc.Size) > len(buf) || fc.Size < 7 {
		return nil, &Error{fmt.Sprintf("buffer too short: %d expected %d",
				len(buf), fc.Size),
				EINVAL},
			0
	}

	p = p[0 : fc.Size-7]
	fc.Pkt = buf[0:fc.Size]
	fcsz = int(fc.Size)
	if fc.Type < Tversion || fc.Type >= Tlast {
		return nil, &Error{"invalid id", EINVAL}, 0
	}

	var sz uint32
	if dotu {
		sz = minFcsize[fc.Type-Tversion]
	} else {
		sz = minFcusize[fc.Type-Tversion]
	}

	if fc.Size < sz {
		goto szerror
	}

	err = nil
	switch fc.Type {
	default:
		return nil, &Error{"invalid message id", EINVAL}, 0

	case Tversion, Rversion:
		fc.Msize, p = gint32(p)
		fc.Version, p = gstr(p)
		if p == nil {
			goto szerror
		}

	case Tauth:
		fc.Afid, p = gint32(p)
		fc.Uname, p = gstr(p)
		if p == nil {
			goto szerror
		}

		fc.Aname, p = gstr(p)
		if p == nil {
			goto szerror
		}

		if dotu {
			if len(p) > 0 {
				fc.Unamenum, p = gint32(p)
			} else {
				fc.Unamenum = NOUID
			}
		} else {
			fc.Unamenum = NOUID
		}

	case Rauth, Rattach:
		p = gqid(p, &fc.Qid)

	case Tflush:
		fc.Oldtag, p = gint16(p)

	case Tattach:
		fc.Fid, p = gint32(p)
		fc.Afid, p = gint32(p)
		fc.Uname, p = gstr(p)
		if p == nil {
			goto szerror
		}

		fc.Aname, p = gstr(p)
		if p == nil {
			goto szerror
		}

		if dotu {
			if len(p) > 0 {
				fc.Unamenum, p = gint32(p)
			} else {
				fc.Unamenum = NOUID
			}
		}

	case Rerror:
		fc.Error, p = gstr(p)
		if p == nil {
			goto szerror
		}
		if dotu {
			fc.Errornum, p = gint32(p)
		} else {
			fc.Errornum = 0
		}

	case Twalk:
		fc.Fid, p = gint32(p)
		fc.Newfid, p = gint32(p)
		m, p = gint16(p)
		fc.Wname = make([]string, m)
		for i := 0; i < int(m); i++ {
			fc.Wname[i], p = gstr(p)
			if p == nil {
				goto szerror
			}
		}

	case Rwalk:
		m, p = gint16(p)
		fc.Wqid = make([]Qid, m)
		for i := 0; i < int(m); i++ {
			p = gqid(p, &fc.Wqid[i])
		}

	case Topen:
		fc.Fid, p = gint32(p)
		fc.Mode, p = gint8(p)

	case Ropen, Rcreate:
		p = gqid(p, &fc.Qid)
		fc.Iounit, p = gint32(p)

	case Tcreate:
		fc.Fid, p = gint32(p)
		fc.Name, p = gstr(p)
		if p == nil {
			goto szerror
		}
		fc.Perm, p = gint32(p)
		fc.Mode, p = gint8(p)
		if dotu {
			fc.Ext, p = gstr(p)
			if p == nil {
				goto szerror
			}
		}

	case Tread:
		fc.Fid, p = gint32(p)
		fc.Offset, p = gint64(p)
		fc.Count, p = gint32(p)

	case Rread:
		fc.Count, p = gint32(p)
		if len(p) < int(fc.Count) {
			goto szerror
		}
		fc.Data = p
		p = p[fc.Count:]

	case Twrite:
		fc.Fid, p = gint32(p)
		fc.Offset, p = gint64(p)
		fc.Count, p = gint32(p)
		if len(p) != int(fc.Count) {
			fc.Data = make([]byte, fc.Count)
			copy(fc.Data, p)
			p = p[len(p):]
		} else {
			fc.Data = p
			p = p[fc.Count:]
		}

	case Rwrite:
		fc.Count, p = gint32(p)

	case Tclunk, Tremove, Tstat:
		fc.Fid, p = gint32(p)

	case Rstat:
		m, p = gint16(p)
		p, err = gstat(p, &fc.Dir, dotu)
		if err != nil {
			return nil, err, 0
		}

	case Twstat:
		fc.Fid, p = gint32(p)
		m, p = gint16(p)
		p, _ = gstat(p, &fc.Dir, dotu)

	case Rflush, Rclunk, Rremove, Rwstat:
	}

	if len(p) > 0 {
		goto szerror
	}

	return

szerror:
	return nil, &Error{"invalid size", EINVAL}, 0
}
