package izapple2

import (
	"fmt"

	"github.com/ivanizag/izapple2/storage"
)

/*
To implement a smartPort hard drive we have to support the smartPort commands.

See:
	Beneath Prodos, section 6-6, 7-13 and 5-8. (http://www.apple-iigs.info/doc/fichiers/beneathprodos.pdf)
	Apple IIc Technical Reference, 2nd Edition. Chapter 8. https://ia800207.us.archive.org/19/items/AppleIIcTechnicalReference2ndEd/Apple%20IIc%20Technical%20Reference%202nd%20ed.pdf

*/

// SmartPortHardDisk represents a hard disk
type SmartPortHardDisk struct {
	host     *CardSmartPort // For DMA
	filename string
	trace    bool
	disk     *storage.BlockDisk
}

// NewSmartPortHardDisk creates a new hard disk with the smartPort interface
func NewSmartPortHardDisk(host *CardSmartPort, filename string) (*SmartPortHardDisk, error) {
	var d SmartPortHardDisk
	d.host = host
	d.filename = filename

	hd, err := storage.OpenBlockDisk(filename)
	if err != nil {
		return nil, err
	}
	d.disk = hd

	return &d, nil
}

func (d *SmartPortHardDisk) exec(call *smartPortCall) uint8 {
	var result uint8

	switch call.command {
	case smartPortCommandStatus:
		address := call.param16(2)
		result = d.status(address)

	case smartPortCommandReadBlock:
		address := call.param16(2)
		block := call.param24(4)
		result = d.readBlock(block, address)

	case smartPortCommandWriteBlock:
		address := call.param16(2)
		block := call.param24(4)
		result = d.writeBlock(block, address)

	default:
		// Prodos device command not supported
		result = smartPortErrorIO
	}

	if d.trace {
		fmt.Printf("[SmartPortHardDisk] Command %v, return %s \n",
			call, smartPortErrorMessage(result))
	}

	return result
}

func (d *SmartPortHardDisk) readBlock(block uint32, dest uint16) uint8 {
	if d.trace {
		fmt.Printf("[SmartPortHardDisk] Read block %v into $%x.\n", block, dest)
	}

	data, err := d.disk.Read(block)
	if err != nil {
		return smartPortErrorIO
	}

	// Byte by byte transfer to memory using the full Poke code path
	for i := uint16(0); i < uint16(len(data)); i++ {
		d.host.a.mmu.Poke(dest+i, data[i])
	}

	return smartPortNoError
}

func (d *SmartPortHardDisk) writeBlock(block uint32, source uint16) uint8 {
	if d.trace {
		fmt.Printf("[SmartPortHardDisk] Write block %v from $%x.\n", block, source)
	}

	if d.disk.IsReadOnly() {
		return smartPortErrorWriteProtected
	}

	// Byte by byte transfer from memory using the full Peek code path
	buf := make([]uint8, storage.ProDosBlockSize)
	for i := uint16(0); i < uint16(len(buf)); i++ {
		buf[i] = d.host.a.mmu.Peek(source + i)
	}

	err := d.disk.Write(block, buf)
	if err != nil {
		return smartPortErrorIO
	}

	return smartPortNoError
}

func (d *SmartPortHardDisk) status(dest uint16) uint8 {
	if d.trace {
		fmt.Printf("[SmartPortHardDisk] Status into $%x.\n", dest)
	}

	// See http://www.1000bit.it/support/manuali/apple/technotes/smpt/tn.smpt.2.html
	d.host.a.mmu.Poke(dest+0, 0x01) // One device
	d.host.a.mmu.Poke(dest+1, 0xff) // No interrupt
	d.host.a.mmu.Poke(dest+2, 0x00)
	d.host.a.mmu.Poke(dest+3, 0x00) // Unknown manufacturer
	d.host.a.mmu.Poke(dest+4, 0x01)
	d.host.a.mmu.Poke(dest+5, 0x00) // Version 1.0 final
	d.host.a.mmu.Poke(dest+6, 0x00)
	d.host.a.mmu.Poke(dest+7, 0x00) // Reserved

	return smartPortNoError
}
