/*
 * 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/streamstack.h"
#include "bu/file.h"
#include "bu/myriadfs.h"
#include "bu/myriadstream.h"
#include "bu/optparser.h"

enum Mode
{
    modeList,
    modeCat,
    modeCopyIn,
    modeCopyOut,
    modeMkdir,
    modeInitialize,
    modeRm,

    modeNone
};

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

class Options : public Bu::OptParser
{
public:
    Options( int argc, char *argv[] ) :
        eMode( modeNone ),
        iBlockSize( 64 )
    {
        addHelpBanner("Options:");
        addOption( sFile, 'f', "file", "Myriadfs file");
        addOption( iBlockSize, 'b', "block-size",
            "Specify the block size when initializing a new MyriadFs");

        setNonOption( Bu::slot( this, &Options::nonOption ) );

        addHelpOption();

        parse( argc, argv );
    }

    int nonOption( Bu::Array<Bu::String> aParams )
    {
        if( eMode == modeNone )
        {
            //Bu::println("Checking mode");
            // First param, must be the mode
            if( aParams[0] == "ls" )
                eMode = modeList;
            else if( aParams[0] == "cat" )
                eMode = modeCat;
            else if( aParams[0] == "cp-in" )
                eMode = modeCopyIn;
            else if( aParams[0] == "cp-out" )
                eMode = modeCopyOut;
            else if( aParams[0] == "mkdir" )
                eMode = modeMkdir;
            else if( aParams[0] == "initialize" )
                eMode = modeInitialize;
            else if( aParams[0] == "rm" )
                eMode = modeRm;
            else
                Bu::println("Unknown command, try --help");
            return 0;
        } else {
            lParams.append( aParams[0] );
        }
        //Bu::println("Got option: \"%1\"").arg( aParams[0] );
        return 0;
    }

    Mode eMode;
    Bu::String sFile;
    Bu::StringList lParams;
    int iBlockSize;
};

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

    if( opt.sFile.isEmpty() )
    {
        Bu::println("You must specify a MyriadFs stream (see -f).\n");
        return 1;
    }

    if( opt.eMode == modeNone )
    {
        Bu::println("Specify an operation to perfrom.\n");
        return 1;
    }

    int iFlags = Bu::File::ReadWrite;

    if( opt.eMode == modeInitialize )
    {
        iFlags |= Bu::File::Create|Bu::File::Truncate;
    }

    Bu::File fFs( opt.sFile, iFlags );
    Bu::MyriadFs mFs( fFs, opt.iBlockSize );

    switch( opt.eMode )
    {
		case modeList:
            {
                Bu::String sPath = "/";
                if( opt.lParams.getSize() > 0 )
                {
                    sPath = opt.lParams.first();
                }
                Bu::MyriadFs::Dir lEnt = mFs.readDir( sPath );
                for( Bu::MyriadFs::Dir::iterator i = lEnt.begin(); i; i++ )
                {
                    Bu::String sPerm;
                    switch( (*i).uPerms&Bu::MyriadFs::typeMask )
                    {
                        case Bu::MyriadFs::typeDir: sPerm += "d"; break;
                        case Bu::MyriadFs::typeChrDev: sPerm += "c"; break;
                        case Bu::MyriadFs::typeBlkDev: sPerm += "b"; break;
                        case Bu::MyriadFs::typeSymLink: sPerm += "l"; break;
                        case Bu::MyriadFs::typeSocket: sPerm += "s"; break;
                        default: sPerm += "-"; break;
                    }
                    sPerm += ((*i).uPerms&Bu::MyriadFs::permUsrR)?"r":"-";
                    sPerm += ((*i).uPerms&Bu::MyriadFs::permUsrW)?"w":"-";
                    sPerm += ((*i).uPerms&Bu::MyriadFs::permUsrX)?"x":"-";
                    sPerm += ((*i).uPerms&Bu::MyriadFs::permGrpR)?"r":"-";
                    sPerm += ((*i).uPerms&Bu::MyriadFs::permGrpW)?"w":"-";
                    sPerm += ((*i).uPerms&Bu::MyriadFs::permGrpX)?"x":"-";
                    sPerm += ((*i).uPerms&Bu::MyriadFs::permOthR)?"r":"-";
                    sPerm += ((*i).uPerms&Bu::MyriadFs::permOthW)?"w":"-";
                    sPerm += ((*i).uPerms&Bu::MyriadFs::permOthX)?"x":"-";
                    Bu::println("%1 %2 %3:%4 %5 %6")
                        .arg( sPerm )
                        .arg( (*i).iNode )
                        .arg( (*i).iUser )
                        .arg( (*i).iGroup )
                        .arg( (*i).iSize )
                        .arg( (*i).sName );
                }
            }
			break;

		case modeCat:
            {
                if( opt.lParams.isEmpty() )
                {
                    Bu::println("Specify at least one file.");
                    return 1;
                }
                int iBlockSize = 1024*1024;
                char *pBuf = new char[iBlockSize];
                for( Bu::StringList::iterator i = opt.lParams.begin(); i; i++ )
                {
                    Bu::MyriadStream ms = mFs.open( *i, Bu::MyriadFs::Read );
                    int iRead = 0;
                    do
                    {
                        iRead = ms.read( pBuf, iBlockSize );
                        if( iRead > 0 )
                        {
                            Bu::sioRaw.write( pBuf, iRead );
                        }
                    } while( iRead == iBlockSize );
                }
                delete[] pBuf;
            }
			break;

		case modeCopyIn:
            {
                if( opt.lParams.getSize() != 2 )
                {
                    Bu::println("Specify a source file and destination file.");
                    return 1;
                }
                int iBlockSize = 1024*1024;
                char *pBuf = new char[iBlockSize];
                Bu::File fs = Bu::File(
                    opt.lParams.first(), Bu::File::Read );
                Bu::MyriadStream ms = mFs.open(
                    opt.lParams.last(), Bu::MyriadFs::WriteNew );
                int iRead = 0;
                do
                {
                    iRead = fs.read( pBuf, iBlockSize );
                    if( iRead > 0 )
                    {
                        ms.write( pBuf, iRead );
                    }
                } while( iRead == iBlockSize );
                delete[] pBuf;
            }
			break;

		case modeCopyOut:
            {
                if( opt.lParams.getSize() != 2 )
                {
                    Bu::println("Specify a source file and destination file.");
                    return 1;
                }
                int iBlockSize = 1024*1024;
                char *pBuf = new char[iBlockSize];
                Bu::MyriadStream ms = mFs.open(
                    opt.lParams.first(), Bu::MyriadFs::Read );
                Bu::File fs = Bu::File(
                    opt.lParams.last(), Bu::File::WriteNew );
                int iRead = 0;
                do
                {
                    iRead = ms.read( pBuf, iBlockSize );
                    if( iRead > 0 )
                    {
                        fs.write( pBuf, iRead );
                    }
                } while( iRead == iBlockSize );
                delete[] pBuf;
            }
			break;

		case modeMkdir:
            {
                if( opt.lParams.isEmpty() )
                {
                    Bu::println("Specify at least one directory.");
                    return 1;
                }
                for( Bu::StringList::iterator i = opt.lParams.begin(); i; i++ )
                {
                    mFs.mkDir( *i, 0777 );
                }
            }
			break;

		case modeInitialize:
            Bu::println("MyriadFs initialized.\n");
			break;

		case modeRm:
            {
                if( opt.lParams.getSize() != 1 )
                {
                    Bu::println("Specify a file path.");
                    return 1;
                }
                mFs.unlink( opt.lParams.first() );
            }
			break;

		case modeNone:
			break;
    }

    return 0;
}
