/*
 * Copyright (C) 2007-2023 Xagasoft, All rights reserved.
 *
 * This file is part of the libbu++ library and is released under the
 * terms of the license contained in the file LICENSE.
 */

#include "bu/sio.h"
#include "bu/file.h"
#include "bu/myriad.h"
#include "bu/myriadstream.h"
#include "bu/optparser.h"

#include <stdlib.h>

using namespace Bu;

enum Mode
{
    modeCreate,
    modeInfo,
    modeStreamNew,
    modeStreamErase,
    modeStreamDump,
    modeStreamPut,
    modeStreamGet,
    modeBlockMap,

    modeNone
};

class Options : public OptParser
{
public:
    Options( int argc, char *argv[] ) :
        eMode( modeNone ),
        iBlockSize( 64 ),
        iPreallocate( 0 ),
        iStream( 0 )
    {
        addHelpBanner("Mode of operation:");
        addOption( eMode, 'c', "create",
                "Create a new Myriad file." );
        addOption( eMode, 'i', "info",
                "Display some info about a Myriad file." );
        addOption( eMode, 'n', "new",
                "Create a new sub-stream in a Myriad file.");
        addOption( eMode, 'e', "erase",
                "Erase sub-stream in a Myriad file.");
        addOption( eMode, 'd', "dump",
                "Display a hexdump of a stream from a Myriad file.");
        addOption( eMode, "get",
                "Get a file out of a Myriad stream (use --dst).");
        addOption( eMode, "put",
                "Put a file into a Myriad stream (usr --src).");
        addOption( eMode, 'm', "block-map",
                "Visualize block usage.");
        addHelpOption();

        addHelpBanner("\nGeneral options:");
        addOption( iBlockSize, 'b', "block-size", "Set the block size." );
        addOption( iPreallocate, 'p', "preallocate",
            "Number of blocks to preallocate." );
        addOption( sFile, 'f', "file", "Set the Myriad filename." );
        addOption( iStream, 's', "stream", "Substream to work with.");
        addOption( sSrc, "src", "Source file for copying into a Myriad file.");
        addOption( sDst, "dst",
                "Destination file for copying out of a Myriad file.");

        setOverride( "create", modeCreate );
        setOverride( "info", modeInfo );
        setOverride( "new", modeStreamNew );
        setOverride( "erase", modeStreamErase );
        setOverride( "dump", modeStreamDump );
        setOverride( "put", modeStreamPut );
        setOverride( "get", modeStreamGet );
        setOverride( "block-map", modeBlockMap );

        parse( argc, argv );
    }

    Mode eMode;
    int iBlockSize;
    int iPreallocate;
    int iStream;
    Bu::String sFile;
    Bu::String sSrc;
    Bu::String sDst;
};

Bu::Formatter &operator>>( Bu::Formatter &f, Mode & /*e*/ )
{
    sio << "Uh oh, the formatter was called..." << sio.nl;
    return f;
}

void printMap( const Bu::BitString &bs )
{
    for( int j = 0; j < bs.getSize(); j++ )
    {
        if( j>0 && (j%50) == 0 )
            Bu::println("");
        if( bs.getBit( j ) )
            Bu::print("#");
        else
            Bu::print("-");
    }
    Bu::println("\n");
}

void printMap( const Bu::Array<int32_t> &bm )
{
    int iBigest = 0;
    for( int j = 0; j < bm.getSize(); j++ )
    {
        if( iBigest < bm[j] )
            iBigest = bm[j];
    }
    int iWidth = Bu::String("%1").arg( iBigest ).end().getSize();
    Bu::String sEmpty;
    for( int j = 0; j < iWidth; j++ )
    {
        sEmpty += '-';
    }
    int iBreakAt = 60/(iWidth+1);
    for( int j = 0; j < bm.getSize(); j++ )
    {
        if( j>0 && (j%iBreakAt) == 0 )
            Bu::println("");

        if( bm[j] < 0 )
            Bu::print("%1 ").arg( sEmpty, Bu::Fmt(2).right().fill(' '));
        else
            Bu::print("%1 ").arg( bm[j], Bu::Fmt(2).right().fill(' '));
        
    }
    Bu::println("\n");
}

int main( int argc, char *argv[] )
{
    Options opts( argc, argv );

    switch( opts.eMode )
    {
        case modeCreate:
            if( !opts.sFile.isSet() )
            {
                sio << "Please specify a file to create." << sio.nl;
                return 0;
            }
            else
            {
                File fOut( opts.sFile, File::WriteNew|File::Read );
                Myriad m( fOut, opts.iBlockSize, opts.iPreallocate );
            }
            break;

        case modeInfo:
            if( !opts.sFile.isSet() )
            {
                sio << "Please specify a file to display info about." << sio.nl;
                return 0;
            }
            else
            {
                File fIn( opts.sFile, File::Read );
                Myriad m( fIn );
                sio << "Myriad info:" << sio.nl
                    << "  Block size:   " << m.getBlockSize() << sio.nl
                    << "  Block count:  " << m.getTotalBlocks() << sio.nl
                    << "  Blocks used:  " << m.getUsedBlocks() << " ("
                    << m.getUsedBlocks()*100/m.getTotalBlocks() << "%)"
                    << sio.nl
                    << "  Stream count: " << m.getTotalStreams() << sio.nl
                    << "  Used space:   " << m.getTotalUsedBytes() << sio.nl
                    << "  Unused space: " << m.getTotalUnusedBytes() << sio.nl
                    << "  % of files:   " << (double)(m.getTotalBlocks()*m.getBlockSize())/(double)(m.getTotalUsedBytes() + m.getTotalUnusedBytes( 4096 ))*100.0 << sio.nl;
/*              Bu::Array<int> aStreams = m.getStreamIds();
                sio << "  Stream info:" << sio.nl;
                for( Bu::Array<int>::iterator i = aStreams.begin(); i; i++ )
                {
                    sio << "    " << Fmt(4) << *i << ") "
                        << m.getStreamSize( *i ) << "b" << sio.nl;
                } */
            }
            break;

        case modeStreamNew:
            if( !opts.sFile.isSet() )
            {
                sio << "Please specify a file manipulate." << sio.nl;
                return 0;
            }
            else
            {
                File fOut( opts.sFile, File::Write|File::Read );
                Myriad m( fOut );
                m.create( Bu::Myriad::WriteNew, opts.iPreallocate );
            }
            break;

        case modeStreamErase:
            if( !opts.sFile.isSet() )
            {
                sio << "Please specify a file manipulate." << sio.nl;
                return 0;
            }
            else
            {
                File fOut( opts.sFile, File::Write|File::Read );
                Myriad m( fOut );
                m.erase( opts.iStream );
                printMap( m.buildBlockUseMap() );
                printMap( m.buildBlockMap() );
            }
            break;

        case modeStreamDump:
            if( !opts.sFile.isSet() )
            {
                sio << "Please specify a file to manipulate." << sio.nl;
                return 0;
            }
            else
            {
                File fOut( opts.sFile, File::Read );
                Myriad m( fOut );
                MyriadStream s = m.open( opts.iStream, Bu::Myriad::Read );
                sio << "Stream " << opts.iStream << ":" << sio.nl;
                char buf[8];
                int iPos = 0;
                while( !s.isEos() )
                {
                    size_t sAmnt = s.read( buf, 8 );
                    sio << Fmt(5) << iPos << ": ";
                    iPos += sAmnt;
                    for( size_t j = 0; j < sAmnt; j++ )
                    {
                        sio << Fmt::hex(2) << (int)((unsigned char)buf[j])
                            << " ";
                    }
                    for( size_t j = sAmnt; j < 8; j++ )
                    {
                        sio << "-- ";
                    }
                    sio << "| ";
                    for( size_t j = 0; j < sAmnt; j++ )
                    {
                        if( buf[j] >= 32 && buf[j] <= 126 )
                            sio << buf[j] << " ";
                        else
                            sio << "  ";
                    }
                    sio << sio.nl;
                }
                sio << "Position: " << s.tell() << ", isEos()=" << s.isEos()
                    << sio.nl;
            }
            break;

        case modeStreamPut:
            if( !opts.sFile.isSet() )
            {
                sio << "Please specify a file manipulate." << sio.nl;
                return 0;
            }
            else if( !opts.sSrc.isSet() )
            {
                sio << "Please specify a source file to read." << sio.nl;
            }
            else
            {
                File fOut( opts.sFile, File::Write|File::Read );
                Myriad m( fOut );
                MyriadStream sOut = m.create(
                    Bu::Myriad::WriteNew, opts.iPreallocate
                    );
                File fIn( opts.sSrc, File::Read );
                char buf[1024];
                while( !fIn.isEos() )
                {
                    sOut.write( buf, fIn.read( buf, 1024 ) );
                }
            }
            break;

        case modeStreamGet:
            if( !opts.sFile.isSet() )
            {
                sio << "Please specify a file manipulate." << sio.nl;
                return 0;
            }
            else if( !opts.sDst.isSet() )
            {
                sio << "Please specify a destination file to write." << sio.nl;
            }
            else
            {
                File fIn( opts.sFile, File::Write|File::Read );
                Myriad m( fIn );
                MyriadStream sIn = m.open( opts.iStream, Bu::Myriad::Read );
                File fOut( opts.sDst, File::Write|File::Create|File::Truncate );
                char buf[1024];
                while( !sIn.isEos() )
                {
                    fOut.write( buf, sIn.read( buf, 1024 ) );
                }
            }
            break;

        case modeBlockMap:
            if( !opts.sFile.isSet() )
            {
                sio << "Please specify a file manipulate." << sio.nl;
                return 0;
            }
            {
                File fIn( opts.sFile, File::Write|File::Read );
                Myriad m( fIn );
                printMap( m.buildBlockUseMap() );
                printMap( m.buildBlockMap() );
            }
            break;

        case modeNone:
            sio << "Please select a mode, for more info, try --help."
                << sio.nl << sio.nl;
            break;
    }

    return 0;
}
