package fat

import (
	"errors"
	"fmt"
	"math"

	"github.com/mitchellh/go-fs"
)

// The first cluster that can really hold user data is always 2
const FirstCluster = 2

// FAT is the actual file allocation table data structure that is
// stored on disk to describe the various clusters on the disk.
type FAT struct {
	bs      *BootSectorCommon
	entries []uint32
}

func DecodeFAT(device fs.BlockDevice, bs *BootSectorCommon, n int) (*FAT, error) {
	if n > int(bs.NumFATs) {
		return nil, fmt.Errorf("FAT #%d greater than total FATs: %d", n, bs.NumFATs)
	}

	data := make([]byte, bs.SectorsPerFat*uint32(bs.BytesPerSector))
	if _, err := device.ReadAt(data, int64(bs.FATOffset(n))); err != nil {
		return nil, err
	}

	result := &FAT{
		bs:      bs,
		entries: make([]uint32, FATEntryCount(bs)),
	}

	fatType := bs.FATType()
	for i := 0; i < int(FATEntryCount(bs)); i++ {
		var entryData uint32
		switch fatType {
		case FAT12:
			entryData = fatReadEntry12(data, i)
		case FAT16:
			entryData = fatReadEntry16(data, i)
		default:
			entryData = fatReadEntry32(data, i)
		}

		result.entries[i] = entryData
	}

	return result, nil
}

// NewFAT creates a new FAT data structure, properly initialized.
func NewFAT(bs *BootSectorCommon) (*FAT, error) {
	result := &FAT{
		bs:      bs,
		entries: make([]uint32, FATEntryCount(bs)),
	}

	// Set the initial two entries according to spec
	result.entries[0] = (uint32(bs.Media) & 0xFF) |
		(0xFFFFFF00 & result.entryMask())
	result.entries[1] = 0xFFFFFFFF & result.entryMask()

	return result, nil
}

// Bytes returns the raw bytes for the FAT that should be written to
// the block device.
func (f *FAT) Bytes() []byte {
	result := make([]byte, f.bs.SectorsPerFat*uint32(f.bs.BytesPerSector))

	for i, entry := range f.entries {
		switch f.bs.FATType() {
		case FAT12:
			f.writeEntry12(result, i, entry)
		case FAT16:
			f.writeEntry16(result, i, entry)
		default:
			f.writeEntry32(result, i, entry)
		}
	}

	return result
}

func (f *FAT) AllocChain() (uint32, error) {
	return f.allocNew()
}

func (f *FAT) allocNew() (uint32, error) {
	dataSize := (f.bs.TotalSectors * uint32(f.bs.BytesPerSector))
	dataSize -= f.bs.DataOffset()
	clusterCount := dataSize / f.bs.BytesPerCluster()
	lastClusterIndex := clusterCount + FirstCluster

	var availIdx uint32
	found := false
	for i := uint32(FirstCluster); i < lastClusterIndex; i++ {
		if f.entries[i] == 0 {
			availIdx = i
			found = true
			break
		}
	}

	if !found {
		return 0, errors.New("FAT FULL")
	}

	// Mark that this is now in use
	f.entries[availIdx] = 0xFFFFFFFF & f.entryMask()

	return availIdx, nil
}

// Chain returns the chain of clusters starting at a certain cluster.
func (f *FAT) Chain(start uint32) []uint32 {
	chain := make([]uint32, 0, 2)

	cluster := start
	for {
		chain = append(chain, cluster)
		cluster = f.entries[cluster]

		if f.isEofCluster(cluster) || cluster == 0 {
			break
		}
	}

	return chain
}

// ResizeChain takes a given cluster number and resizes the chain
// to the given length. It returns the new chain of clusters.
func (f *FAT) ResizeChain(start uint32, length int) ([]uint32, error) {
	chain := f.Chain(start)
	if len(chain) == length {
		return chain, nil
	}

	change := int(math.Abs(float64(length - len(chain))))
	if length > len(chain) {
		var lastCluster uint32

		lastCluster = chain[0]
		for i := 1; i < len(chain); i++ {
			if f.isEofCluster(f.entries[lastCluster]) {
				break
			}

			lastCluster = chain[i]
		}

		for i := 0; i < change; i++ {
			newCluster, err := f.allocNew()
			if err != nil {
				return nil, err
			}

			f.entries[lastCluster] = newCluster
			lastCluster = newCluster
		}
	} else {
		panic("making chains smaller not implemented yet")
	}

	return f.Chain(start), nil
}

func (f *FAT) WriteToDevice(device fs.BlockDevice) error {
	fatBytes := f.Bytes()
	for i := 0; i < int(f.bs.NumFATs); i++ {
		offset := int64(f.bs.FATOffset(i))
		if _, err := device.WriteAt(fatBytes, offset); err != nil {
			return err
		}
	}

	return nil
}

func (f *FAT) entryMask() uint32 {
	switch f.bs.FATType() {
	case FAT12:
		return 0x0FFF
	case FAT16:
		return 0xFFFF
	default:
		return 0x0FFFFFFF
	}
}

func (f *FAT) isEofCluster(cluster uint32) bool {
	return cluster >= (0xFFFFFF8 & f.entryMask())
}

func (f *FAT) writeEntry12(data []byte, idx int, entry uint32) {
	dataIdx := idx + (idx / 2)
	data = data[dataIdx : dataIdx+2]

	if idx%2 == 1 {
		// ODD
		data[0] |= byte((entry & 0x0F) << 4)
		data[1] = byte((entry >> 4) & 0xFF)
	} else {
		// Even
		data[0] = byte(entry & 0xFF)
		data[1] = byte((entry >> 8) & 0x0F)
	}
}

func (f *FAT) writeEntry16(data []byte, idx int, entry uint32) {
	idx <<= 1
	data[idx] = byte(entry & 0xFF)
	data[idx+1] = byte((entry >> 8) & 0xFF)
}

func (f *FAT) writeEntry32(data []byte, idx int, entry uint32) {
	idx <<= 2
	data[idx] = byte(entry & 0xFF)
	data[idx+1] = byte((entry >> 8) & 0xFF)
	data[idx+2] = byte((entry >> 16) & 0xFF)
	data[idx+3] = byte((entry >> 24) & 0xFF)
}

// FATEntryCount returns the number of entries per fat for the given
// boot sector.
func FATEntryCount(bs *BootSectorCommon) uint32 {
	// Determine the number of entries that'll go in the FAT.
	var entryCount uint32 = bs.SectorsPerFat * uint32(bs.BytesPerSector)
	switch bs.FATType() {
	case FAT12:
		entryCount = uint32((uint64(entryCount) * 8) / 12)
	case FAT16:
		entryCount /= 2
	case FAT32:
		entryCount /= 4
	default:
		panic("impossible fat type")
	}

	return entryCount
}

func fatReadEntry12(data []byte, idx int) uint32 {
	idx += idx / 2

	var result uint32 = (uint32(data[idx+1]) << 8) | uint32(data[idx])
	if idx%2 == 0 {
		return result & 0xFFF
	} else {
		return result >> 4
	}
}

func fatReadEntry16(data []byte, idx int) uint32 {
	idx <<= 1
	return (uint32(data[idx+1]) << 8) | uint32(data[idx])
}

func fatReadEntry32(data []byte, idx int) uint32 {
	idx <<= 2
	return (uint32(data[idx+3]) << 24) |
		(uint32(data[idx+2]) << 16) |
		(uint32(data[idx+1]) << 8) |
		uint32(data[idx+0])
}
