package box

import (
	"bytes"
	"encoding/binary"
	"io"
)

var (
	TypeURL = BoxType{'u', 'r', 'l', ' '}
	TypeURN = BoxType{'u', 'r', 'n', ' '}
)

// aligned(8) class DataEntryUrlBox (bit(24) flags) extends FullBox('url ', version = 0, flags) {
// 	string location;
// }
// aligned(8) class DataEntryUrnBox (bit(24) flags) extends FullBox('urn ', version = 0, flags) {
// 	string name;
// 	string location;
// }
// aligned(8) class DataReferenceBox extends FullBox('dref', version = 0, 0) {
// 	unsigned int(32)  entry_count;
//     for (i=1; i <= entry_count; i++) {
// 		DataEntryBox(entry_version, entry_flags) data_entry;
// 	}
// }

type DataInformationBox struct {
	BaseBox
	Dref *DataReferenceBox
}

type DataReferenceBox struct {
	FullBox
	Entries []IBox
}

type DataEntryUrlBox struct {
	FullBox
	Location string
}

type DataEntryUrnBox struct {
	FullBox
	Name     string
	Location string
}

func CreateDataInformationBox() *DataInformationBox {
	dref := CreateDataReferenceBox()
	return &DataInformationBox{
		BaseBox: BaseBox{
			typ:  TypeDINF,
			size: uint32(BasicBoxLen + dref.size),
		},
		Dref: dref,
	}
}

func CreateDataReferenceBox() *DataReferenceBox {
	url := CreateDataEntryUrlBox("")
	return &DataReferenceBox{
		FullBox: FullBox{
			BaseBox: BaseBox{
				typ:  TypeDREF,
				size: uint32(FullBoxLen + 4 + url.size), // 4 for entry_count
			},
			Version: 0,
			Flags:   [3]byte{0, 0, 0},
		},
		Entries: []IBox{url},
	}
}

func CreateDataEntryUrlBox(location string) *DataEntryUrlBox {
	return &DataEntryUrlBox{
		FullBox: FullBox{
			BaseBox: BaseBox{
				typ:  TypeURL,
				size: uint32(FullBoxLen + len(location)),
			},
			Version: 0,
			Flags:   [3]byte{0, 0, 1}, // self-contained flag
		},
		Location: location,
	}
}

func CreateDataEntryUrnBox(name, location string) *DataEntryUrnBox {
	return &DataEntryUrnBox{
		FullBox: FullBox{
			BaseBox: BaseBox{
				typ:  TypeURN,
				size: uint32(FullBoxLen + len(name) + 1 + len(location)),
			},
			Version: 0,
			Flags:   [3]byte{0, 0, 0},
		},
		Name:     name,
		Location: location,
	}
}

func (box *DataInformationBox) WriteTo(w io.Writer) (n int64, err error) {
	return WriteTo(w, box.Dref)
}

func (box *DataInformationBox) Unmarshal(buf []byte) (b IBox, err error) {
	b, err = ReadFrom(bytes.NewReader(buf))
	if err != nil {
		return nil, err
	}
	if dref, ok := b.(*DataReferenceBox); ok {
		box.Dref = dref
	}
	return box, nil
}

func (box *DataReferenceBox) WriteTo(w io.Writer) (n int64, err error) {
	var tmp [4]byte
	binary.BigEndian.PutUint32(tmp[:], uint32(len(box.Entries)))
	nn, err := w.Write(tmp[:])
	if err != nil {
		return int64(nn), err
	}
	n = int64(nn)

	for _, entry := range box.Entries {
		var en int64
		en, err = WriteTo(w, entry)
		if err != nil {
			return
		}
		n += en
	}
	return
}

func (box *DataReferenceBox) Unmarshal(buf []byte) (IBox, error) {
	if len(buf) < 4 {
		return nil, io.ErrShortBuffer
	}
	entryCount := binary.BigEndian.Uint32(buf)
	r := bytes.NewReader(buf[4:])
	box.Entries = make([]IBox, 0, entryCount)

	for i := uint32(0); i < entryCount; i++ {
		entry, err := ReadFrom(r)
		if err != nil {
			break
		}
		box.Entries = append(box.Entries, entry)
	}
	return box, nil
}

func (box *DataEntryUrlBox) WriteTo(w io.Writer) (n int64, err error) {
	if len(box.Location) > 0 {
		nn, err := w.Write([]byte(box.Location))
		return int64(nn), err
	}
	return 0, nil
}

func (box *DataEntryUrlBox) Unmarshal(buf []byte) (IBox, error) {
	if len(buf) > 0 {
		box.Location = string(buf)
	}
	return box, nil
}

func (box *DataEntryUrnBox) WriteTo(w io.Writer) (n int64, err error) {
	nn, err := w.Write([]byte(box.Name + "\x00" + box.Location))
	return int64(nn), err
}

func (box *DataEntryUrnBox) Unmarshal(buf []byte) (IBox, error) {
	parts := bytes.SplitN(buf, []byte{0}, 2)
	if len(parts) > 0 {
		box.Name = string(parts[0])
		if len(parts) > 1 {
			box.Location = string(parts[1])
		}
	}
	return box, nil
}

func init() {
	RegisterBox[*DataInformationBox](TypeDINF)
	RegisterBox[*DataReferenceBox](TypeDREF)
	RegisterBox[*DataEntryUrlBox](TypeURL)
	RegisterBox[*DataEntryUrnBox](TypeURN)
}
