import math

from misoclib.mem.litesata.common import *
from misoclib.mem.litesata.test.common import *

from misoclib.mem.litesata.test.model.phy import *
from misoclib.mem.litesata.test.model.link import *
from misoclib.mem.litesata.test.model.transport import *
from misoclib.mem.litesata.test.model.command import *


def print_hdd(s, n=None):
    print_with_prefix(s, "[HDD{}]: ".format("" if n is None else str(n)))


class HDDMemRegion:
    def __init__(self, base, count, sector_size):
        self.base = base
        self.count = count
        self.data = [0]*(count*sector_size//4)


class HDD(Module):
    def __init__(self, n=None,
            link_debug=False, link_random_level=0,
            transport_debug=False, transport_loopback=False,
            hdd_debug=False,
            ):
        self.n = n
        self.submodules.phy = PHYLayer()
        self.submodules.link = LinkLayer(self.phy, link_debug, link_random_level)
        self.submodules.transport = TransportLayer(self.link, transport_debug, transport_loopback)
        self.submodules.command = CommandLayer(self.transport)

        self.command.set_hdd(self)

        self.debug = hdd_debug
        self.mem = None
        self.wr_sector = 0
        self.wr_end_sector = 0
        self.rd_sector = 0
        self.rx_end_sector = 0

    def malloc(self, sector, count):
        if self.debug:
            s = "Allocating {n} sectors: {s} to {e}".format(n=count, s=sector, e=sector+count-1)
            s += " ({} KB)".format(count*logical_sector_size//1024)
            print_hdd(s, self.n)
        self.mem = HDDMemRegion(sector, count, logical_sector_size)

    def write(self, sector, data):
        n = math.ceil(dwords2sectors(len(data)))
        if self.debug:
            if n == 1:
                s = "{}".format(sector)
            else:
                s = "{s} to {e}".format(s=sector, e=sector+n-1)
            print_hdd("Writing sector " + s, self.n)
        for i in range(len(data)):
            offset = sectors2dwords(sector)
            self.mem.data[offset+i] = data[i]

    def read(self, sector, count):
        if self.debug:
            if count == 1:
                s = "{}".format(sector)
            else:
                s = "{s} to {e}".format(s=sector, e=sector+count-1)
            print_hdd("Reading sector " + s, self.n)
        data = []
        for i in range(sectors2dwords(count)):
            data.append(self.mem.data[sectors2dwords(sector)+i])
        return data

    def write_dma_callback(self, fis):
        self.wr_sector = fis.lba_lsb + (fis.lba_msb << 32)
        self.wr_end_sector = self.wr_sector + fis.count
        return [FIS_DMA_ACTIVATE_D2H()]

    def read_dma_callback(self, fis):
        self.rd_sector = fis.lba_lsb + (fis.lba_msb << 32)
        self.rd_end_sector = self.rd_sector + fis.count
        packets = []
        while self.rd_sector != self.rd_end_sector:
            count = min(self.rd_end_sector-self.rd_sector, (fis_max_dwords*4)//logical_sector_size)
            packet = self.read(self.rd_sector, count)
            packet.insert(0, 0)
            packets.append(FIS_DATA(packet, direction="D2H"))
            self.rd_sector += count
        packets.append(FIS_REG_D2H())
        return packets

    def data_callback(self, fis):
        self.write(self.wr_sector, fis.packet[1:])
        self.wr_sector += dwords2sectors(len(fis.packet[1:]))
        if self.wr_sector == self.wr_end_sector:
            return [FIS_REG_D2H()]
        else:
            return [FIS_DMA_ACTIVATE_D2H()]