package imap

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"mime"
	"strconv"
	"strings"
	"time"
)

// System message flags, defined in RFC 3501 section 2.3.2.
const (
	SeenFlag     = "\\Seen"
	AnsweredFlag = "\\Answered"
	FlaggedFlag  = "\\Flagged"
	DeletedFlag  = "\\Deleted"
	DraftFlag    = "\\Draft"
	RecentFlag   = "\\Recent"
)

// ImportantFlag is a message flag to signal that a message is likely important
// to the user. This flag is defined in RFC 8457 section 2.
const ImportantFlag = "$Important"

// TryCreateFlag is a special flag in MailboxStatus.PermanentFlags indicating
// that it is possible to create new keywords by attempting to store those
// flags in the mailbox.
const TryCreateFlag = "\\*"

var flags = []string{
	SeenFlag,
	AnsweredFlag,
	FlaggedFlag,
	DeletedFlag,
	DraftFlag,
	RecentFlag,
}

// A PartSpecifier specifies which parts of the MIME entity should be returned.
type PartSpecifier string

// Part specifiers described in RFC 3501 page 55.
const (
	// Refers to the entire part, including headers.
	EntireSpecifier PartSpecifier = ""
	// Refers to the header of the part. Must include the final CRLF delimiting
	// the header and the body.
	HeaderSpecifier = "HEADER"
	// Refers to the text body of the part, omitting the header.
	TextSpecifier = "TEXT"
	// Refers to the MIME Internet Message Body header.  Must include the final
	// CRLF delimiting the header and the body.
	MIMESpecifier = "MIME"
)

// CanonicalFlag returns the canonical form of a flag. Flags are case-insensitive.
//
// If the flag is defined in RFC 3501, it returns the flag with the case of the
// RFC. Otherwise, it returns the lowercase version of the flag.
func CanonicalFlag(flag string) string {
	for _, f := range flags {
		if strings.EqualFold(f, flag) {
			return f
		}
	}
	return strings.ToLower(flag)
}

func ParseParamList(fields []interface{}) (map[string]string, error) {
	params := make(map[string]string)

	var k string
	for i, f := range fields {
		p, err := ParseString(f)
		if err != nil {
			return nil, errors.New("Parameter list contains a non-string: " + err.Error())
		}

		if i%2 == 0 {
			k = p
		} else {
			params[k] = p
			k = ""
		}
	}

	if k != "" {
		return nil, errors.New("Parameter list contains a key without a value")
	}
	return params, nil
}

func FormatParamList(params map[string]string) []interface{} {
	var fields []interface{}
	for key, value := range params {
		fields = append(fields, key, value)
	}
	return fields
}

var wordDecoder = &mime.WordDecoder{
	CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
		if CharsetReader != nil {
			return CharsetReader(charset, input)
		}
		return nil, fmt.Errorf("imap: unhandled charset %q", charset)
	},
}

func decodeHeader(s string) (string, error) {
	dec, err := wordDecoder.DecodeHeader(s)
	if err != nil {
		return s, err
	}
	return dec, nil
}

func encodeHeader(s string) string {
	return mime.QEncoding.Encode("utf-8", s)
}

func stringLowered(i interface{}) (string, bool) {
	s, ok := i.(string)
	return strings.ToLower(s), ok
}

func parseHeaderParamList(fields []interface{}) (map[string]string, error) {
	params, err := ParseParamList(fields)
	if err != nil {
		return nil, err
	}

	for k, v := range params {
		if lower := strings.ToLower(k); lower != k {
			delete(params, k)
			k = lower
		}

		params[k], _ = decodeHeader(v)
	}

	return params, nil
}

func formatHeaderParamList(params map[string]string) []interface{} {
	encoded := make(map[string]string)
	for k, v := range params {
		encoded[k] = encodeHeader(v)
	}
	return FormatParamList(encoded)
}

// A message.
type Message struct {
	// The message sequence number. It must be greater than or equal to 1.
	SeqNum uint32
	// The mailbox items that are currently filled in. This map's values
	// should not be used directly, they must only be used by libraries
	// implementing extensions of the IMAP protocol.
	Items map[FetchItem]interface{}

	// The message envelope.
	Envelope *Envelope
	// The message body structure (either BODYSTRUCTURE or BODY).
	BodyStructure *BodyStructure
	// The message flags.
	Flags []string
	// The date the message was received by the server.
	InternalDate time.Time
	// The message size.
	Size uint32
	// The message unique identifier. It must be greater than or equal to 1.
	Uid uint32
	// The message body sections.
	Body map[*BodySectionName]Literal

	// The order in which items were requested. This order must be preserved
	// because some bad IMAP clients (looking at you, Outlook!) refuse responses
	// containing items in a different order.
	itemsOrder []FetchItem
}

// Create a new empty message that will contain the specified items.
func NewMessage(seqNum uint32, items []FetchItem) *Message {
	msg := &Message{
		SeqNum:     seqNum,
		Items:      make(map[FetchItem]interface{}),
		Body:       make(map[*BodySectionName]Literal),
		itemsOrder: items,
	}

	for _, k := range items {
		msg.Items[k] = nil
	}

	return msg
}

// Parse a message from fields.
func (m *Message) Parse(fields []interface{}) error {
	m.Items = make(map[FetchItem]interface{})
	m.Body = map[*BodySectionName]Literal{}
	m.itemsOrder = nil

	var k FetchItem
	for i, f := range fields {
		if i%2 == 0 { // It's a key
			switch f := f.(type) {
			case string:
				k = FetchItem(strings.ToUpper(f))
			case RawString:
				k = FetchItem(strings.ToUpper(string(f)))
			default:
				return fmt.Errorf("cannot parse message: key is not a string, but a %T", f)
			}
		} else { // It's a value
			m.Items[k] = nil
			m.itemsOrder = append(m.itemsOrder, k)

			switch k {
			case FetchBody, FetchBodyStructure:
				bs, ok := f.([]interface{})
				if !ok {
					return fmt.Errorf("cannot parse message: BODYSTRUCTURE is not a list, but a %T", f)
				}

				m.BodyStructure = &BodyStructure{Extended: k == FetchBodyStructure}
				if err := m.BodyStructure.Parse(bs); err != nil {
					return err
				}
			case FetchEnvelope:
				env, ok := f.([]interface{})
				if !ok {
					return fmt.Errorf("cannot parse message: ENVELOPE is not a list, but a %T", f)
				}

				m.Envelope = &Envelope{}
				if err := m.Envelope.Parse(env); err != nil {
					return err
				}
			case FetchFlags:
				flags, ok := f.([]interface{})
				if !ok {
					return fmt.Errorf("cannot parse message: FLAGS is not a list, but a %T", f)
				}

				m.Flags = make([]string, len(flags))
				for i, flag := range flags {
					s, _ := ParseString(flag)
					m.Flags[i] = CanonicalFlag(s)
				}
			case FetchInternalDate:
				date, _ := f.(string)
				m.InternalDate, _ = time.Parse(DateTimeLayout, date)
			case FetchRFC822Size:
				m.Size, _ = ParseNumber(f)
			case FetchUid:
				m.Uid, _ = ParseNumber(f)
			default:
				// Likely to be a section of the body
				// First check that the section name is correct
				if section, err := ParseBodySectionName(k); err != nil {
					// Not a section name, maybe an attribute defined in an IMAP extension
					m.Items[k] = f
				} else {
					m.Body[section], _ = f.(Literal)
				}
			}
		}
	}

	return nil
}

func (m *Message) formatItem(k FetchItem) []interface{} {
	v := m.Items[k]
	var kk interface{} = RawString(k)

	switch k {
	case FetchBody, FetchBodyStructure:
		// Extension data is only returned with the BODYSTRUCTURE fetch
		m.BodyStructure.Extended = k == FetchBodyStructure
		v = m.BodyStructure.Format()
	case FetchEnvelope:
		v = m.Envelope.Format()
	case FetchFlags:
		flags := make([]interface{}, len(m.Flags))
		for i, flag := range m.Flags {
			flags[i] = RawString(flag)
		}
		v = flags
	case FetchInternalDate:
		v = m.InternalDate
	case FetchRFC822Size:
		v = m.Size
	case FetchUid:
		v = m.Uid
	default:
		for section, literal := range m.Body {
			if section.value == k {
				// This can contain spaces, so we can't pass it as a string directly
				kk = section.resp()
				v = literal
				break
			}
		}
	}

	return []interface{}{kk, v}
}

func (m *Message) Format() []interface{} {
	var fields []interface{}

	// First send ordered items
	processed := make(map[FetchItem]bool)
	for _, k := range m.itemsOrder {
		if _, ok := m.Items[k]; ok {
			fields = append(fields, m.formatItem(k)...)
			processed[k] = true
		}
	}

	// Then send other remaining items
	for k := range m.Items {
		if !processed[k] {
			fields = append(fields, m.formatItem(k)...)
		}
	}

	return fields
}

// GetBody gets the body section with the specified name. Returns nil if it's not found.
func (m *Message) GetBody(section *BodySectionName) Literal {
	section = section.resp()

	for s, body := range m.Body {
		if section.Equal(s) {
			if body == nil {
				// Server can return nil, we need to treat as empty string per RFC 3501
				body = bytes.NewReader(nil)
			}
			return body
		}
	}
	return nil
}

// A body section name.
// See RFC 3501 page 55.
type BodySectionName struct {
	BodyPartName

	// If set to true, do not implicitly set the \Seen flag.
	Peek bool
	// The substring of the section requested. The first value is the position of
	// the first desired octet and the second value is the maximum number of
	// octets desired.
	Partial []int

	value FetchItem
}

func (section *BodySectionName) parse(s string) error {
	section.value = FetchItem(s)

	if s == "RFC822" {
		s = "BODY[]"
	}
	if s == "RFC822.HEADER" {
		s = "BODY.PEEK[HEADER]"
	}
	if s == "RFC822.TEXT" {
		s = "BODY[TEXT]"
	}

	partStart := strings.Index(s, "[")
	if partStart == -1 {
		return errors.New("Invalid body section name: must contain an open bracket")
	}

	partEnd := strings.LastIndex(s, "]")
	if partEnd == -1 {
		return errors.New("Invalid body section name: must contain a close bracket")
	}

	name := s[:partStart]
	part := s[partStart+1 : partEnd]
	partial := s[partEnd+1:]

	if name == "BODY.PEEK" {
		section.Peek = true
	} else if name != "BODY" {
		return errors.New("Invalid body section name")
	}

	b := bytes.NewBufferString(part + string(cr) + string(lf))
	r := NewReader(b)
	fields, err := r.ReadFields()
	if err != nil {
		return err
	}

	if err := section.BodyPartName.parse(fields); err != nil {
		return err
	}

	if len(partial) > 0 {
		if !strings.HasPrefix(partial, "<") || !strings.HasSuffix(partial, ">") {
			return errors.New("Invalid body section name: invalid partial")
		}
		partial = partial[1 : len(partial)-1]

		partialParts := strings.SplitN(partial, ".", 2)

		var from, length int
		if from, err = strconv.Atoi(partialParts[0]); err != nil {
			return errors.New("Invalid body section name: invalid partial: invalid from: " + err.Error())
		}
		section.Partial = []int{from}

		if len(partialParts) == 2 {
			if length, err = strconv.Atoi(partialParts[1]); err != nil {
				return errors.New("Invalid body section name: invalid partial: invalid length: " + err.Error())
			}
			section.Partial = append(section.Partial, length)
		}
	}

	return nil
}

func (section *BodySectionName) FetchItem() FetchItem {
	if section.value != "" {
		return section.value
	}

	s := "BODY"
	if section.Peek {
		s += ".PEEK"
	}

	s += "[" + section.BodyPartName.string() + "]"

	if len(section.Partial) > 0 {
		s += "<"
		s += strconv.Itoa(section.Partial[0])

		if len(section.Partial) > 1 {
			s += "."
			s += strconv.Itoa(section.Partial[1])
		}

		s += ">"
	}

	return FetchItem(s)
}

// Equal checks whether two sections are equal.
func (section *BodySectionName) Equal(other *BodySectionName) bool {
	if section.Peek != other.Peek {
		return false
	}
	if len(section.Partial) != len(other.Partial) {
		return false
	}
	if len(section.Partial) > 0 && section.Partial[0] != other.Partial[0] {
		return false
	}
	if len(section.Partial) > 1 && section.Partial[1] != other.Partial[1] {
		return false
	}
	return section.BodyPartName.Equal(&other.BodyPartName)
}

func (section *BodySectionName) resp() *BodySectionName {
	resp := *section // Copy section
	if resp.Peek {
		resp.Peek = false
	}
	if len(resp.Partial) == 2 {
		resp.Partial = []int{resp.Partial[0]}
	}
	if !strings.HasPrefix(string(resp.value), string(FetchRFC822)) {
		resp.value = ""
	}
	return &resp
}

// ExtractPartial returns a subset of the specified bytes matching the partial requested in the
// section name.
func (section *BodySectionName) ExtractPartial(b []byte) []byte {
	if len(section.Partial) != 2 {
		return b
	}

	from := section.Partial[0]
	length := section.Partial[1]
	to := from + length
	if from > len(b) {
		return nil
	}
	if to > len(b) {
		to = len(b)
	}
	return b[from:to]
}

// ParseBodySectionName parses a body section name.
func ParseBodySectionName(s FetchItem) (*BodySectionName, error) {
	section := new(BodySectionName)
	err := section.parse(string(s))
	return section, err
}

// A body part name.
type BodyPartName struct {
	// The specifier of the requested part.
	Specifier PartSpecifier
	// The part path. Parts indexes start at 1.
	Path []int
	// If Specifier is HEADER, contains header fields that will/won't be returned,
	// depending of the value of NotFields.
	Fields []string
	// If set to true, Fields is a blacklist of fields instead of a whitelist.
	NotFields bool
}

func (part *BodyPartName) parse(fields []interface{}) error {
	if len(fields) == 0 {
		return nil
	}

	name, ok := fields[0].(string)
	if !ok {
		return errors.New("Invalid body section name: part name must be a string")
	}

	args := fields[1:]

	path := strings.Split(strings.ToUpper(name), ".")

	end := 0
loop:
	for i, node := range path {
		switch PartSpecifier(node) {
		case EntireSpecifier, HeaderSpecifier, MIMESpecifier, TextSpecifier:
			part.Specifier = PartSpecifier(node)
			end = i + 1
			break loop
		}

		index, err := strconv.Atoi(node)
		if err != nil {
			return errors.New("Invalid body part name: " + err.Error())
		}
		if index <= 0 {
			return errors.New("Invalid body part name: index <= 0")
		}

		part.Path = append(part.Path, index)
	}

	if part.Specifier == HeaderSpecifier && len(path) > end && path[end] == "FIELDS" && len(args) > 0 {
		end++
		if len(path) > end && path[end] == "NOT" {
			part.NotFields = true
		}

		names, ok := args[0].([]interface{})
		if !ok {
			return errors.New("Invalid body part name: HEADER.FIELDS must have a list argument")
		}

		for _, namei := range names {
			if name, ok := namei.(string); ok {
				part.Fields = append(part.Fields, name)
			}
		}
	}

	return nil
}

func (part *BodyPartName) string() string {
	path := make([]string, len(part.Path))
	for i, index := range part.Path {
		path[i] = strconv.Itoa(index)
	}

	if part.Specifier != EntireSpecifier {
		path = append(path, string(part.Specifier))
	}

	if part.Specifier == HeaderSpecifier && len(part.Fields) > 0 {
		path = append(path, "FIELDS")

		if part.NotFields {
			path = append(path, "NOT")
		}
	}

	s := strings.Join(path, ".")

	if len(part.Fields) > 0 {
		s += " (" + strings.Join(part.Fields, " ") + ")"
	}

	return s
}

// Equal checks whether two body part names are equal.
func (part *BodyPartName) Equal(other *BodyPartName) bool {
	if part.Specifier != other.Specifier {
		return false
	}
	if part.NotFields != other.NotFields {
		return false
	}
	if len(part.Path) != len(other.Path) {
		return false
	}
	for i, node := range part.Path {
		if node != other.Path[i] {
			return false
		}
	}
	if len(part.Fields) != len(other.Fields) {
		return false
	}
	for _, field := range part.Fields {
		found := false
		for _, f := range other.Fields {
			if strings.EqualFold(field, f) {
				found = true
				break
			}
		}
		if !found {
			return false
		}
	}
	return true
}

// An address.
type Address struct {
	// The personal name.
	PersonalName string
	// The SMTP at-domain-list (source route).
	AtDomainList string
	// The mailbox name.
	MailboxName string
	// The host name.
	HostName string
}

// Address returns the mailbox address (e.g. "foo@example.org").
func (addr *Address) Address() string {
	return addr.MailboxName + "@" + addr.HostName
}

// Parse an address from fields.
func (addr *Address) Parse(fields []interface{}) error {
	if len(fields) < 4 {
		return errors.New("Address doesn't contain 4 fields")
	}

	if s, err := ParseString(fields[0]); err == nil {
		addr.PersonalName, _ = decodeHeader(s)
	}
	if s, err := ParseString(fields[1]); err == nil {
		addr.AtDomainList, _ = decodeHeader(s)
	}

	s, err := ParseString(fields[2])
	if err != nil {
		return errors.New("Mailbox name could not be parsed")
	}
	addr.MailboxName, _ = decodeHeader(s)

	s, err = ParseString(fields[3])
	if err != nil {
		return errors.New("Host name could not be parsed")
	}
	addr.HostName, _ = decodeHeader(s)

	return nil
}

// Format an address to fields.
func (addr *Address) Format() []interface{} {
	fields := make([]interface{}, 4)

	if addr.PersonalName != "" {
		fields[0] = encodeHeader(addr.PersonalName)
	}
	if addr.AtDomainList != "" {
		fields[1] = addr.AtDomainList
	}
	if addr.MailboxName != "" {
		fields[2] = addr.MailboxName
	}
	if addr.HostName != "" {
		fields[3] = addr.HostName
	}

	return fields
}

// Parse an address list from fields.
func ParseAddressList(fields []interface{}) (addrs []*Address) {
	for _, f := range fields {
		if addrFields, ok := f.([]interface{}); ok {
			addr := &Address{}
			if err := addr.Parse(addrFields); err == nil {
				addrs = append(addrs, addr)
			}
		}
	}

	return
}

// Format an address list to fields.
func FormatAddressList(addrs []*Address) interface{} {
	if len(addrs) == 0 {
		return nil
	}

	fields := make([]interface{}, len(addrs))

	for i, addr := range addrs {
		fields[i] = addr.Format()
	}

	return fields
}

// A message envelope, ie. message metadata from its headers.
// See RFC 3501 page 77.
type Envelope struct {
	// The message date.
	Date time.Time
	// The message subject.
	Subject string
	// The From header addresses.
	From []*Address
	// The message senders.
	Sender []*Address
	// The Reply-To header addresses.
	ReplyTo []*Address
	// The To header addresses.
	To []*Address
	// The Cc header addresses.
	Cc []*Address
	// The Bcc header addresses.
	Bcc []*Address
	// The In-Reply-To header. Contains the parent Message-Id.
	InReplyTo string
	// The Message-Id header.
	MessageId string
}

// Parse an envelope from fields.
func (e *Envelope) Parse(fields []interface{}) error {
	if len(fields) < 10 {
		return errors.New("ENVELOPE doesn't contain 10 fields")
	}

	if date, ok := fields[0].(string); ok {
		e.Date, _ = parseMessageDateTime(date)
	}
	if subject, err := ParseString(fields[1]); err == nil {
		e.Subject, _ = decodeHeader(subject)
	}
	if from, ok := fields[2].([]interface{}); ok {
		e.From = ParseAddressList(from)
	}
	if sender, ok := fields[3].([]interface{}); ok {
		e.Sender = ParseAddressList(sender)
	}
	if replyTo, ok := fields[4].([]interface{}); ok {
		e.ReplyTo = ParseAddressList(replyTo)
	}
	if to, ok := fields[5].([]interface{}); ok {
		e.To = ParseAddressList(to)
	}
	if cc, ok := fields[6].([]interface{}); ok {
		e.Cc = ParseAddressList(cc)
	}
	if bcc, ok := fields[7].([]interface{}); ok {
		e.Bcc = ParseAddressList(bcc)
	}
	if inReplyTo, ok := fields[8].(string); ok {
		e.InReplyTo = inReplyTo
	}
	if msgId, ok := fields[9].(string); ok {
		e.MessageId = msgId
	}

	return nil
}

// Format an envelope to fields.
func (e *Envelope) Format() (fields []interface{}) {
	fields = make([]interface{}, 0, 10)
	fields = append(fields, envelopeDateTime(e.Date))
	if e.Subject != "" {
		fields = append(fields, encodeHeader(e.Subject))
	} else {
		fields = append(fields, nil)
	}
	fields = append(fields,
		FormatAddressList(e.From),
		FormatAddressList(e.Sender),
		FormatAddressList(e.ReplyTo),
		FormatAddressList(e.To),
		FormatAddressList(e.Cc),
		FormatAddressList(e.Bcc),
	)
	if e.InReplyTo != "" {
		fields = append(fields, e.InReplyTo)
	} else {
		fields = append(fields, nil)
	}
	if e.MessageId != "" {
		fields = append(fields, e.MessageId)
	} else {
		fields = append(fields, nil)
	}
	return fields
}

// A body structure.
// See RFC 3501 page 74.
type BodyStructure struct {
	// Basic fields

	// The MIME type (e.g. "text", "image")
	MIMEType string
	// The MIME subtype (e.g. "plain", "png")
	MIMESubType string
	// The MIME parameters.
	Params map[string]string

	// The Content-Id header.
	Id string
	// The Content-Description header.
	Description string
	// The Content-Encoding header.
	Encoding string
	// The Content-Length header.
	Size uint32

	// Type-specific fields

	// The children parts, if multipart.
	Parts []*BodyStructure
	// The envelope, if message/rfc822.
	Envelope *Envelope
	// The body structure, if message/rfc822.
	BodyStructure *BodyStructure
	// The number of lines, if text or message/rfc822.
	Lines uint32

	// Extension data

	// True if the body structure contains extension data.
	Extended bool

	// The Content-Disposition header field value.
	Disposition string
	// The Content-Disposition header field parameters.
	DispositionParams map[string]string
	// The Content-Language header field, if multipart.
	Language []string
	// The content URI, if multipart.
	Location []string

	// The MD5 checksum.
	MD5 string
}

func (bs *BodyStructure) Parse(fields []interface{}) error {
	if len(fields) == 0 {
		return nil
	}

	// Initialize params map
	bs.Params = make(map[string]string)

	switch fields[0].(type) {
	case []interface{}: // A multipart body part
		bs.MIMEType = "multipart"

		end := 0
		for i, fi := range fields {
			switch f := fi.(type) {
			case []interface{}: // A part
				part := new(BodyStructure)
				if err := part.Parse(f); err != nil {
					return err
				}
				bs.Parts = append(bs.Parts, part)
			case string:
				end = i
			}

			if end > 0 {
				break
			}
		}

		bs.MIMESubType, _ = fields[end].(string)
		end++

		// GMail seems to return only 3 extension data fields. Parse as many fields
		// as we can.
		if len(fields) > end {
			bs.Extended = true // Contains extension data

			params, _ := fields[end].([]interface{})
			bs.Params, _ = parseHeaderParamList(params)
			end++
		}
		if len(fields) > end {
			if disp, ok := fields[end].([]interface{}); ok && len(disp) >= 2 {
				if s, ok := disp[0].(string); ok {
					bs.Disposition, _ = decodeHeader(s)
					bs.Disposition = strings.ToLower(bs.Disposition)
				}
				if params, ok := disp[1].([]interface{}); ok {
					bs.DispositionParams, _ = parseHeaderParamList(params)
				}
			}
			end++
		}
		if len(fields) > end {
			switch langs := fields[end].(type) {
			case string:
				bs.Language = []string{langs}
			case []interface{}:
				bs.Language, _ = ParseStringList(langs)
			default:
				bs.Language = nil
			}
			end++
		}
		if len(fields) > end {
			location, _ := fields[end].([]interface{})
			bs.Location, _ = ParseStringList(location)
			end++
		}
	case string: // A non-multipart body part
		if len(fields) < 7 {
			return errors.New("Non-multipart body part doesn't have 7 fields")
		}

		bs.MIMEType, _ = stringLowered(fields[0])
		bs.MIMESubType, _ = stringLowered(fields[1])

		params, _ := fields[2].([]interface{})
		bs.Params, _ = parseHeaderParamList(params)

		bs.Id, _ = fields[3].(string)
		if desc, err := ParseString(fields[4]); err == nil {
			bs.Description, _ = decodeHeader(desc)
		}
		bs.Encoding, _ = stringLowered(fields[5])
		bs.Size, _ = ParseNumber(fields[6])

		end := 7

		// Type-specific fields
		if strings.EqualFold(bs.MIMEType, "message") && strings.EqualFold(bs.MIMESubType, "rfc822") {
			if len(fields)-end < 3 {
				return errors.New("Missing type-specific fields for message/rfc822")
			}

			envelope, _ := fields[end].([]interface{})
			bs.Envelope = new(Envelope)
			bs.Envelope.Parse(envelope)

			structure, _ := fields[end+1].([]interface{})
			bs.BodyStructure = new(BodyStructure)
			bs.BodyStructure.Parse(structure)

			bs.Lines, _ = ParseNumber(fields[end+2])

			end += 3
		}
		if strings.EqualFold(bs.MIMEType, "text") {
			if len(fields)-end < 1 {
				return errors.New("Missing type-specific fields for text/*")
			}

			bs.Lines, _ = ParseNumber(fields[end])
			end++
		}

		// GMail seems to return only 3 extension data fields. Parse as many fields
		// as we can.
		if len(fields) > end {
			bs.Extended = true // Contains extension data

			bs.MD5, _ = fields[end].(string)
			end++
		}
		if len(fields) > end {
			if disp, ok := fields[end].([]interface{}); ok && len(disp) >= 2 {
				if s, ok := disp[0].(string); ok {
					bs.Disposition, _ = decodeHeader(s)
					bs.Disposition = strings.ToLower(bs.Disposition)
				}
				if params, ok := disp[1].([]interface{}); ok {
					bs.DispositionParams, _ = parseHeaderParamList(params)
				}
			}
			end++
		}
		if len(fields) > end {
			switch langs := fields[end].(type) {
			case string:
				bs.Language = []string{langs}
			case []interface{}:
				bs.Language, _ = ParseStringList(langs)
			default:
				bs.Language = nil
			}
			end++
		}
		if len(fields) > end {
			location, _ := fields[end].([]interface{})
			bs.Location, _ = ParseStringList(location)
			end++
		}
	}

	return nil
}

func (bs *BodyStructure) Format() (fields []interface{}) {
	if strings.EqualFold(bs.MIMEType, "multipart") {
		for _, part := range bs.Parts {
			fields = append(fields, part.Format())
		}

		fields = append(fields, bs.MIMESubType)

		if bs.Extended {
			extended := make([]interface{}, 4)

			if bs.Params != nil {
				extended[0] = formatHeaderParamList(bs.Params)
			}
			if bs.Disposition != "" {
				extended[1] = []interface{}{
					encodeHeader(bs.Disposition),
					formatHeaderParamList(bs.DispositionParams),
				}
			}
			if bs.Language != nil {
				extended[2] = FormatStringList(bs.Language)
			}
			if bs.Location != nil {
				extended[3] = FormatStringList(bs.Location)
			}

			fields = append(fields, extended...)
		}
	} else {
		fields = make([]interface{}, 7)
		fields[0] = bs.MIMEType
		fields[1] = bs.MIMESubType
		fields[2] = formatHeaderParamList(bs.Params)

		if bs.Id != "" {
			fields[3] = bs.Id
		}
		if bs.Description != "" {
			fields[4] = encodeHeader(bs.Description)
		}
		if bs.Encoding != "" {
			fields[5] = bs.Encoding
		}

		fields[6] = bs.Size

		// Type-specific fields
		if strings.EqualFold(bs.MIMEType, "message") && strings.EqualFold(bs.MIMESubType, "rfc822") {
			var env interface{}
			if bs.Envelope != nil {
				env = bs.Envelope.Format()
			}

			var bsbs interface{}
			if bs.BodyStructure != nil {
				bsbs = bs.BodyStructure.Format()
			}

			fields = append(fields, env, bsbs, bs.Lines)
		}
		if strings.EqualFold(bs.MIMEType, "text") {
			fields = append(fields, bs.Lines)
		}

		// Extension data
		if bs.Extended {
			extended := make([]interface{}, 4)

			if bs.MD5 != "" {
				extended[0] = bs.MD5
			}
			if bs.Disposition != "" {
				extended[1] = []interface{}{
					encodeHeader(bs.Disposition),
					formatHeaderParamList(bs.DispositionParams),
				}
			}
			if bs.Language != nil {
				extended[2] = FormatStringList(bs.Language)
			}
			if bs.Location != nil {
				extended[3] = FormatStringList(bs.Location)
			}

			fields = append(fields, extended...)
		}
	}

	return
}

// Filename parses the body structure's filename, if it's an attachment. An
// empty string is returned if the filename isn't specified. An error is
// returned if and only if a charset error occurs, in which case the undecoded
// filename is returned too.
func (bs *BodyStructure) Filename() (string, error) {
	raw, ok := bs.DispositionParams["filename"]
	if !ok {
		// Using "name" in Content-Type is discouraged
		raw = bs.Params["name"]
	}
	return decodeHeader(raw)
}

// BodyStructureWalkFunc is the type of the function called for each body
// structure visited by BodyStructure.Walk. The path argument contains the IMAP
// part path (see BodyPartName).
//
// The function should return true to visit all of the part's children or false
// to skip them.
type BodyStructureWalkFunc func(path []int, part *BodyStructure) (walkChildren bool)

// Walk walks the body structure tree, calling f for each part in the tree,
// including bs itself. The parts are visited in DFS pre-order.
func (bs *BodyStructure) Walk(f BodyStructureWalkFunc) {
	// Non-multipart messages only have part 1
	if len(bs.Parts) == 0 {
		f([]int{1}, bs)
		return
	}

	bs.walk(f, nil)
}

func (bs *BodyStructure) walk(f BodyStructureWalkFunc, path []int) {
	if !f(path, bs) {
		return
	}

	for i, part := range bs.Parts {
		num := i + 1

		partPath := append([]int(nil), path...)
		partPath = append(partPath, num)

		part.walk(f, partPath)
	}
}
