
#include "d64.h"

#include "endianness.h"

// D64 Utility Functions

bool D64IStream::seekBlock( uint64_t index, uint8_t offset )
{
	uint16_t sectorOffset = 0;
    uint8_t track = 0;

    // Debug_printv("track[%d] sector[%d] offset[%d]", track, sector, offset);

    // Determine actual track & sector from index
    do
	{
        track++;
		uint8_t count = sectorsPerTrack[speedZone(track)];
        if ( sectorOffset + count < index )
            sectorOffset += count;
        else
            break;
        
        //Debug_printv("track[%d] speedZone[%d] secotorsPerTrack[%d] sectorOffset[%d]", track, speedZone(track), count, sectorOffset);
	} while ( true );
	uint8_t sector = index - sectorOffset;

    this->block = index;
    this->track = track;
    this->sector = sector;

    //Debug_printv("track[%d] sector[%d] speedZone[%d] sectorOffset[%d]", track, sector, speedZone(track), sectorOffset);

    return containerStream->seek( (index * block_size) + offset );
}

bool D64IStream::seekSector( uint8_t track, uint8_t sector, uint8_t offset )
{
	uint16_t sectorOffset = 0;

    //Debug_printv("track[%d] sector[%d] offset[%d]", track, sector, offset);

    // Is this a valid track?
    uint8_t c = partitions[partition].block_allocation_map.size() - 1;
    uint8_t start_track = partitions[partition].block_allocation_map[0].start_track;
    uint8_t end_track = partitions[partition].block_allocation_map[c].end_track;
    if ( track < start_track || track > end_track )
    {
        Debug_printv("track[%d] start_track[%d] end_track[%d]", track, start_track, end_track);
        return false;
    }

    // Is this a valid sector?
    c = sectorsPerTrack[speedZone(track)];
    if ( sector > c )
    {
        Debug_printv("sector[%d] track[%d] sectorsPerTrack[%d]", sector, track, c);
        return false;
    }

    track--;
	for (uint8_t index = 0; index < track; ++index)
	{
		sectorOffset += sectorsPerTrack[speedZone(index + 1)];
        // Debug_printv("track[%d] speedZone[%d] secotorsPerTrack[%d] sectorOffset[%d]", (index + 1), speedZone(index), sectorsPerTrack[speedZone(index)], sectorOffset);
	}
    track++;
	sectorOffset += sector;

    this->block = sectorOffset;
    this->track = track;
    this->sector = sector;

    //Debug_printv("track[%d] sector[%d] speedZone[%d] sectorOffset[%d]", track, sector, speedZone(track), sectorOffset);

    return containerStream->seek( (sectorOffset * block_size) + offset );
}

bool D64IStream::seekSector( std::vector<uint8_t> trackSectorOffset )
{
    return seekSector(trackSectorOffset[0], trackSectorOffset[1], trackSectorOffset[2]);
}


std::string D64IStream::readBlock(uint8_t track, uint8_t sector)
{
    return "";
}

bool D64IStream::writeBlock(uint8_t track, uint8_t sector, std::string data)
{
    return true;
}

bool D64IStream::allocateBlock( uint8_t track, uint8_t sector)
{
    return true;
}

bool D64IStream::deallocateBlock( uint8_t track, uint8_t sector)
{
    return true;
}

bool D64IStream::seekEntry( std::string filename )
{
    uint32_t index = 1;
    mstr::replaceAll(filename, "\\", "/");
    bool wildcard =  ( mstr::contains(filename, "*") || mstr::contains(filename, "?") );

    // Read Directory Entries
    if ( filename.size() )
    {
        while ( seekEntry( index ) )
        {
            std::string entryFilename = entry.filename;
            mstr::rtrimA0(entryFilename);
            entryFilename = mstr::toUTF8(entryFilename);

            Debug_printv("index[%d] track[%d] sector[%d] filename[%s] entry.filename[%.16s]", index, track, sector, filename.c_str(), entryFilename.c_str());

            //Debug_printv("filename[%s] entry[%s]", filename.c_str(), entryFilename.c_str());

            // Read Entry From Stream
            if (entry.file_type & 0b00000111 && filename == "*") // Match first PRG
            {
                filename = entryFilename;
                return true;
            }
            else if ( filename == entryFilename ) // Match exact
            {
                return true;
            }
            else if ( wildcard )
            {
                if ( mstr::compare(filename, entryFilename) ) // X?XX?X* Wildcard match
                {
                    // Move stream pointer to start track/sector
                    return true;
                }
            }

            index++;
        }

        Debug_printv("File not found!");
    }

    entry.next_track = 0;
    entry.next_sector = 0;
    entry.blocks = 0;
    entry.filename[0] = '\0';

    return false;
}

bool D64IStream::seekEntry( uint16_t index )
{
    // Calculate Sector offset & Entry offset
    // 8 Entries Per Sector, 32 bytes Per Entry
    index--;
    uint16_t sectorOffset = index / 8;
    uint16_t entryOffset = (index % 8) * 32;

    // Debug_printv("----------");
    // Debug_printv("index[%d] sectorOffset[%d] entryOffset[%d] entry_index[%d]", index, sectorOffset, entryOffset, entry_index);


    if (index == 0 || index != entry_index)
    {
        // Start at first sector of directory
        next_track = 0;
        if ( !seekSector( 
                partitions[partition].directory_track,
                partitions[partition].directory_sector,
                partitions[partition].directory_offset
            )
        ) return false;

        // Find sector with requested entry
        do
        {
            if ( next_track )
            {
                Debug_printv("next_track[%d] next_sector[%d]", entry.next_track, entry.next_sector);
                if ( !seekSector( entry.next_track, entry.next_sector ) )
                    return false;
            }

            containerStream->read((uint8_t *)&entry, sizeof(entry));
            next_track = entry.next_track;
            next_sector = entry.next_sector;

            Debug_printv("sectorOffset[%d] -> track[%d] sector[%d]", sectorOffset, track, sector);
        } while ( sectorOffset-- > 0 );
        if ( !seekSector( track, sector, entryOffset ) )
            return false;
    }
    else
    {
        if ( entryOffset == 0 )
        {
            if ( next_track == 0 )
                return false;

            //Debug_printv("Follow link track[%d] sector[%d] entryOffset[%d]", next_track, next_sector, entryOffset);
            if ( !seekSector( next_track, next_sector, entryOffset ) )
                return false;
        }
    }

    containerStream->read((uint8_t *)&entry, sizeof(entry));

    // If we are at the first entry in the sector then get next_track/next_sector
    if ( entryOffset == 0 )
    {
        next_track = entry.next_track;
        next_sector = entry.next_sector;
    }

    // Debug_printv("r[%d] file_type[%02X] file_name[%.16s]", r, entry.file_type, entry.filename);

    //if ( next_track == 0 && next_sector == 0xFF )
    entry_index = index + 1;

    return true;
}


uint16_t D64IStream::blocksFree()
{
    uint16_t free_count = 0;

    for(uint8_t x = 0; x < partitions[partition].block_allocation_map.size(); x++)
    {
        uint8_t bam[partitions[partition].block_allocation_map[x].byte_count];
        //Debug_printv("start_track[%d] end_track[%d]", block_allocation_map[x].start_track, block_allocation_map[x].end_track);

        if ( !seekSector(
                partitions[partition].block_allocation_map[x].track, 
                partitions[partition].block_allocation_map[x].sector, 
                partitions[partition].block_allocation_map[x].offset
            )
        ) return 0;

        for(uint8_t i = partitions[partition].block_allocation_map[x].start_track; i <= partitions[partition].block_allocation_map[x].end_track; i++)
        {
            containerStream->read((uint8_t *)&bam, sizeof(bam));
            if ( sizeof(bam) > 3 )
            {
                if ( i != partitions[partition].directory_track )
                {
                    //Debug_printv("x[%d] track[%d] count[%d] size[%d]", x, i, bam[0], sizeof(bam));
                    free_count += bam[0];
                }
            }
            else
            {
                // D71 tracks 36 - 70 you have to count the 1 bits (0 is allocated)
                uint8_t bit_count = 0;
                bit_count += std::bitset<8>(bam[0]).count();
                bit_count += std::bitset<8>(bam[1]).count();
                bit_count += std::bitset<8>(bam[2]).count();

                //Debug_printv("x[%d] track[%d] count[%d] size[%d] bam0[%d] bam1[%d] bam2[%d] (counting 1 bits)", x, i, bit_count, sizeof(bam), bam[0], bam[1], bam[2]);
                free_count += bit_count;
            }
        }
    }

    return free_count;
}

uint16_t D64IStream::readFile(uint8_t* buf, uint16_t size) {

    if ( sector_offset % block_size == 0 )
    {
        // We are at the beginning of the block
        // Read track/sector link
        containerStream->read((uint8_t *)&next_track, 1);
        containerStream->read((uint8_t *)&next_sector, 1);
        sector_offset += 2;
        //Debug_printv("next_track[%d] next_sector[%d] sector_offset[%d]", next_track, next_sector, sector_offset);
    }

    uint16_t bytesRead = 0;
    if ( size > available() )
        size = available();
    
    if ( size > 0 )
    {
        bytesRead += containerStream->read(buf, size);
        sector_offset += bytesRead;

        if ( sector_offset % block_size == 0 )
        {
            // We are at the end of the block
            // Follow track/sector link to move to next block
            if ( !seekSector( next_track, next_sector ) )
            {
                return 0;
            }
            //Debug_printv("track[%d] sector[%d] sector_offset[%d]", track, sector, sector_offset);
        }
    }

    // if ( !bytesRead )
    // {
    //     sector_offset = 0;
    // }

    return bytesRead;
}



bool D64IStream::seekPath(std::string path) {
    // Implement this to skip a queue of file streams to start of file by name
    // this will cause the next read to return bytes of 'path'
    seekCalled = true;

    next_track = 0;
    next_sector = 0;
    sector_offset = 0;

    entry_index = 0;

    // call image method to obtain file bytes here, return true on success:
    // return D64Image.seekFile(containerIStream, path);
    if ( mstr::endsWith(path,"#") ) // Direct Access Mode
    {
        Debug_printv("Direct Access Mode track[1] sector[0] path[%s]", path.c_str());
        seekCalled = false;
        return seekSector(1, 0);
    }
    else if ( seekEntry(path) )
    {
        //auto entry = containerImage->entry;
        auto type = decodeType(entry.file_type).c_str();
        Debug_printv("filename[%.16s] type[%s] start_track[%d] start_sector[%d]", entry.filename, type, entry.start_track, entry.start_sector);

        // Calculate file size
        uint8_t t = entry.start_track;
        uint8_t s = entry.start_sector;
        _size = seekFileSize( t, s );

        // Set position to beginning of file
        bool r = seekSector( t, s );

        Debug_printv("File Size: blocks[%d] size[%d] available[%d] r[%d]", entry.blocks, _size, available(), r);

        return r;
    }
    else
    {
        Debug_printv( "Not found! [%s]", path.c_str());
    }

    return false;
};


/********************************************************
 * File implementations
 ********************************************************/

MStream* D64File::getDecodedStream(std::shared_ptr<MStream> containerIstream) {
    // Debug_printv("[%s]", url.c_str());

    return new D64IStream(containerIstream);
}

bool D64File::isDirectory() {
    //Debug_printv("pathInStream[%s]", pathInStream.c_str());
    if ( pathInStream == "" )
        return true;
    else
        return false;
};

bool D64File::rewindDirectory() {
    dirIsOpen = true;
    //Debug_printv("streamFile->url[%s]", streamFile->url.c_str());
    auto image = ImageBroker::obtain<D64IStream>(streamFile->url);
    if ( image == nullptr )
        Debug_printv("image pointer is null");

    image->resetEntryCounter();

    // Read Header
    image->seekHeader();

    // Set Media Info Fields
    media_header = mstr::format("%.16s", image->header.disk_name);
    mstr::A02Space(media_header);
    media_id = mstr::format("%.5s", image->header.id_dos);
    mstr::A02Space(media_id);
    media_blocks_free = image->blocksFree();
    media_block_size = image->block_size;
    media_image = name;
    //mstr::toUTF8(media_image);

    return true;
}

MFile* D64File::getNextFileInDir() {

    if(!dirIsOpen)
        rewindDirectory();

    // Get entry pointed to by containerStream
    auto image = ImageBroker::obtain<D64IStream>(streamFile->url);

    bool r = false;
    do
    {
        r = image->seekNextImageEntry();
    } while ( r && image->entry.file_type == 0x00 ); // Skip hidden files
    
    if ( r )
    {
        std::string fileName = image->entry.filename;
        //mstr::rtrimA0(fileName);
        mstr::replaceAll(fileName, "/", "\\");
        //Debug_printv( "entry[%s]", (streamFile->url + "/" + fileName).c_str() );
        auto file = MFSOwner::File(streamFile->url + "/" + fileName);
        file->extension = image->decodeType(image->entry.file_type);
        return file;
    }
    else
    {
        //Debug_printv( "END OF DIRECTORY");
        dirIsOpen = false;
        return nullptr;
    }
}

time_t D64File::getLastWrite() {
    return getCreationTime();
}

time_t D64File::getCreationTime() {
    tm *entry_time = 0;
    auto entry = ImageBroker::obtain<D64IStream>(streamFile->url)->entry;
    entry_time->tm_year = entry.year + 1900;
    entry_time->tm_mon = entry.month;
    entry_time->tm_mday = entry.day;
    entry_time->tm_hour = entry.hour;
    entry_time->tm_min = entry.minute;

    return mktime(entry_time);
}

bool D64File::exists() {
    // here I'd rather use D64 logic to see if such file name exists in the image!
    //Debug_printv("here");
    return true;
}

uint32_t D64File::size() {
    //Debug_printv("[%s]", streamFile->url.c_str());
    // use D64 to get size of the file in image
    auto entry = ImageBroker::obtain<D64IStream>(streamFile->url)->entry;
    uint32_t bytes = UINT16_FROM_LE_UINT16(entry.blocks);
    
    return bytes;
}
