#!/usr/bin/env python3
# 
# Cross Platform and Multi Architecture Advanced Binary Emulation Framework
#

from .mapper import QlFsMappedObject

# Open a file as a Disk
#     host_path: The file path on the host machine.
#     drive_path: The drive path on the emulated system. e.g. /dev/sda \\.\PHYSICALDRIVE0 0x80
# 
# Note: CHS and LBA support is very limited since a raw file doesn't contain enough information.
#       We simply assume that it is a disk with 1 head, 1 cylinder and (filesize/512) sectors.
# See: https://en.wikipedia.org/wiki/Cylinder-head-sector
#      https://en.wikipedia.org/wiki/Logical_block_addressing
#      http://www.uruk.org/orig-grub/PC_partitioning.txt
class QlDisk(QlFsMappedObject):

    def __init__(self, host_path, drive_path, n_heads=1, n_cylinders=1, sector_size=512):
        self._host_path = host_path
        self._drive_path = drive_path
        self._fp = open(host_path, "rb+")
        self._n_heads = n_heads
        self._n_cylinders = n_cylinders
        self._sector_size = sector_size
        self.lseek(0, 2)
        self._filesize = self.tell()
        self._n_sectors = (self._filesize - 1)// self.sector_size + 1

    def __del__(self):
        if not self.fp.closed:
            self.fp.close()

    @property
    def filesize(self):
        return self._filesize

    @property
    def n_heads(self):
        return self._n_heads

    @property
    def n_sectors(self):
        return self._n_sectors

    @property
    def n_cylinders(self):
        return self._n_cylinders

    @property
    def sector_size(self):
        return self._sector_size

    @property
    def host_path(self):
        return self._host_path
    
    @property
    def drive_path(self):
        return self._drive_path
    
    @property
    def fp(self):
        return self._fp

    # Methods from FsMappedObject
    def read(self, l):
        return self.fp.read(l)
    
    def write(self, bs):
        return self.fp.write(bs)

    def lseek(self, offset, origin):
        return self.fp.seek(offset, origin)
    
    def tell(self):
        return self.fp.tell()

    def close(self):
        return self.fp.close()
    
    # Methods for QlDisk
    def lba(self, cylinder, head, sector):
        return (cylinder * self.n_heads + head) * self._n_sectors + sector - 1
    
    def read_sectors(self, lba, cnt):
        self.lseek(self.sector_size * lba, 0)
        return self.read(self.sector_size*cnt)
    
    def read_chs(self, cylinder, head, sector, cnt):
        return self.read_sectors(self.lba(cylinder, head, sector), cnt)

    def write_sectors(self, lba, cnt, buffer):
        if len(buffer) > self.sector_size * cnt:
            buffer = buffer[:self.sector_size*cnt]
        self.lseek(self.sector_size * lba, 0)
        return self.write(buffer)
    
    def write_chs(self, cylinder, head, sector, cnt, buffer):
        return self.write_sectors(self.lba(cylinder, head, sector), cnt, buffer)