package mshal

import (
	"bytes"
	"encoding/hex"
	"errors"
	"time"
)

type cbApplyParamType func(h *HAL, out []byte) error
type cbPostExchangeType func(h *HAL, addr int, buf []byte) error

type romCommand struct {
	id byte

	is16bit    bool
	isWrite    bool
	maxPayload int
	offset     int
	addrShift  int

	cbApplyParam   cbApplyParamType
	cbPostExchange cbPostExchangeType
}

func romCommandMake(id byte, is16bit bool, isWrite bool) romCommand {
	return romCommand{
		id:      id,
		is16bit: is16bit,
		isWrite: isWrite,
	}
}

func romCommandMakeReadWrite(id byte, is16bit bool) (romCommand, romCommand) {
	return romCommandMake(id, is16bit, false), romCommandMake(id+1, is16bit, true)
}

func (h *HAL) romExchangeReport(out [9]byte, checkLen int) ([9]byte, error) {
	var in [9]byte

	if h.config.LogFunc != nil {
		h.config.LogFunc(3, "ROMOut:   %s", hex.EncodeToString(out[:]))
	}

	if _, err := h.dev.SendFeatureReport(out[:]); err != nil {
		return in, err
	}

	_, err := h.dev.GetFeatureReport(in[:])
	if err != nil {
		return in, err
	}

	if h.config.LogFunc != nil {
		h.config.LogFunc(3, "ROMIn:    %s", hex.EncodeToString(in[:]))
	}

	if !bytes.Equal(out[:checkLen], in[:checkLen]) {
		return in, ErrorInvalidResponse
	}
	return in, nil
}

func (h *HAL) ROMExchangeReport(out []byte) ([]byte, error) {
	var outb [9]byte
	if len(out) > 8 {
		return nil, errors.New("buffer longer than 8 bytes")
	}
	copy(outb[1:], out)
	inb, err := h.romExchangeReport(outb, 0)
	return inb[1:], err
}

func (h *HAL) romProtocolMakeHeader(cmd byte, is16bit bool, addr int, index int) ([9]byte, int) {
	var out [9]byte

	out[0] = 0
	out[1] = byte(cmd)

	if index == 0 {
		index = 2
	}
	if is16bit {
		out[index] = byte(addr >> 8)
		index++
	}
	out[index] = byte(addr)
	index++

	return out, index
}

func (h *HAL) romProtocolReadReply(buf []byte, in []byte, index int, maxReply int) int {
	if len(buf) > maxReply {
		buf = buf[:maxReply]
	}
	return copy(buf, in[index:])

}

func (h *HAL) romProtocolWritePayload(buf []byte, out []byte, index int, maxLen int) int {
	if len(buf) > maxLen {
		buf = buf[:maxLen]
	}
	return copy(out[index:], buf)
}

func (h *HAL) romProtocolExec(cmd romCommand, addr int, buf []byte) (int, error) {
	if len(buf) == 0 {
		return 0, nil
	}

	maxPayload := cmd.maxPayload
	if maxPayload <= 0 {
		maxPayload = 1
	}

	addr >>= cmd.addrShift

	out, index := h.romProtocolMakeHeader(cmd.id, cmd.is16bit, addr, cmd.offset)

	txrLen := 0
	if cmd.isWrite {
		txrLen = h.romProtocolWritePayload(buf, out[:], index, maxPayload)
	}

	if cmd.cbApplyParam != nil {
		if err := cmd.cbApplyParam(h, out[:]); err != nil {
			return 0, err
		}
	}

	in, err := h.romExchangeReport(out, index)
	if err != nil {
		return 0, err
	}

	if !cmd.isWrite {
		txrLen = h.romProtocolReadReply(buf, in[:], index, maxPayload)
	}

	if cmd.cbPostExchange != nil {
		if err := cmd.cbPostExchange(h, addr, buf); err != nil {
			return 0, err
		}
	}

	return txrLen, nil
}

type halROMMemoryRegion struct {
	hal          *HAL
	readCommand  *romCommand
	writeCommand *romCommand
	baseAddr     int
	length       int
	name         MemoryRegionNameType
	alignment    int
}

func (h halROMMemoryRegion) GetName() MemoryRegionNameType {
	return h.name
}

func (h halROMMemoryRegion) GetLength() int {
	return h.length
}

func (h halROMMemoryRegion) GetParent() (MemoryRegion, int) {
	return nil, 0
}

func (h halROMMemoryRegion) GetAlignment() int {
	return h.alignment
}

func (h halROMMemoryRegion) Access(write bool, addr int, buf []byte) (int, error) {
	if addr > h.length {
		return 0, nil
	}
	if addr+len(buf) > h.length {
		buf = buf[:h.length-addr]
	}

	if write {
		if h.writeCommand == nil {
			return 0, ErrorWriteNotAllowed
		}
		return h.hal.romProtocolExec(*h.writeCommand, h.baseAddr+addr, buf)
	}

	if h.readCommand == nil {
		return 0, ErrorReadNotAllowed
	}
	return h.hal.romProtocolExec(*h.readCommand, h.baseAddr+addr, buf)
}

func (h *HAL) romMemoryRegionMake(name MemoryRegionNameType, baseAddr int, length int, alignment int, read *romCommand, write *romCommand) MemoryRegion {
	return regionWrapCompleteIO(halROMMemoryRegion{
		hal:          h,
		baseAddr:     baseAddr,
		length:       length,
		readCommand:  read,
		writeCommand: write,
		name:         name,
		alignment:    alignment,
	})
}

func romEepromV2HandleTwoByteAddress(h *HAL, out []byte) error {
	if h.eepromSize > 2048 {
		out[8] = 1
	}
	return nil
}

func romEepromVerify(region MemoryRegion) cbPostExchangeType {
	return func(h *HAL, addr int, buf []byte) error {
		if buf[0] == 0 {
			/* The chip returns 0 if there is no I2C response, so we just have to wait */
			time.Sleep(15 * time.Millisecond)
			return nil
		}

		var tmp [1]byte
		for i := 0; i < 25; i++ {
			if _, err := region.Access(false, addr, tmp[:]); err != nil {
				return err
			}
			if tmp[0] == buf[0] {
				return nil
			}
		}
		return ErrorTimeout
	}
}
