#define _LARGEFILE_SOURCE
#define _LARGEFILE64_SOURCE
#define _GNU_SOURCE
#include <errno.h>

#include "ibm.h"
#include "hdd_file.h"
#include "minivhd/minivhd.h"
#include "minivhd/minivhd_util.h"

void hdd_load_ext(hdd_file_t *hdd, const char *fn, int spt, int hpc, int tracks, int read_only)
{
	if (hdd->f == NULL)
        {
		/* Try to open existing hard disk image */
		if (read_only)
                        hdd->f = (void*)fopen64(fn, "rb");
		else
                        hdd->f = (void*)fopen64(fn, "rb+");
                if (hdd->f != NULL)
                {
                        hdd->img_type = HDD_IMG_RAW;
                        /* Check if the file we opened is a VHD */
                        if (mvhd_file_is_vhd((FILE*)hdd->f))
                        {
                                int err;
                                fclose((FILE*)hdd->f);
                                MVHDMeta *vhdm = mvhd_open(fn, (bool)read_only, &err);
                                if (vhdm == NULL)
                                {
                                        hdd->f = NULL;
                                        if (err == MVHD_ERR_FILE)
                                        {
                                                pclog("Cannot open VHD file '%s' : %s",
                                                        fn, strerror(mvhd_errno));
                                        }
                                        else
                                        {
                                                pclog("Cannot open VHD file '%s' : %s",
                                                        fn, mvhd_strerr(err));
                                        }
                                        return;
                                }
                                else if (err == MVHD_ERR_TIMESTAMP)
                                {
                                        pclog("VHD: Parent/child timestamp mismatch");
                                        mvhd_close(vhdm);
                                        hdd->f = NULL;
                                        return;
                                }
                                hdd->f = (void*)vhdm;
                                hdd->img_type = HDD_IMG_VHD;
                        }
                }
		else
                {
			/* Failed to open existing hard disk image */
			if (errno == ENOENT && !read_only)
                        {
				/* Failed because it does not exist,
				   so try to create new file */
				hdd->f = (void*)fopen64(fn, "wb+");
				if (hdd->f == NULL)
                                {
					pclog("Cannot create file '%s': %s",
					      fn, strerror(errno));
					return;
				}
			}
                        else
                        {
				/* Failed for another reason */
				pclog("Cannot open file '%s': %s",
				      fn, strerror(errno));
				return;
			}
                        hdd->img_type = HDD_IMG_RAW;
		}
	}
        if (hdd->img_type == HDD_IMG_VHD)
        {
                MVHDMeta *vhdm = (MVHDMeta*)hdd->f;
                MVHDGeom geom = mvhd_get_geometry(vhdm);
                hdd->spt = geom.spt;
                hdd->hpc = geom.heads;
                hdd->tracks = geom.cyl;
        }
        else if (hdd->img_type == HDD_IMG_RAW)
        {
                hdd->spt = spt;
                hdd->hpc = hpc;
                hdd->tracks = tracks;
        }
        hdd->sectors = hdd->spt * hdd->hpc * hdd->tracks;
        hdd->read_only = read_only;
}

void hdd_load(hdd_file_t *hdd, int d, const char *fn)
{
        hdd_load_ext(hdd, fn, hdc[d].spt, hdc[d].hpc, hdc[d].tracks, 0);
}

void hdd_close(hdd_file_t *hdd)
{
        if (hdd->f)
        {
                if (hdd->img_type == HDD_IMG_VHD)
                        mvhd_close((MVHDMeta*)hdd->f);
                else if (hdd->img_type == HDD_IMG_RAW)
                        fclose((FILE*)hdd->f);
        }
        hdd->img_type = HDD_IMG_RAW;
        hdd->f = NULL;
}

int hdd_read_sectors(hdd_file_t *hdd, int offset, int nr_sectors, void *buffer)
{
        if (hdd->img_type == HDD_IMG_VHD)
        {
                return mvhd_read_sectors((MVHDMeta*)hdd->f, offset, nr_sectors, buffer);
        }
        else if (hdd->img_type == HDD_IMG_RAW)
        {
                off64_t addr;
                int transfer_sectors = nr_sectors;

                if ((hdd->sectors - offset) < transfer_sectors)
                        transfer_sectors = hdd->sectors - offset;
                addr = (uint64_t)offset * 512;

                fseeko64((FILE*)hdd->f, addr, SEEK_SET);
                fread(buffer, transfer_sectors*512, 1, (FILE*)hdd->f);

                if (nr_sectors != transfer_sectors)
                        return 1;
                return 0;
        }
        /* Keep the compiler happy */
        return 1;
}

int hdd_write_sectors(hdd_file_t *hdd, int offset, int nr_sectors, void *buffer)
{
        if (hdd->img_type == HDD_IMG_VHD)
        {
                return mvhd_write_sectors((MVHDMeta*)hdd->f, offset, nr_sectors, buffer);
        }
        else if (hdd->img_type == HDD_IMG_RAW)
        {
                off64_t addr;
                int transfer_sectors = nr_sectors;

                if (hdd->read_only)
                        return 1;

                if ((hdd->sectors - offset) < transfer_sectors)
                        transfer_sectors = hdd->sectors - offset;
                addr = (uint64_t)offset * 512;

                fseeko64((FILE*)hdd->f, addr, SEEK_SET);
                fwrite(buffer, transfer_sectors*512, 1, (FILE*)hdd->f);

                if (nr_sectors != transfer_sectors)
                        return 1;
                return 0;
        }
        /* Keep the compiler happy */
        return 1;
}

int hdd_format_sectors(hdd_file_t *hdd, int offset, int nr_sectors)
{
        if (hdd->img_type == HDD_IMG_VHD)
        {
                return mvhd_format_sectors((MVHDMeta*)hdd->f, offset, nr_sectors);
        }
        else if (hdd->img_type == HDD_IMG_RAW)
        {
                off64_t addr;
                int c;
                uint8_t zero_buffer[512];
                int transfer_sectors = nr_sectors;

                if (hdd->read_only)
                        return 1;

                memset(zero_buffer, 0, 512);
                
                if ((hdd->sectors - offset) < transfer_sectors)
                        transfer_sectors = hdd->sectors - offset;
                addr = (uint64_t)offset * 512;
                fseeko64((FILE*)hdd->f, addr, SEEK_SET);
                for (c = 0; c < transfer_sectors; c++)
                        fwrite(zero_buffer, 512, 1, (FILE*)hdd->f);

                if (nr_sectors != transfer_sectors)
                        return 1;
                return 0;
        }
        /* Keep the compiler happy */
        return 1;
}
