#include "globals.h"
#include "flags.h"
#include "sector.h"
#include "imagereader/imagereader.h"
#include "image.h"
#include "proto.h"
#include "lib/config.pb.h"
#include "fmt/format.h"
#include <algorithm>
#include <iostream>
#include <fstream>

static unsigned getModulationandSpeed(uint8_t flags, bool *mfm)
{
    switch (flags)
        {
            case 0: /* 500 kbps FM */
                //clockRateKhz.setDefaultValue(250);
                *mfm = false;
                return 500;
                break;

            case 1: /* 300 kbps FM */
                *mfm  = false;
                return 300;
                
                break;

            case 2: /* 250 kbps FM */
                *mfm  = false;
                return 250;
                break;

            case 3: /* 500 kbps MFM */
                *mfm  = true;
                return 500;
                break;

            case 4: /* 300 kbps MFM */
                *mfm  = true;
                return 300;
                break;

            case 5: /* 250 kbps MFM */
                *mfm  = true;
                return 250;
                break;

            default:
                Error() << fmt::format("don't understand IMD disks with this modulation and speed {}", flags);
                throw 0;
        }
}

struct TrackHeader
{
    uint8_t ModeValue;
    uint8_t track;
    uint8_t Head;
    uint8_t numSectors;
    uint8_t SectorSize;
};

static unsigned getSectorSize(uint8_t flags)
{
    switch (flags)
    {
        case 0: return 128;
        case 1: return 256;
        case 2: return 512;
        case 3: return 1024;
        case 4: return 2048;
        case 5: return 4096;
        case 6: return 8192;
    }
    Error() << "not reachable";
}


#define SEC_CYL_MAP_FLAG  0x80 
#define SEC_HEAD_MAP_FLAG 0x40
#define HEAD_MASK         0x3F
#define END_OF_FILE       0x1A


class IMDImageReader : public ImageReader
{
public:
    IMDImageReader(const ImageReaderProto& config):
        ImageReader(config)
    {}

    std::unique_ptr<Image> readImage()
    /*
    IMAGE FILE FORMAT
    The overall layout of an ImageDisk .IMD image file is:
    IMD v.vv: dd/mm/yyyy hh:mm:ss
    Comment (ASCII only - unlimited size)
    1A byte - ASCII EOF character
    - For each track on the disk:
    1 byte Mode value                           see getModulationspeed for definition       
    1 byte Cylinder
    1 byte Head
    1 byte number of sectors in track           
    1 byte sector size                          see getsectorsize for definition
    sector numbering map
    sector cylinder map (optional)              definied in high byte of head (since head is 0 or 1)
    sector head map (optional)                  definied in high byte of head (since head is 0 or 1)
    sector data records
    <End of file>
    */
    {
        //Read File
        std::ifstream inputFile(_config.filename(), std::ios::in | std::ios::binary);
        if (!inputFile.is_open())
            Error() << "cannot open input file";
        //define some variables
        bool mfm = false;   //define coding just to show in comment for setting the right write parameters
        inputFile.seekg(0, inputFile.end);
        int inputFileSize = inputFile.tellg();  // determine filesize
        inputFile.seekg(0, inputFile.beg);
        Bytes data;
        data.writer() += inputFile;
        ByteReader br(data);
        std::unique_ptr<Image> image(new Image);
        TrackHeader header = {0, 0, 0, 0, 0};

        unsigned n = 0;
        unsigned headerPtr = 0;
        unsigned Modulation_Speed = 0;
        unsigned sectorSize = 0;
        std::string sector_skew;
        int b;  
        unsigned char comment[8192]; //i choose a fixed value. dont know how to make dynamic arrays in C++. This should be enough
        // Read comment
        while ((b = br.read_8()) != EOF && b != 0x1A)
        {
            comment[n++] = (unsigned char)b;
        }
        headerPtr = n; //set pointer to after comment
        comment[n] = '\0'; // null-terminate the string
        //write comment to screen
        std::cout   << "Comment in IMD image:\n"
                    << fmt::format("{}\n",
                    comment);

        //first read header
        for (;;)
        {
            if (headerPtr >= inputFileSize-1)
            {
                break;
            }
            header.ModeValue = br.read_8();
            headerPtr++;
            Modulation_Speed = getModulationandSpeed(header.ModeValue, &mfm);
            header.track = br.read_8();
            headerPtr++;
            header.Head = br.read_8();
            headerPtr++;
            header.numSectors = br.read_8();
            headerPtr++;
            header.SectorSize = br.read_8();
            headerPtr++;
            sectorSize = getSectorSize(header.SectorSize);

            //Read optional cylinder map To Do

            //Read optional sector head map To Do

            //read sector numbering map
            std::vector<unsigned> sector_map(header.numSectors);
            sector_skew.clear();
            for (int b = 0;  b < header.numSectors; b++)
            {   
                sector_map[b] = br.read_8();
                headerPtr++;
            }

            //read the sectors
            for (int s = 0; s < header.numSectors; s++)
            {
                Bytes sectordata;
                const auto& sector = image->put(header.track, header.Head, sector_map[s]);
                //read the status of the sector
                unsigned int Status_Sector = br.read_8();
                headerPtr++;

                switch (Status_Sector)
                {
                    case 0: /* Sector data unavailable - could not be read */
                        break;

                    case 1: /* Normal data: (Sector Size) bytes follow */
                        sectordata = br.read(sectorSize);
                        headerPtr += sectorSize;
                        sector->data.writer().append(sectordata);
                        break;

                    case 2: /* Compressed: All bytes in sector have same value (xx) */
                        sectordata = br.read(1);
                        headerPtr++;
                        sector->data.writer().append(sectordata);

                        for (int k = 1; k < sectorSize; k++)
                        {
                            //fill data till sector is full
                            sector->data.writer().append(sectordata);
                        }
                        break;

                    case 3: /* Normal data with "Deleted-Data address mark" */
                    case 4: /* Compressed with "Deleted-Data address mark"*/
                    case 5: /* Normal data read with data error */
                    case 6: /* Compressed read with data error" */
                    case 7: /* Deleted data read with data error" */
                    case 8: /* Compressed, Deleted read with data error" */
                    default:
                        Error() << fmt::format("don't understand IMD disks with sector status {}", Status_Sector);
                }       
                sector->status = Sector::OK;
                sector->logicalTrack = sector->physicalCylinder = header.track;
                sector->logicalSide = sector->physicalHead = header.Head;
                sector->logicalSector = (sector_map[s]);
            }

        }
        //Write format detected in IMD image to screen to help user set the right write parameters

        image->setGeometry({
            .numTracks = header.track,
            .numSides = header.Head + 1U,
            .numSectors = header.numSectors,
            .sectorSize = sectorSize
        });

        size_t headSize = header.numSectors * sectorSize;
        size_t trackSize = headSize * (header.Head + 1);

        std::cout << fmt::format("IMD: {} tracks, {} heads; {}; {} kbps; {} sectors; sectorsize {};\n"
                                 "     sectormap {}; {} kB total\n",
                    header.track, header.Head + 1,
                    mfm ? "MFM" : "FM",
                    Modulation_Speed, header.numSectors, sectorSize, sector_skew, (header.track+1) * trackSize / 1024);

        return image;
    }
};

std::unique_ptr<ImageReader> ImageReader::createIMDImageReader(
    const ImageReaderProto& config)
{
    return std::unique_ptr<ImageReader>(new IMDImageReader(config));
}

// vim: ts=4 sw=4 et
