func (c *Client) PlaylistMove(name string, id, pos int) error {
	return c.Command("playlistmove %s %d %d", name, id, pos).OK()
}
PlaylistMove moves a song identified by id in a playlist identified by name
to the position pos.
package mpd
import (
	"errors"
	"fmt"
	"io"
	"net/textproto"
	"strconv"
	"strings"
	"time"
)
func quote(s string) string {
	q := make([]byte, 2+2*len(s))
	i := 0
	q[i], i = '"', i+1
	for _, c := range []byte(s) {
		if c == '"' || c == '\\' || c == '\'' {
			q[i] = '\\'
			i++
		}
		q[i] = c
		i++
	}
	q[i], i = '"', i+1
	return string(q[:i])
}
func quoteArgs(args []string) string {
	quoted := make([]string, len(args))
	for index, arg := range args {
		quoted[index] = quote(arg)
	}
	return strings.Join(quoted, " ")
}
type Client struct {
	text    *textproto.Conn
	version string
}
type Error struct {
	Code             ErrorCode
	CommandListIndex int
	CommandName      string
	Message          string
}
type ErrorCode int
const (
	ErrorNotList       ErrorCode = 1
	ErrorArg           ErrorCode = 2
	ErrorPassword      ErrorCode = 3
	ErrorPermission    ErrorCode = 4
	ErrorUnknown       ErrorCode = 5
	ErrorNoExist       ErrorCode = 50
	ErrorPlaylistMax   ErrorCode = 51
	ErrorSystem        ErrorCode = 52
	ErrorPlaylistLoad  ErrorCode = 53
	ErrorUpdateAlready ErrorCode = 54
	ErrorPlayerSync    ErrorCode = 55
	ErrorExist         ErrorCode = 56
)
func (e Error) Error() string {
	if e.CommandName != "" {
		return fmt.Sprintf("command '%s' failed: %s", e.CommandName, e.Message)
	}
	return e.Message
}
type Attrs map[string]string
func Dial(network, addr string) (c *Client, err error) {
	text, err := textproto.Dial(network, addr)
	if err != nil {
		return nil, err
	}
	line, err := text.ReadLine()
	if err != nil {
		return nil, err
	}
	if line[0:6] != "OK MPD" {
		return nil, textproto.ProtocolError("no greeting")
	}
	return &Client{text: text, version: line[7:]}, nil
}
func DialAuthenticated(network, addr, password string) (c *Client, err error) {
	c, err = Dial(network, addr)
	if err == nil && len(password) > 0 {
		err = c.Command("password %s", password).OK()
	}
	return c, err
}
func (c *Client) Version() string {
	return c.version
}
func (c *Client) cmd(format string, args ...interface{}) (uint, error) {
	id := c.text.Next()
	c.text.StartRequest(id)
	defer c.text.EndRequest(id)
	if err := c.printfLine(format, args...); err != nil {
		return 0, err
	}
	return id, nil
}
func (c *Client) printfLine(format string, args ...interface{}) error {
	fmt.Fprintf(c.text.W, format, args...)
	c.text.W.WriteByte('\n')
	return c.text.W.Flush()
}
func (c *Client) Close() (err error) {
	if c.text != nil {
		c.printfLine("close")
		err = c.text.Close()
		c.text = nil
	}
	return
}
func (c *Client) Ping() error {
	return c.Command("ping").OK()
}
func (c *Client) readList(key string) (list []string, err error) {
	list = []string{}
	key += ": "
	for {
		line, err := c.readLine()
		if err != nil {
			return nil, err
		}
		if line == "OK" {
			break
		}
		if !strings.HasPrefix(line, key) {
			return nil, textproto.ProtocolError("unexpected: " + line)
		}
		list = append(list, line[len(key):])
	}
	return
}
func (c *Client) readLine() (string, error) {
	line, err := c.text.ReadLine()
	if err != nil {
		return "", err
	}
	if strings.HasPrefix(line, "ACK ") {
		cur := line[4:]
		var code, idx int
		if strings.HasPrefix(cur, "[") {
			sep := strings.Index(cur, "@")
			end := strings.Index(cur, "] ")
			if sep > 0 && end > 0 {
				code, err = strconv.Atoi(cur[1:sep])
				if err != nil {
					return "", err
				}
				idx, err = strconv.Atoi(cur[sep+1 : end])
				if err != nil {
					return "", err
				}
				cur = cur[end+2:]
			}
		}
		var cmd string
		if strings.HasPrefix(cur, "{") {
			if end := strings.Index(cur, "} "); end > 0 {
				cmd = cur[1:end]
				cur = cur[end+2:]
			}
		}
		msg := strings.TrimSpace(cur)
		return "", Error{
			Code:             ErrorCode(code),
			CommandListIndex: idx,
			CommandName:      cmd,
			Message:          msg,
		}
	}
	return line, nil
}
func (c *Client) readBytes(length int) ([]byte, error) {
	data := make([]byte, length)
	if _, err := io.ReadFull(c.text.R, data); err != nil {
		return nil, err
	}
	termByte, err := c.text.R.ReadByte()
	if err != nil {
		return nil, textproto.ProtocolError("failed to read binary data terminator: " + err.Error())
	}
	if termByte != '\n' {
		return nil, textproto.ProtocolError(fmt.Sprintf("wrong binary data terminator: want 0x0a, got %x", termByte))
	}
	return data, nil
}
func (c *Client) readAttrsList(startKey string) (attrs []Attrs, err error) {
	attrs = []Attrs{}
	startKey += ": "
	for {
		line, err := c.readLine()
		if err != nil {
			return nil, err
		}
		if line == "OK" {
			break
		}
		if strings.HasPrefix(line, startKey) {  
			attrs = append(attrs, Attrs{})
		}
		if len(attrs) == 0 {
			return nil, textproto.ProtocolError("unexpected: " + line)
		}
		i := strings.Index(line, ": ")
		if i < 0 {
			return nil, textproto.ProtocolError("can't parse line: " + line)
		}
		attrs[len(attrs)-1][line[0:i]] = line[i+2:]
	}
	return attrs, nil
}
func (c *Client) readAttrs(terminator string) (attrs Attrs, err error) {
	attrs = make(Attrs)
	for {
		line, err := c.readLine()
		if err != nil {
			return nil, err
		}
		if line == terminator {
			break
		}
		z := strings.Index(line, ": ")
		if z < 0 {
			return nil, textproto.ProtocolError("can't parse line: " + line)
		}
		key := line[0:z]
		attrs[key] = line[z+2:]
	}
	return
}
func (c *Client) readBinary() ([]byte, int, error) {
	size := -1
	for {
		line, err := c.readLine()
		switch {
		case err != nil:
			return nil, 0, err
		case strings.HasPrefix(line, "size: "):
			if size, err = strconv.Atoi(line[6:]); err != nil {
				return nil, 0, textproto.ProtocolError("failed to parse size: " + err.Error())
			}
		case strings.HasPrefix(line, "binary: "):
			length := -1
			if length, err = strconv.Atoi(line[8:]); err != nil {
				return nil, 0, textproto.ProtocolError("failed to parse binary: " + err.Error())
			}
			if size < 0 {
				size = length
			}
			data, err := c.readBytes(length)
			if err != nil {
				return nil, 0, err
			}
			if s, err := c.readLine(); err != nil {
				return nil, 0, err
			} else if s != "OK" {
				return nil, 0, textproto.ProtocolError("expected 'OK', got " + s)
			}
			return data, size, nil
		case line == "", line == "OK":
			return nil, 0, textproto.ProtocolError("no binary data found in response")
		}
	}
}
func (c *Client) CurrentSong() (Attrs, error) {
	return c.Command("currentsong").Attrs()
}
func (c *Client) Status() (Attrs, error) {
	return c.Command("status").Attrs()
}
func (c *Client) Stats() (Attrs, error) {
	return c.Command("stats").Attrs()
}
func (c *Client) readOKLine(terminator string) (err error) {
	line, err := c.readLine()
	if err != nil {
		return
	}
	if line == terminator {
		return nil
	}
	return textproto.ProtocolError("unexpected response: " + line)
}
func (c *Client) idle(subsystems ...string) ([]string, error) {
	return c.Command("idle %s", Quoted(strings.Join(subsystems, " "))).Strings("changed")
}
func (c *Client) noIdle() (err error) {
	id, err := c.cmd("noidle")
	if err == nil {
		c.text.StartResponse(id)
		c.text.EndResponse(id)
	}
	return
}
func (c *Client) Next() error {
	return c.Command("next").OK()
}
func (c *Client) Pause(pause bool) error {
	if pause {
		return c.Command("pause 1").OK()
	}
	return c.Command("pause 0").OK()
}
func (c *Client) Play(pos int) error {
	if pos < 0 {
		return c.Command("play").OK()
	}
	return c.Command("play %d", pos).OK()
}
func (c *Client) PlayID(id int) error {
	if id < 0 {
		return c.Command("playid").OK()
	}
	return c.Command("playid %d", id).OK()
}
func (c *Client) Previous() error {
	return c.Command("previous").OK()
}
func (c *Client) Seek(pos, time int) error {
	return c.Command("seek %d %d", pos, time).OK()
}
func (c *Client) SeekID(id, time int) error {
	return c.Command("seekid %d %d", id, time).OK()
}
func (c *Client) SeekPos(pos int, d time.Duration) error {
	return c.Command("seek %d %f", pos, d.Seconds()).OK()
}
func (c *Client) SeekSongID(id int, d time.Duration) error {
	return c.Command("seekid %d %f", id, d.Seconds()).OK()
}
func (c *Client) SeekCur(d time.Duration, relative bool) error {
	if relative {
		return c.Command("seekcur %+f", d.Seconds()).OK()
	}
	return c.Command("seekcur %f", d.Seconds()).OK()
}
func (c *Client) Stop() error {
	return c.Command("stop").OK()
}
func (c *Client) SetVolume(volume int) error {
	return c.Command("setvol %d", volume).OK()
}
func (c *Client) Random(random bool) error {
	if random {
		return c.Command("random 1").OK()
	}
	return c.Command("random 0").OK()
}
func (c *Client) Repeat(repeat bool) error {
	if repeat {
		return c.Command("repeat 1").OK()
	}
	return c.Command("repeat 0").OK()
}
func (c *Client) Single(single bool) error {
	if single {
		return c.Command("single 1").OK()
	}
	return c.Command("single 0").OK()
}
func (c *Client) Consume(consume bool) error {
	if consume {
		return c.Command("consume 1").OK()
	}
	return c.Command("consume 0").OK()
}
func (c *Client) PlaylistInfo(start, end int) ([]Attrs, error) {
	var cmd *Command
	switch {
	case start < 0 && end < 0:
		cmd = c.Command("playlistinfo")
	case start >= 0 && end >= 0:
		cmd = c.Command("playlistinfo %d:%d", start, end)
	case start >= 0 && end < 0:
		cmd = c.Command("playlistinfo %d", start)
	case start < 0 && end >= 0:
		return nil, errors.New("negative start index")
	default:
		panic("unreachable")
	}
	return cmd.AttrsList("file")
}
func (c *Client) SetPriority(priority, start, end int) error {
	switch {
	case start < 0 && end < 0:
		return errors.New("negative start and end index")
	case start >= 0 && end >= 0:
		return c.Command("prio %d %d:%d", priority, start, end).OK()
	case start >= 0 && end < 0:
		return c.Command("prio %d %d", priority, start).OK()
	case start < 0 && end >= 0:
		return errors.New("negative start index")
	default:
		panic("unreachable")
	}
}
func (c *Client) SetPriorityID(priority, id int) error {
	return c.Command("prioid %d %d", priority, id).OK()
}
func (c *Client) Delete(start, end int) error {
	if start < 0 {
		return errors.New("negative start index")
	}
	if end < 0 {
		return c.Command("delete %d", start).OK()
	}
	return c.Command("delete %d:%d", start, end).OK()
}
func (c *Client) DeleteID(id int) error {
	return c.Command("deleteid %d", id).OK()
}
func (c *Client) Move(start, end, position int) error {
	if start < 0 {
		return errors.New("negative start index")
	}
	if end < 0 {
		return c.Command("move %d %d", start, position).OK()
	}
	return c.Command("move %d:%d %d", start, end, position).OK()
}
func (c *Client) MoveID(songid, position int) error {
	return c.Command("moveid %d %d", songid, position).OK()
}
func (c *Client) Add(uri string) error {
	return c.Command("add %s", uri).OK()
}
func (c *Client) AddID(uri string, pos int) (int, error) {
	var cmd *Command
	if pos >= 0 {
		cmd = c.Command("addid %s %d", uri, pos)
	} else {
		cmd = c.Command("addid %s", uri)
	}
	attrs, err := cmd.Attrs()
	if err != nil {
		return -1, err
	}
	tok, ok := attrs["Id"]
	if !ok {
		return -1, textproto.ProtocolError("addid did not return Id")
	}
	return strconv.Atoi(tok)
}
func (c *Client) Clear() error {
	return c.Command("clear").OK()
}
func (c *Client) Shuffle(start, end int) error {
	if start < 0 || end < 0 {
		return c.Command("shuffle").OK()
	}
	return c.Command("shuffle %d:%d", start, end).OK()
}
func (c *Client) GetFiles() ([]string, error) {
	return c.Command("list file").Strings("file")
}
func (c *Client) Update(uri string) (jobID int, err error) {
	id, err := c.cmd("update %s", quote(uri))
	if err != nil {
		return
	}
	c.text.StartResponse(id)
	defer c.text.EndResponse(id)
	line, err := c.readLine()
	if err != nil {
		return
	}
	if !strings.HasPrefix(line, "updating_db: ") {
		return 0, textproto.ProtocolError("unexpected response: " + line)
	}
	jobID, err = strconv.Atoi(line[13:])
	if err != nil {
		return
	}
	return jobID, c.readOKLine("OK")
}
func (c *Client) Rescan(uri string) (jobID int, err error) {
	id, err := c.cmd("rescan %s", quote(uri))
	if err != nil {
		return
	}
	c.text.StartResponse(id)
	defer c.text.EndResponse(id)
	line, err := c.readLine()
	if err != nil {
		return
	}
	if !strings.HasPrefix(line, "updating_db: ") {
		return 0, textproto.ProtocolError("unexpected response: " + line)
	}
	jobID, err = strconv.Atoi(line[13:])
	if err != nil {
		return
	}
	return jobID, c.readOKLine("OK")
}
func (c *Client) ListAllInfo(uri string) ([]Attrs, error) {
	id, err := c.cmd("listallinfo %s ", quote(uri))
	if err != nil {
		return nil, err
	}
	c.text.StartResponse(id)
	defer c.text.EndResponse(id)
	attrs := []Attrs{}
	inEntry := false
	for {
		line, err := c.readLine()
		if err != nil {
			return nil, err
		}
		if line == "OK" {
			break
		} else if strings.HasPrefix(line, "file: ") {  
			attrs = append(attrs, Attrs{})
			inEntry = true
		} else if strings.HasPrefix(line, "directory: ") {
			inEntry = false
		}
		if inEntry {
			i := strings.Index(line, ": ")
			if i < 0 {
				return nil, textproto.ProtocolError("can't parse line: " + line)
			}
			attrs[len(attrs)-1][line[0:i]] = line[i+2:]
		}
	}
	return attrs, nil
}
func (c *Client) ListInfo(uri string) ([]Attrs, error) {
	id, err := c.cmd("lsinfo %s", quote(uri))
	if err != nil {
		return nil, err
	}
	c.text.StartResponse(id)
	defer c.text.EndResponse(id)
	attrs := []Attrs{}
	for {
		line, err := c.readLine()
		if err != nil {
			return nil, err
		}
		if line == "OK" {
			break
		}
		if strings.HasPrefix(line, "file: ") ||
			strings.HasPrefix(line, "directory: ") ||
			strings.HasPrefix(line, "playlist: ") {
			attrs = append(attrs, Attrs{})
		}
		i := strings.Index(line, ": ")
		if i < 0 {
			return nil, textproto.ProtocolError("can't parse line: " + line)
		}
		attrs[len(attrs)-1][strings.ToLower(line[0:i])] = line[i+2:]
	}
	return attrs, nil
}
func (c *Client) ReadComments(uri string) (Attrs, error) {
	return c.Command("readcomments %s", uri).Attrs()
}
func (c *Client) Find(args ...string) ([]Attrs, error) {
	return c.Command("find " + quoteArgs(args)).AttrsList("file")
}
func (c *Client) Search(args ...string) ([]Attrs, error) {
	return c.Command("search " + quoteArgs(args)).AttrsList("file")
}
func (c *Client) List(args ...string) ([]string, error) {
	id, err := c.cmd("list " + quoteArgs(args))
	if err != nil {
		return nil, err
	}
	c.text.StartResponse(id)
	defer c.text.EndResponse(id)
	var ret []string
	for {
		line, err := c.readLine()
		if err != nil {
			return nil, err
		}
		i := strings.Index(line, ": ")
		if i > 0 {
			ret = append(ret, line[i+2:])
		} else if line == "OK" {
			break
		} else {
			return nil, textproto.ProtocolError("can't parse line: " + line)
		}
	}
	return ret, nil
}
func (c *Client) Partition(name string) error {
	return c.Command("partition %s", name).OK()
}
func (c *Client) ListPartitions() ([]Attrs, error) {
	return c.Command("listpartitions").AttrsList("partition")
}
func (c *Client) NewPartition(name string) error {
	return c.Command("newpartition %s", name).OK()
}
func (c *Client) DelPartition(name string) error {
	return c.Command("delpartition %s", name).OK()
}
func (c *Client) MoveOutput(name string) error {
	return c.Command("moveoutput %s", name).OK()
}
func (c *Client) ListOutputs() ([]Attrs, error) {
	return c.Command("outputs").AttrsList("outputid")
}
func (c *Client) EnableOutput(id int) error {
	return c.Command("enableoutput %d", id).OK()
}
func (c *Client) DisableOutput(id int) error {
	return c.Command("disableoutput %d", id).OK()
}
func (c *Client) ListPlaylists() ([]Attrs, error) {
	return c.Command("listplaylists").AttrsList("playlist")
}
func (c *Client) PlaylistContents(name string) ([]Attrs, error) {
	return c.Command("listplaylistinfo %s", name).AttrsList("file")
}
func (c *Client) PlaylistLoad(name string, start, end int) error {
	if start < 0 || end < 0 {
		return c.Command("load %s", name).OK()
	}
	return c.Command("load %s %d:%d", name, start, end).OK()
}
func (c *Client) PlaylistAdd(name string, uri string) error {
	return c.Command("playlistadd %s %s", name, uri).OK()
}
func (c *Client) PlaylistClear(name string) error {
	return c.Command("playlistclear %s", name).OK()
}
func (c *Client) PlaylistDelete(name string, pos int) error {
	return c.Command("playlistdelete %s %d", name, pos).OK()
}