package disk

import (
	"errors"
	"fmt"
	"regexp"
	"strings"
)

type FileType byte

const (
	FileTypeTXT FileType = 0x00
	FileTypeINT FileType = 0x01
	FileTypeAPP FileType = 0x02
	FileTypeBIN FileType = 0x04
	FileTypeS   FileType = 0x08
	FileTypeREL FileType = 0x10
	FileTypeA   FileType = 0x20
	FileTypeB   FileType = 0x40
)

var AppleDOSTypeMap = map[FileType][2]string{
	0x00: [2]string{"TXT", "ASCII Text"},
	0x01: [2]string{"INT", "Integer Basic Program"},
	0x02: [2]string{"BAS", "Applesoft Basic Program"},
	0x04: [2]string{"BIN", "Binary File"},
	0x08: [2]string{"S", "S File Type"},
	0x10: [2]string{"REL", "Relocatable Object Code"},
	0x20: [2]string{"A", "A File Type"},
	0x40: [2]string{"B", "B File Type"},
}

func (ft FileType) String() string {

	info, ok := AppleDOSTypeMap[ft]
	if ok {
		return info[1]
	}

	return "Unknown"
}

func AppleDOSFileTypeFromExt(ext string) FileType {
	for ft, info := range AppleDOSTypeMap {
		if strings.ToUpper(ext) == info[0] {
			return ft
		}
	}
	return 0x04
}

func (ft FileType) Ext() string {

	info, ok := AppleDOSTypeMap[ft]
	if ok {
		return info[0]
	}

	return "BIN"
}

type FileDescriptor struct {
	Data              []byte
	trackid, sectorid int
	sectoroffset      int
}

func (fd *FileDescriptor) SetData(data []byte, t, s, o int) {

	fd.trackid = t
	fd.sectorid = s
	fd.sectoroffset = o

	if fd.Data == nil && len(data) == 35 {
		fd.Data = data
	}

	for i, v := range data {
		fd.Data[i] = v
	}
}

func (fd *FileDescriptor) Publish(dsk *DSKWrapper) error {

	err := dsk.Seek(fd.trackid, fd.sectorid)
	if err != nil {
		return err
	}
	block := dsk.Read()

	for i, v := range fd.Data {
		block[fd.sectoroffset+i] = v
	}

	dsk.Write(block)

	return nil

}

func (fd *FileDescriptor) IsUnused() bool {
	return fd.Data[0] == 0xff || fd.Data[0] == 0x00 || fd.Type().String() == "Unknown" || fd.TotalSectors() == 0
}

func (fd *FileDescriptor) GetTrackSectorListStart() (int, int) {
	return int(fd.Data[0]), int(fd.Data[1])
}

func (fd *FileDescriptor) IsLocked() bool {
	return (fd.Data[2]&0x80 != 0)
}

func (fd *FileDescriptor) SetLocked(b bool) {
	fd.Data[2] = fd.Data[2] & 0x7f
	if b {
		fd.Data[2] = fd.Data[2] | 0x80
	}
}

func (fd *FileDescriptor) Type() FileType {
	return FileType(fd.Data[2] & 0x7f)
}

func (fd *FileDescriptor) SetType(t FileType) {
	fd.Data[0x02] = byte(t)
}

func AsciiToPoke(b byte) byte {
	if b < 32 || b > 127 {
		b = 32
	}
	return b | 128
}

func (fd *FileDescriptor) SetName(s string) {
	maxlen := len(s)
	if maxlen > 30 {
		maxlen = 30
	}
	for i := 0; i < 30; i++ {
		fd.Data[0x03+i] = 0xa0
	}
	for i, b := range []byte(s) {
		if i >= maxlen {
			break
		}
		fd.Data[0x03+i] = AsciiToPoke(b)
	}
}

func (fd *FileDescriptor) Name() string {
	r := fd.Data[0x03:0x21]
	s := ""
	for _, v := range r {
		ch := PokeToAscii(uint(v), false)
		s = s + string(ch)
	}

	s = strings.ToLower(strings.Trim(s, " "))

	switch fd.Type() {
	case FileTypeAPP:
		s += ".a"
	case FileTypeINT:
		s += ".i"
	case FileTypeBIN:
		s += ".s"
	case FileTypeTXT:
		s += ".t"
	}

	return s
}

func (fd *FileDescriptor) NameUnadorned() string {
	r := fd.Data[0x03:0x21]
	s := ""
	for _, v := range r {
		ch := PokeToAscii(uint(v), false)
		s = s + string(ch)
	}

	s = strings.ToLower(strings.Trim(s, " "))

	return s
}

func (fd *FileDescriptor) NameBytes() []byte {
	return fd.Data[0x03:0x21]
}

func (fd *FileDescriptor) NameOK() bool {
	for _, v := range fd.NameBytes() {
		if v < 32 {
			return false
		}
	}

	return true
}

func (fd *FileDescriptor) TotalSectors() int {
	return int(fd.Data[0x21]) + 256*int(fd.Data[0x22])
}

func (fd *FileDescriptor) SetTotalSectors(v int) {
	fmt.Printf("Call to set sector count to %d\n", v)
	fd.Data[0x21] = byte(v & 0xff)
	fd.Data[0x22] = byte(v / 0x100)
	fmt.Printf("Sector count bytes are %d, %d\n", fd.Data[0x21], fd.Data[0x22])
}

func (fd *FileDescriptor) SetTrackSectorListStart(t, s int) {
	fd.Data[0] = byte(t)
	fd.Data[1] = byte(s)
}

type VTOC struct {
	Data [256]byte
	t, s int
}

func (fd *VTOC) SetData(data []byte, t, s int) {
	fd.t, fd.s = t, s
	for i, v := range data {
		fd.Data[i] = v
	}
}

func (fd *VTOC) GetCatalogStart() (int, int) {
	return int(fd.Data[1]), int(fd.Data[2])
}

func (fd *VTOC) GetDOSVersion() byte {
	return fd.Data[3]
}

func (fd *VTOC) GetVolumeID() byte {
	return fd.Data[6]
}

func (fd *VTOC) GetMaxTSPairsPerSector() int {
	return int(fd.Data[0x27])
}

func (fd *VTOC) GetTracks() int {
	return int(fd.Data[0x34])
}

func (fd *VTOC) GetSectors() int {
	return int(fd.Data[0x35])
}

func (fd *VTOC) GetTrackOrder() int {
	return int(fd.Data[0x31])
}

func (fd *VTOC) BytesPerSector() int {
	size := int(fd.Data[0x36]) + 256*int(fd.Data[0x37])
	if size < 256 {
		size = 256
	}
	return size
}

func (fd *VTOC) IsTSFree(t, s int) bool {
	offset := 0x38 + t*4
	if s < 8 {
		offset++
	}
	bitmask := byte(1 << uint(s&0x7))

	return (fd.Data[offset]&bitmask != 0)
}

// SetTSFree marks a T/S free or not
func (fd *VTOC) SetTSFree(t, s int, b bool) {
	offset := 0x38 + t*4
	if s < 8 {
		offset++
	}
	bitmask := byte(1 << uint(s&0x7))
	clrmask := 0xff ^ bitmask

	v := fd.Data[offset]
	if b {
		v |= bitmask
	} else {
		v &= clrmask
	}

	fd.Data[offset] = v
}

func (fd *VTOC) Publish(dsk *DSKWrapper) error {
	err := dsk.Seek(fd.t, fd.s)
	if err != nil {
		return err
	}

	dsk.Write(fd.Data[:])

	return nil
}

func (fd *VTOC) DumpMap() {

	fmt.Printf("Disk has %d tracks and %d sectors per track, %d bytes per sector (ordering %d)...\n", fd.GetTracks(), fd.GetSectors(), fd.BytesPerSector(), fd.GetTrackOrder())
	fmt.Printf("Volume ID is %d\n", fd.GetVolumeID())
	ct, cs := fd.GetCatalogStart()
	fmt.Printf("Catalog starts at T%d, S%d\n", ct, cs)

	tcount := fd.GetTracks()
	scount := fd.GetSectors()

	for t := 0; t < tcount; t++ {
		fmt.Printf("TRACK %.2x: |", t)
		for s := 0; s < scount; s++ {
			if fd.IsTSFree(t, s) {
				fmt.Print(".")
			} else {
				fmt.Print("X")
			}
		}
		fmt.Println("|")
	}

}

func (dsk *DSKWrapper) IsAppleDOS() (bool, DiskFormat, SectorOrder) {

	oldFormat := dsk.Format
	oldLayout := dsk.Layout

	defer func() {
		dsk.Format = oldFormat
		dsk.Layout = oldLayout
	}()

	if len(dsk.Data) == STD_DISK_BYTES {

		layouts := []SectorOrder{SectorOrderDOS33, SectorOrderDOS33Alt, SectorOrderProDOS, SectorOrderProDOSLinear}

		for _, l := range layouts {

			dsk.Layout = l

			vtoc, err := dsk.AppleDOSGetVTOC()
			if err != nil {
				continue
			}

			if vtoc.GetTracks() != 35 || vtoc.GetSectors() != 16 {
				continue
			}

			_, files, err := dsk.AppleDOSGetCatalog("*")
			if err != nil {
				continue
			}

			if len(files) > 0 {
				return true, GetDiskFormat(DF_DOS_SECTORS_16), l
			}

		}

	} else if len(dsk.Data) == STD_DISK_BYTES_OLD {

		layouts := []SectorOrder{SectorOrderDOS33, SectorOrderDOS33Alt, SectorOrderProDOS, SectorOrderProDOSLinear}

		dsk.Format = GetDiskFormat(DF_DOS_SECTORS_13)

		for _, l := range layouts {
			dsk.Layout = l

			vtoc, err := dsk.AppleDOSGetVTOC()
			if err != nil {
				continue
			}

			if vtoc.GetTracks() != 35 || vtoc.GetSectors() != 13 {
				continue
			}

			_, files, err := dsk.AppleDOSGetCatalog("*")
			if err != nil {
				continue
			}

			if len(files) > 0 {
				return true, GetDiskFormat(DF_DOS_SECTORS_13), l
			}

		}

	}

	return false, oldFormat, oldLayout

}

func (d *DSKWrapper) AppleDOSReadFileRaw(fd FileDescriptor) (int, int, []byte, error) {

	data, e := d.AppleDOSReadFileSectors(fd, -1)

	if e != nil || len(data) == 0 {
		return 0, 0, data, e
	}

	switch fd.Type() {
	case FileTypeINT:
		l := int(data[0]) + 256*int(data[1])
		if l+2 > len(data) {
			l = len(data) - 2
		}
		return l, 0x801, data[2 : 2+l], nil
	case FileTypeAPP:
		l := int(data[0]) + 256*int(data[1])
		if l+2 > len(data) {
			l = len(data) - 2
		}
		return l, 0x801, data[2 : 2+l], nil
	case FileTypeTXT:
		return len(data), 0x0000, data, nil
	case FileTypeBIN:
		addr := int(data[0]) + 256*int(data[1])
		l := int(data[2]) + 256*int(data[3])
		if l+4 > len(data) {
			l = len(data) - 4
		}
		//fmt.Printf("%x, %x, %x\n", l, addr, len(data))
		return l, addr, data[4 : 4+l], nil
	default:
		l := int(data[0]) + 256*int(data[1])
		if l+2 > len(data) {
			l = len(data) - 2
		}
		return l, 0, data[2 : 2+l], nil
	}

}

func (d *DSKWrapper) AppleDOSGetVTOC() (*VTOC, error) {
	e := d.Seek(17, 0)
	if e != nil {
		return nil, e
	}
	data := d.Read()

	vtoc := &VTOC{}
	vtoc.SetData(data, 17, 0)
	return vtoc, nil
}

func (dsk *DSKWrapper) AppleDOSUsedBitmap() ([]bool, error) {

	var out []bool = make([]bool, dsk.Format.TPD()*dsk.Format.SPT())

	_, files, err := dsk.AppleDOSGetCatalog("*")
	if err != nil {
		return out, err
	}

	for _, f := range files {
		tslist, err := dsk.AppleDOSGetFileSectors(f, 0)
		if err == nil {
			for _, pair := range tslist {
				track := pair[0]
				sector := pair[1]
				out[track*dsk.Format.SPT()+sector] = true
			}
		}
	}

	return out, nil

}

func (d *DSKWrapper) AppleDOSGetCatalog(pattern string) (*VTOC, []FileDescriptor, error) {

	var files []FileDescriptor
	var e error
	var vtoc *VTOC

	vtoc, e = d.AppleDOSGetVTOC()
	if e != nil {
		return vtoc, files, e
	}

	count := 0
	ct, cs := vtoc.GetCatalogStart()

	e = d.Seek(ct, cs)
	if e != nil {
		return vtoc, files, e
	}

	data := d.Read()

	var re *regexp.Regexp
	if pattern != "" {
		patterntmp := strings.Replace(pattern, ".", "[.]", -1)
		patterntmp = strings.Replace(patterntmp, "*", ".*", -1)
		patterntmp = "(?i)^" + patterntmp + "$"
		re = regexp.MustCompile(patterntmp)
	}

	for e == nil && count < 105 {
		slot := count % 7
		pos := 0x0b + 35*slot

		fd := FileDescriptor{}
		fd.SetData(data[pos:pos+35], ct, cs, pos)

		var skipname bool = false
		if re != nil {
			skipname = !re.MatchString(fd.Name())
		}

		if fd.Data[0] != 0xff && fd.Data[0] != 0x00 && fd.Type().String() != "Unknown" && !skipname {
			files = append(files, fd)
		}
		count++
		if count%7 == 0 {
			// move to next catalog sector
			ct = int(data[1])
			cs = int(data[2])
			if ct == 0 {
				return vtoc, files, nil
			}
			e = d.Seek(ct, cs)
			if e != nil {
				return vtoc, files, e
			}
			data = d.Read()
		}
	}

	return vtoc, files, nil
}

func (d *DSKWrapper) AppleDOSGetFileSectors(fd FileDescriptor, maxblocks int) ([][2]int, error) {
	var e error
	var data []byte
	tl, sl := fd.GetTrackSectorListStart()

	// var tracks []int
	// var sectors []int

	var tslist [][2]int

	var tsmap = make(map[int]int)

	for e == nil && (tl != 0 || sl != 0) {
		// Get TS List
		e = d.Seek(tl, sl)
		if e != nil {
			return tslist, e
		}
		data = d.Read()

		//fmt.Printf("DEBUG: T/S List follows from T%d, S%d:\n", tl, sl)
		//Dump(data)

		ptr := 0x0c
		for ptr < 0x100 {
			// check entry
			t, s := int(data[ptr]), int(data[ptr+1])

			if t == 0 && s == 0 || t >= d.Format.TPD() || s >= d.Format.SPT() {
				//fmt.Println("BREAK ptr =", ptr, len(tracks))
				break
			}

			//fmt.Printf("File block at T%d, S%d\n", t, s)

			// tracks = append(tracks, t)
			// sectors = append(sectors, s)

			tslist = append(tslist, [2]int{t, s})

			// next entry
			ptr += 2
		}

		// get next TS List block
		ntl, nsl := int(data[1]), int(data[2])
		if _, ex := tsmap[100*ntl+nsl]; ex {
			//fmt.Printf("circular ts list")
			break
		}

		tl, sl = ntl, nsl

		tsmap[100*tl+sl] = 1

		//fmt.Printf("Next Track Sector list is at T%d, S%d (%d)\n", tl, sl, len(tracks))

	}

	return tslist, nil
}

func (d *DSKWrapper) AppleDOSReadFileSectors(fd FileDescriptor, maxblocks int) ([]byte, error) {
	var e error
	var data []byte
	var file []byte
	tl, sl := fd.GetTrackSectorListStart()

	var tracks []int
	var sectors []int

	var tsmap = make(map[int]int)

	for e == nil && (tl != 0 || sl != 0) {
		// Get TS List
		e = d.Seek(tl, sl)
		if e != nil {
			return file, e
		}
		data = d.Read()

		//fmt.Printf("DEBUG: T/S List follows from T%d, S%d:\n", tl, sl)
		//Dump(data)

		ptr := 0x0c
		for ptr < 0x100 {
			// check entry
			t, s := int(data[ptr]), int(data[ptr+1])

			if t == 0 && s == 0 || t >= d.Format.TPD() || s >= d.Format.SPT() {
				//fmt.Println("BREAK ptr =", ptr, len(tracks))
				break
			}

			//fmt.Printf("File block at T%d, S%d\n", t, s)

			tracks = append(tracks, t)
			sectors = append(sectors, s)

			// next entry
			ptr += 2
		}

		// get next TS List block
		ntl, nsl := int(data[1]), int(data[2])
		if _, ex := tsmap[100*ntl+nsl]; ex {
			//fmt.Printf("circular ts list")
			break
		}

		tl, sl = ntl, nsl

		tsmap[100*tl+sl] = 1

		//fmt.Printf("Next Track Sector list is at T%d, S%d (%d)\n", tl, sl, len(tracks))

	}

	// Here got T/S list
	//fmt.Println("READING FILE")
	blocksread := 0
	for i, t := range tracks {
		s := sectors[i]

		//fmt.Printf("TS Fetch #%d: Track %d, %d\n", i, t, s)

		e = d.Seek(t, s)
		if e != nil {
			return file, e
		}
		c := d.Read()

		//Dump(c)

		file = append(file, c...)
		blocksread++

		if maxblocks != -1 && blocksread >= maxblocks {
			break
		}
	}

	return file, nil
}

func (d *DSKWrapper) AppleDOSGetTSListSectors(fd FileDescriptor, maxblocks int) ([][2]int, error) {
	var e error
	var data []byte

	tl, sl := fd.GetTrackSectorListStart()

	var tslist [][2]int

	var tsmap = make(map[int]int)

	for e == nil && (tl != 0 || sl != 0) {
		// Get TS List
		e = d.Seek(tl, sl)
		if e != nil {
			return tslist, e
		}
		data = d.Read()

		tslist = append(tslist, [2]int{tl, sl})

		// get next TS List block
		ntl, nsl := int(data[1]), int(data[2])
		if _, ex := tsmap[100*ntl+nsl]; ex {
			break
		}

		tl, sl = ntl, nsl

		tsmap[100*tl+sl] = 1

	}

	return tslist, nil
}

func (d *DSKWrapper) AppleDOSReadFile(fd FileDescriptor) (int, int, []byte, error) {

	data, e := d.AppleDOSReadFileSectors(fd, -1)

	if e != nil {
		return 0, 0, data, e
	}

	switch fd.Type() {
	case FileTypeINT:
		l := int(data[0]) + 256*int(data[1])
		return l, 0x801, IntegerDetoks(data[2 : 2+l]), nil
	case FileTypeAPP:
		l := int(data[0]) + 256*int(data[1])
		return l, 0x801, ApplesoftDetoks(data[2 : 2+l]), nil
	case FileTypeTXT:
		return len(data), 0x0000, data, nil
	case FileTypeBIN:
		addr := int(data[0]) + 256*int(data[1])
		l := int(data[2]) + 256*int(data[3])
		//fmt.Printf("%x, %x, %x\n", l, addr, len(data))
		return l, addr, data[4 : 4+l], nil
	default:
		l := int(data[0]) + 256*int(data[1])
		return l, 0, data[2 : 2+l], nil
	}

}

// AppleDOSGetFreeSectors tries to find free sectors for certain size file...
// Remember, we need space for the T/S list as well...
func (dsk *DSKWrapper) AppleDOSGetFreeSectors(size int) ([][2]int, [][2]int, error) {

	needed := make([][2]int, 0)
	vtoc, err := dsk.AppleDOSGetVTOC()
	if err != nil {
		return nil, nil, err
	}

	catTrack, _ := vtoc.GetCatalogStart()

	// needed:
	// size/256 + 1 for data
	// 1 for T/S list
	dataBlocks := (size / 256) + 1
	tsListBlocks := (dataBlocks / vtoc.GetMaxTSPairsPerSector()) + 1
	totalBlocks := tsListBlocks + dataBlocks

	for t := dsk.Format.TPD() - 1; t >= 0; t-- {

		if t == catTrack {
			continue // skip catalog track
		}

		for s := dsk.Format.SPT() - 1; s >= 0; s-- {

			if len(needed) >= totalBlocks {
				break
			}

			if vtoc.IsTSFree(t, s) {
				needed = append(needed, [2]int{t, s})
			}
		}

	}

	if len(needed) >= totalBlocks {
		return needed[:tsListBlocks], needed[tsListBlocks:], nil
	}

	return nil, nil, errors.New("Insufficent space")

}

func (d *DSKWrapper) AppleDOSNextFreeCatalogEntry(name string) (*FileDescriptor, error) {

	var e error
	var vtoc *VTOC

	vtoc, e = d.AppleDOSGetVTOC()
	if e != nil {
		return nil, e
	}

	count := 0
	ct, cs := vtoc.GetCatalogStart()

	e = d.Seek(ct, cs)
	if e != nil {
		return nil, e
	}

	data := d.Read()

	for e == nil && count < 105 {
		fmt.Printf("AppleDOSNextFreeCatalogEntry: checking entry %d\n", count)
		slot := count % 7
		pos := 0x0b + 35*slot

		fd := FileDescriptor{}
		fd.SetData(data[pos:pos+35], ct, cs, pos)

		if fd.IsUnused() {
		    fmt.Printf("Is unused\n")
			return &fd, nil
		} else if name != "" && strings.ToLower(fd.NameUnadorned()) == strings.ToLower(name) {
			fmt.Printf("Name match found\n")
			return &fd, nil
		}
		count++
		if count%7 == 0 {
			// move to next catalog sector
			ct = int(data[1])
			cs = int(data[2])
			if ct == 0 {
				return nil, nil
			}
			e = d.Seek(ct, cs)
			if e != nil {
				return nil, e
			}
			data = d.Read()
		}
	}

	return nil, errors.New("No free entry")
}

func (d *DSKWrapper) AppleDOSNamedCatalogEntry(name string) (*FileDescriptor, error) {

	var e error
	var vtoc *VTOC

	vtoc, e = d.AppleDOSGetVTOC()
	if e != nil {
		return nil, e
	}

	count := 0
	ct, cs := vtoc.GetCatalogStart()

	e = d.Seek(ct, cs)
	if e != nil {
		return nil, e
	}

	data := d.Read()

	for e == nil && count < 105 {
		slot := count % 7
		pos := 0x0b + 35*slot

		fd := FileDescriptor{}
		fd.SetData(data[pos:pos+35], ct, cs, pos)

		//fmt.Printf("FILE NAME CHECK [%s] vs [%s]\n", strings.ToLower(fd.NameUnadorned()), strings.ToLower(name))

		if name != "" && strings.ToLower(fd.NameUnadorned()) == strings.ToLower(name) {
			return &fd, nil
		}
		count++
		if count%7 == 0 {
			// move to next catalog sector
			ct = int(data[1])
			cs = int(data[2])
			if ct == 0 {
				return nil, errors.New("Not found")
			}
			e = d.Seek(ct, cs)
			if e != nil {
				return nil, e
			}
			data = d.Read()
		}
	}

	return nil, errors.New("Not found")
}

func (dsk *DSKWrapper) AppleDOSWriteFile(name string, kind FileType, data []byte, loadAddr int) error {

	name = strings.ToUpper(name)

	vtoc, err := dsk.AppleDOSGetVTOC()
	if err != nil {
		return err
	}

	fmt.Printf("Writing file with type %s\n", kind.Ext())

	if kind & FileTypeBIN != 0 {
		l := len(data)
		fmt.Printf("Length is %d\n", l)
		header := []byte{byte(l % 256), byte(l / 256)}
		data = append(header, data...)
	}

	if kind & FileTypeTXT == 0 {
		header := []byte{byte(loadAddr % 256), byte(loadAddr / 256)}
		data = append(header, data...)
	}

	// try get catalog entry - delete existing if match found
	fd, err := dsk.AppleDOSNamedCatalogEntry(name)
	if err == nil {
		if kind != fd.Type() {
			return errors.New("File type mismatch")
		} else {
			// need to delete this file...
			err = dsk.AppleDOSRemoveFile(fd)
			if err != nil {
				return err
			}
			// reread vtoc here
			vtoc, err = dsk.AppleDOSGetVTOC()
			if err != nil {
				return err
			}
		}
	} else {
		fd, err = dsk.AppleDOSNextFreeCatalogEntry(name)
		if err != nil {
			return err
		}
	}

	// 1st: check we have sufficient space...
	tsBlocks, dataBlocks, err := dsk.AppleDOSGetFreeSectors(len(data))
	if err != nil {
		return err
	}

	// 2nd: check we can get a free catalog entry
	sectorCount := len(dataBlocks) + len(tsBlocks)
	
	// 3rd: Write the datablocks
	var block int = 0
	for len(data) > 0 {

		max := STD_BYTES_PER_SECTOR
		if len(data) < STD_BYTES_PER_SECTOR {
			max = len(data)
		}
		chunk := data[:max]
		// Pad final sector with 0x00 bytes
		for len(chunk) < STD_BYTES_PER_SECTOR {
			chunk = append(chunk, 0x00)
		}
		data = data[max:]

		pair := dataBlocks[block]

		track, sector := pair[0], pair[1]

		err = dsk.Seek(track, sector)
		if err != nil {
			return err
		}
		dsk.Write(chunk)

		block++

	}

	// 4th: Write the T/S List
	offset := 0
	for blockIdx, block := range tsBlocks {
		listTrack, listSector := block[0], block[1]
		nextTrack, nextSector := 0, 0
		if blockIdx < len(tsBlocks)-1 {
			nextTrack, nextSector = tsBlocks[blockIdx+1][0], tsBlocks[blockIdx+1][1]
		}

		buffer := make([]byte, STD_BYTES_PER_SECTOR)

		// header
		buffer[0x01] = byte(nextTrack)
		buffer[0x02] = byte(nextSector)
		buffer[0x05] = byte(offset & 0xff)
		buffer[0x06] = byte(offset / 0x100)

		//
		count := vtoc.GetMaxTSPairsPerSector()
		if offset+count >= len(dataBlocks) {
			count = len(dataBlocks) - offset
		}

		for i := 0; i < count; i++ {
			pos := 0x0c + i*2
			buffer[pos+0x00] = byte(dataBlocks[offset+i][0])
			buffer[pos+0x01] = byte(dataBlocks[offset+i][1])
			vtoc.SetTSFree(dataBlocks[offset+i][0], dataBlocks[offset+i][1], false)
		}

		// Write the sector
		err = dsk.Seek(listTrack, listSector)
		if err != nil {
			return err
		}
		dsk.Write(buffer)
		vtoc.SetTSFree(listTrack, listSector, false)
	}

	err = vtoc.Publish(dsk)
	if err != nil {
		return err
	}

	// 5th and finally: Let's make that catalog entry
	fd.SetName(name)
	fd.SetTrackSectorListStart(tsBlocks[0][0], tsBlocks[0][1])
	fd.SetType(kind)
	fd.SetTotalSectors(sectorCount)
	fd.Publish(dsk)

	fmt.Printf("Added file %s (kind %d)\n", name, kind)

	return nil

}

func (d *DSKWrapper) AppleDOSRemoveFile(fd *FileDescriptor) error {

	vtoc, err := d.AppleDOSGetVTOC()
	if err != nil {
		return err
	}

	if fd.IsUnused() {
		return errors.New("File does not exist")
	}

	tsBlocks, e := d.AppleDOSGetTSListSectors(*fd, -1)
	if e != nil {
		return e
	}

	dataBlocks, e := d.AppleDOSGetFileSectors(*fd, -1)
	if e != nil {
		return e
	}

	for _, pair := range dataBlocks {
		vtoc.SetTSFree(pair[0], pair[1], true)
	}

	for _, pair := range tsBlocks {
		vtoc.SetTSFree(pair[0], pair[1], true)
	}

	vtoc.Publish(d)

	fd.Data[0x00] = 0xff
	fd.SetName("")
	return fd.Publish(d)

}

func (dsk *DSKWrapper) AppleDOSDeleteFile(name string) error {

	vtoc, err := dsk.AppleDOSGetVTOC()
	if err != nil {
		return err
	}

	// We cheat here a bit and use the get first free entry call with
	// autogrow turned off.
	fd, err := dsk.AppleDOSNamedCatalogEntry(name)
	if err != nil {
		return err
	}

	if fd.IsUnused() {
		return errors.New("Not found")
	}

	// At this stage we have a match so get blocks to remove
	tsBlocks, e := dsk.AppleDOSGetTSListSectors(*fd, -1)
	if e != nil {
		return e
	}

	dataBlocks, e := dsk.AppleDOSGetFileSectors(*fd, -1)
	if e != nil {
		return e
	}

	for _, pair := range dataBlocks {
		vtoc.SetTSFree(pair[0], pair[1], true)
	}

	for _, pair := range tsBlocks {
		vtoc.SetTSFree(pair[0], pair[1], true)
	}

	err = vtoc.Publish(dsk)
	if err != nil {
		return err
	}

	fd.Data[0x00] = 0xff
	fd.SetName("")
	return fd.Publish(dsk)

}

func (dsk *DSKWrapper) AppleDOSSetLocked(name string, lock bool) error {

	// We cheat here a bit and use the get first free entry call with
	// autogrow turned off.
	fd, err := dsk.AppleDOSNamedCatalogEntry(name)
	if err != nil {
		return err
	}

	if fd.IsUnused() {
		return errors.New("Not found")
	}

	fd.SetLocked(lock)
	return fd.Publish(dsk)

}

func (dsk *DSKWrapper) AppleDOSRenameFile(name, newname string) error {

	fd, err := dsk.AppleDOSNamedCatalogEntry(name)
	if err != nil {
		return err
	}

	_, err = dsk.AppleDOSNamedCatalogEntry(newname)
	if err == nil {
		return errors.New("New name already exists")
	}

	// can rename here
	fd.SetName(newname)
	return fd.Publish(dsk)

}
