// Copyright 2015 The Go 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 binres

// NodeHeader is header all xml node types have, providing additional
// information regarding an xml node over binChunkHeader.
type NodeHeader struct {
	chunkHeader
	LineNumber uint32  // line number in source file this element appears
	Comment    PoolRef // optional xml comment associated with element, MaxUint32 if none
}

// UnmarshalBinary creates a node header from binary data
func (hdr *NodeHeader) UnmarshalBinary(bin []byte) error {
	if err := (&hdr.chunkHeader).UnmarshalBinary(bin); err != nil {
		return err
	}
	hdr.LineNumber = btou32(bin[8:])
	hdr.Comment = PoolRef(btou32(bin[12:]))
	return nil
}

// MarshalBinary outputs the binary format from a given header
func (hdr *NodeHeader) MarshalBinary() ([]byte, error) {
	bin := make([]byte, 16)
	b, err := hdr.chunkHeader.MarshalBinary()
	if err != nil {
		return nil, err
	}
	copy(bin, b)
	putu32(bin[8:], hdr.LineNumber)
	putu32(bin[12:], uint32(hdr.Comment))
	return bin, nil
}

// Namespace represents an XML namespace
type Namespace struct {
	NodeHeader
	prefix PoolRef
	uri    PoolRef

	end *Namespace // TODO don't let this type be recursive
}

// UnmarshalBinary creates a namespace from binary data
func (ns *Namespace) UnmarshalBinary(bin []byte) error {
	if err := (&ns.NodeHeader).UnmarshalBinary(bin); err != nil {
		return err
	}
	buf := bin[ns.headerByteSize:]
	ns.prefix = PoolRef(btou32(buf))
	ns.uri = PoolRef(btou32(buf[4:]))
	return nil
}

// MarshalBinary outputs the binary format from a given namespace
func (ns *Namespace) MarshalBinary() ([]byte, error) {
	if ns.end == nil {
		ns.typ = ResXMLEndNamespace
	} else {
		ns.typ = ResXMLStartNamespace
	}
	ns.headerByteSize = 16
	ns.byteSize = 24

	bin := make([]byte, ns.byteSize)
	b, err := ns.NodeHeader.MarshalBinary()
	if err != nil {
		return nil, err
	}
	copy(bin, b)
	putu32(bin[16:], uint32(ns.prefix))
	putu32(bin[20:], uint32(ns.uri))
	return bin, nil
}

// Element represents an XML element
type Element struct {
	NodeHeader
	NS             PoolRef
	Name           PoolRef // name of node if element, otherwise chardata if CDATA
	AttributeStart uint16  // byte offset where attrs start
	AttributeSize  uint16  // byte size of attrs
	AttributeCount uint16  // length of attrs
	IDIndex        uint16  // Index (1-based) of the "id" attribute. 0 if none.
	ClassIndex     uint16  // Index (1-based) of the "class" attribute. 0 if none.
	StyleIndex     uint16  // Index (1-based) of the "style" attribute. 0 if none.

	attrs    []*Attribute
	Children []*Element
	end      *ElementEnd

	head, tail *CharData
}

// UnmarshalBinary creates an element from binary data
func (el *Element) UnmarshalBinary(buf []byte) error {
	if err := (&el.NodeHeader).UnmarshalBinary(buf); err != nil {
		return err
	}
	buf = buf[el.headerByteSize:]
	el.NS = PoolRef(btou32(buf))
	el.Name = PoolRef(btou32(buf[4:]))

	el.AttributeStart = btou16(buf[8:])
	el.AttributeSize = btou16(buf[10:])
	el.AttributeCount = btou16(buf[12:])
	el.IDIndex = btou16(buf[14:])
	el.ClassIndex = btou16(buf[16:])
	el.StyleIndex = btou16(buf[18:])

	buf = buf[el.AttributeStart:]
	el.attrs = make([]*Attribute, int(el.AttributeCount))
	for i := range el.attrs {
		attr := new(Attribute)
		if err := attr.UnmarshalBinary(buf); err != nil {
			return err
		}
		el.attrs[i] = attr
		buf = buf[el.AttributeSize:]
	}

	return nil
}

// MarshalBinary outputs the binary format from a given element
func (el *Element) MarshalBinary() ([]byte, error) {
	el.typ = ResXMLStartElement
	el.headerByteSize = 16
	el.AttributeSize = 20
	el.AttributeStart = 20
	el.AttributeCount = uint16(len(el.attrs))
	el.IDIndex = 0
	el.ClassIndex = 0
	el.StyleIndex = 0
	el.byteSize = uint32(el.headerByteSize) + uint32(el.AttributeStart) + uint32(len(el.attrs)*int(el.AttributeSize))

	bin := make([]byte, el.byteSize)
	b, err := el.NodeHeader.MarshalBinary()
	if err != nil {
		return nil, err
	}
	copy(bin, b)
	putu32(bin[16:], uint32(el.NS))
	putu32(bin[20:], uint32(el.Name))
	putu16(bin[24:], el.AttributeStart)
	putu16(bin[26:], el.AttributeSize)
	putu16(bin[28:], el.AttributeCount)
	putu16(bin[30:], el.IDIndex)
	putu16(bin[32:], el.ClassIndex)
	putu16(bin[34:], el.StyleIndex)

	buf := bin[36:]
	for _, attr := range el.attrs {
		b, err := attr.MarshalBinary()
		if err != nil {
			return nil, err
		}
		copy(buf, b)
		buf = buf[int(el.AttributeSize):]
	}

	return bin, nil
}

// ElementEnd marks the end of an element node, either Element or CharData.
type ElementEnd struct {
	NodeHeader
	NS   PoolRef
	Name PoolRef // name of node if binElement, raw chardata if binCharData
}

// UnmarshalBinary creates the element end from binary data
func (el *ElementEnd) UnmarshalBinary(bin []byte) error {
	err := (&el.NodeHeader).UnmarshalBinary(bin)
	if err != nil {
		return err
	}

	buf := bin[el.headerByteSize:]
	el.NS = PoolRef(btou32(buf))
	el.Name = PoolRef(btou32(buf[4:]))
	return nil
}

// MarshalBinary outputs the binary format from a given element end
func (el *ElementEnd) MarshalBinary() ([]byte, error) {
	el.typ = ResXMLEndElement
	el.headerByteSize = 16
	el.byteSize = 24

	bin := make([]byte, 24)
	b, err := el.NodeHeader.MarshalBinary()
	if err != nil {
		return nil, err
	}
	copy(bin, b)
	putu32(bin[16:], uint32(el.NS))
	putu32(bin[20:], uint32(el.Name))
	return bin, nil
}

// Attribute represents an XML attribute
type Attribute struct {
	NS         PoolRef
	Name       PoolRef
	RawValue   PoolRef // The original raw string value of this attribute.
	TypedValue Data    // Processesd typed value of this attribute.
}

// UnmarshalBinary creates the attribute end from binary data
func (attr *Attribute) UnmarshalBinary(bin []byte) error {
	attr.NS = PoolRef(btou32(bin))
	attr.Name = PoolRef(btou32(bin[4:]))
	attr.RawValue = PoolRef(btou32(bin[8:]))
	return (&attr.TypedValue).UnmarshalBinary(bin[12:])
}

// MarshalBinary outputs the binary format from a given attribute
func (attr *Attribute) MarshalBinary() ([]byte, error) {
	bin := make([]byte, 20)
	putu32(bin, uint32(attr.NS))
	putu32(bin[4:], uint32(attr.Name))
	putu32(bin[8:], uint32(attr.RawValue))
	b, err := attr.TypedValue.MarshalBinary()
	if err != nil {
		return nil, err
	}
	copy(bin[12:], b)
	return bin, nil
}

// CharData represents a CDATA node and includes ref to node's text value.
type CharData struct {
	NodeHeader
	RawData   PoolRef // raw character data
	TypedData Data    // typed value of character data
}

// UnmarshalBinary creates the character data from binary data
func (cdt *CharData) UnmarshalBinary(bin []byte) error {
	if err := (&cdt.NodeHeader).UnmarshalBinary(bin); err != nil {
		return err
	}
	buf := bin[cdt.headerByteSize:]
	cdt.RawData = PoolRef(btou32(buf))
	return (&cdt.TypedData).UnmarshalBinary(buf[4:])
}

// MarshalBinary outputs the binary format from given character data
func (cdt *CharData) MarshalBinary() ([]byte, error) {
	cdt.typ = ResXMLCharData
	cdt.headerByteSize = 16
	cdt.byteSize = 28

	bin := make([]byte, cdt.byteSize)
	b, err := cdt.NodeHeader.MarshalBinary()
	if err != nil {
		return nil, err
	}
	copy(bin, b)
	putu32(bin[16:], uint32(cdt.RawData))
	b, err = cdt.TypedData.MarshalBinary()
	if err != nil {
		return nil, err
	}
	copy(bin[20:], b)
	return bin, nil
}
