package imap

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

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

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

// Message attributes that can be fetched, defined in RFC 3501 section 6.4.5.
// Attributes that fetches the message contents are defined with
// BodySectionName.
const (
	// Non-extensible form of BODYSTRUCTURE.
	BodyMsgAttr = "BODY"
	// MIME body structure of the message.
	BodyStructureMsgAttr = "BODYSTRUCTURE"
	// The envelope structure of the message.
	EnvelopeMsgAttr = "ENVELOPE"
	// The flags that are set for the message.
	FlagsMsgAttr = "FLAGS"
	// The internal date of the message.
	InternalDateMsgAttr = "INTERNALDATE"
	// The RFC 822 size of the message.
	SizeMsgAttr = "RFC822.SIZE"
	// The unique identifier for the message.
	UidMsgAttr = "UID"
)

// Part specifiers described in RFC 3501 page 55.
const (
	// Refers to the entire part, including headers.
	EntireSpecifier = ""
	// 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"
)

// 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 {
	flag = strings.ToLower(flag)
	for _, f := range flags {
		if strings.ToLower(f) == flag {
			return f
		}
	}
	return flag
}

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

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

		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 parseHeaderParamList(fields []interface{}) (map[string]string, error) {
	params, err := ParseParamList(fields)
	if err != nil {
		return nil, err
	}

	for k, v := range params {
		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[string]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 []string
}

// Create a new empty message that will contain the specified items.
func NewMessage(seqNum uint32, items []string) *Message {
	msg := &Message{
		SeqNum:     seqNum,
		Items:      make(map[string]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[string]interface{})
	m.Body = map[*BodySectionName]Literal{}
	m.itemsOrder = nil

	var k string
	for i, f := range fields {
		if i%2 == 0 { // It's a key
			var ok bool
			if k, ok = f.(string); !ok {
				return errors.New("Key is not a string")
			}
			k = strings.ToUpper(k)
		} else { // It's a value
			m.Items[k] = nil
			m.itemsOrder = append(m.itemsOrder, k)

			switch k {
			case BodyMsgAttr, BodyStructureMsgAttr:
				bs, ok := f.([]interface{})
				if !ok {
					return errors.New("BODYSTRUCTURE is not a list")
				}

				m.BodyStructure = &BodyStructure{Extended: k == BodyStructureMsgAttr}
				if err := m.BodyStructure.Parse(bs); err != nil {
					return err
				}
			case EnvelopeMsgAttr:
				env, ok := f.([]interface{})
				if !ok {
					return errors.New("ENVELOPE is not a list")
				}

				m.Envelope = &Envelope{}
				if err := m.Envelope.Parse(env); err != nil {
					return err
				}
			case FlagsMsgAttr:
				flags, ok := f.([]interface{})
				if !ok {
					return errors.New("FLAGS is not a list")
				}

				m.Flags = make([]string, len(flags))
				for i, flag := range flags {
					s, _ := flag.(string)
					m.Flags[i] = CanonicalFlag(s)
				}
			case InternalDateMsgAttr:
				date, _ := f.(string)
				m.InternalDate, _ = time.Parse(DateTimeLayout, date)
			case SizeMsgAttr:
				m.Size, _ = ParseNumber(f)
			case UidMsgAttr:
				m.Uid, _ = ParseNumber(f)
			default:
				// Likely to be a section of the body
				// First check that the section name is correct
				if section, err := NewBodySectionName(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 string) []interface{} {
	v := m.Items[k]
	var kk interface{} = k

	switch strings.ToUpper(k) {
	case BodyMsgAttr, BodyStructureMsgAttr:
		// Extension data is only returned with the BODYSTRUCTURE fetch
		m.BodyStructure.Extended = k == BodyStructureMsgAttr
		v = m.BodyStructure.Format()
	case EnvelopeMsgAttr:
		v = m.Envelope.Format()
	case FlagsMsgAttr:
		v = FormatStringList(m.Flags)
	case InternalDateMsgAttr:
		v = m.InternalDate
	case SizeMsgAttr:
		v = m.Size
	case UidMsgAttr:
		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[string]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
}

// Get the body section with the specified name. Returns nil if it's not found.
func (m *Message) GetBody(s string) Literal {
	for section, body := range m.Body {
		if section.value == s {
			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 string
}

func (section *BodySectionName) parse(s string) (err error) {
	section.value = 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)
	var fields []interface{}
	if fields, err = r.ReadFields(); err != nil {
		return
	}

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

	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) String() (s string) {
	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
}

func (section *BodySectionName) resp() *BodySectionName {
	var reset bool

	if section.Peek != false {
		section.Peek = false
		reset = true
	}

	if len(section.Partial) == 2 {
		section.Partial = []int{section.Partial[0]}
		reset = true
	}

	if reset && !strings.HasPrefix(section.value, "RFC822") {
		section.value = "" // Reset cached value
	}

	return section
}

// 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]
}

// Parse a body section name.
func NewBodySectionName(s string) (section *BodySectionName, err error) {
	section = &BodySectionName{}
	err = section.parse(s)
	return
}

// A body part name.
type BodyPartName struct {
	// The specifier of the requested part.
	Specifier string
	// 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
	for i, node := range path {
		if node == "" || node == HeaderSpecifier || node == MimeSpecifier || node == TextSpecifier {
			part.Specifier = node
			end = i + 1
			break
		}

		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() (s string) {
	path := make([]string, len(part.Path))
	for i, index := range part.Path {
		path[i] = strconv.Itoa(index)
	}

	if part.Specifier != "" {
		path = append(path, 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
}

// 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
}

// 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 f, ok := fields[0].(string); ok {
		addr.PersonalName, _ = decodeHeader(f)
	}
	if f, ok := fields[1].(string); ok {
		addr.AtDomainList = f
	}
	if f, ok := fields[2].(string); ok {
		addr.MailboxName = f
	}
	if f, ok := fields[3].(string); ok {
		addr.HostName = f
	}

	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) {
	addrs = make([]*Address, len(fields))

	for i, f := range fields {
		if addrFields, ok := f.([]interface{}); ok {
			addr := &Address{}
			if err := addr.Parse(addrFields); err == nil {
				addrs[i] = addr
			}
		}
	}

	return
}

// Format an address list to fields.
func FormatAddressList(addrs []*Address) (fields []interface{}) {
	fields = make([]interface{}, len(addrs))

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

	return
}

// 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, ok := fields[1].(string); ok {
		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{}) {
	return []interface{}{
		envelopeDateTime(e.Date),
		encodeHeader(e.Subject),
		FormatAddressList(e.From),
		FormatAddressList(e.Sender),
		FormatAddressList(e.ReplyTo),
		FormatAddressList(e.To),
		FormatAddressList(e.Cc),
		FormatAddressList(e.Bcc),
		e.InReplyTo,
		e.MessageId,
	}
}

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

	// The MIME type.
	MimeType string
	// The MIME subtype.
	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)
				}
				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, _ = fields[0].(string)
		bs.MimeSubType, _ = fields[1].(string)

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

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

		end := 7

		// Type-specific fields
		if bs.MimeType == "message" && 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 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)
				}
				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 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 bs.MimeType == "message" && 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 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
}
