///////////////////////////////////////////////////////////////////////////////
//
//  The contents of this file are subject to the Mozilla Public License
//  Version 1.1 (the "License"); you may not use this file except in
//  compliance with the License. You may obtain a copy of the License at
//  http://www.mozilla.org/MPL/
//
//  Software distributed under the License is distributed on an "AS IS"
//  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
//  License for the specific language governing rights and limitations
//  under the License.
// 
//  The Original Code is MP4v2.
// 
//  The Initial Developer of the Original Code is Kona Blend.
//  Portions created by Kona Blend are Copyright (C) 2008.
//  Portions created by David Byron are Copyright (C) 2010.
//  All Rights Reserved.
//
//  Contributors:
//      Kona Blend, kona8lend@@gmail.com
//      David Byron, dbyron@dbyron.com
//
///////////////////////////////////////////////////////////////////////////////

#include "util/impl.h"

namespace mp4v2 { namespace util {

///////////////////////////////////////////////////////////////////////////////

class FileUtility : public Utility
{
private:
    enum FileLongCode {
        LC_LIST = _LC_MAX,
        LC_OPTIMIZE,
        LC_DUMP
    };

public:
    FileUtility( int, char** );

protected:
    // delegates implementation
    bool utility_option( int, bool& );
    bool utility_job( JobContext& );

private:
    bool actionList     ( JobContext& );
    bool actionOptimize ( JobContext& );
    bool actionDump     ( JobContext& );

private:
    Group _actionGroup;

    bool (FileUtility::*_action)( JobContext& );
};

///////////////////////////////////////////////////////////////////////////////

FileUtility::FileUtility( int argc, char** argv )
    : Utility      ( "mp4file", argc, argv )
    , _actionGroup ( "ACTIONS" )
    , _action      ( NULL )
{
    // add standard options which make sense for this utility
    _group.add( STD_DRYRUN );
    _group.add( STD_KEEPGOING );
    _group.add( STD_QUIET );
    _group.add( STD_DEBUG );
    _group.add( STD_VERBOSE );
    _group.add( STD_HELP );
    _group.add( STD_VERSION );
    _group.add( STD_VERSIONX );

    _actionGroup.add( "list",     false, LC_LIST,     "list (summary information)" );
    _actionGroup.add( "optimize", false, LC_OPTIMIZE, "optimize mp4 structure" );
    _actionGroup.add( "dump",     false, LC_DUMP,     "dump mp4 structure in human-readable format" );
    _groups.push_back( &_actionGroup );

    _usage = "[OPTION]... ACTION file...";
    _description =
        // 79-cols, inclusive, max desired width
        // |----------------------------------------------------------------------------|
        "\nFor each mp4 file specified, perform the specified ACTION. An action must be"
        "\nspecified. Some options are not applicable to some actions.";
}

///////////////////////////////////////////////////////////////////////////////

bool
FileUtility::actionDump( JobContext& job )
{
    job.fileHandle = MP4Read( job.file.c_str() );
    if( job.fileHandle == MP4_INVALID_FILE_HANDLE )
        return herrf( "unable to open for read: %s\n", job.file.c_str() );

    if( !MP4Dump( job.fileHandle, _debugImplicits ))
        return herrf( "dump failed: %s\n", job.file.c_str() );

    return SUCCESS;
}

///////////////////////////////////////////////////////////////////////////////

bool
FileUtility::actionList( JobContext& job )
{
    ostringstream report;

    const int wbrand = 5;
    const int wcompat = 18;
    const int wsizing = 6;
    const string sep = "  ";

    if( _jobCount == 0 ) {
        report << setw(wbrand) << left << "BRAND" 
               << sep << setw(wcompat) << left << "COMPAT" 
               << sep << setw(wsizing) << left << "SIZING" 
               << sep << setw(0) << "FILE"
               << '\n';

        report << setfill('-') << setw(70) << "" << setfill(' ') << '\n';
    }

    job.fileHandle = MP4Read( job.file.c_str() );
    if( job.fileHandle == MP4_INVALID_FILE_HANDLE )
        return herrf( "unable to open for read: %s\n", job.file.c_str() );

    FileSummaryInfo info;
    if( fileFetchSummaryInfo( job.fileHandle, info ))
        return herrf( "unable to fetch file summary info" );

    string compat;
    {
        const FileSummaryInfo::BrandSet::iterator ie = info.compatible_brands.end();
        int count = 0;
        for( FileSummaryInfo::BrandSet::iterator it = info.compatible_brands.begin(); it != ie; ++it, count++ ) {
            if( count > 0 )
                compat += ',';
            compat += *it;
        }
    }

    const bool sizing = info.nlargesize | info.nversion1 | info.nspecial;

    report << setw(wbrand) << left << info.major_brand
           << sep << setw(wcompat) << left << compat
           << sep << setw(wsizing) << left << (sizing ? "64-bit" : "32-bit")
           << sep << job.file
           << '\n';

    verbose1f( "%s", report.str().c_str() );
    return SUCCESS;
}

///////////////////////////////////////////////////////////////////////////////

bool
FileUtility::actionOptimize( JobContext& job )
{
    verbose1f( "optimizing %s\n", job.file.c_str() );

    if( dryrunAbort() )
        return SUCCESS;

    if( !MP4Optimize( job.file.c_str(), NULL ))
        return herrf( "optimize failed: %s\n", job.file.c_str() );

    return SUCCESS;
}

///////////////////////////////////////////////////////////////////////////////

bool
FileUtility::utility_job( JobContext& job )
{
    if( !_action )
        return herrf( "no action specified\n" );

    return (this->*_action)( job );
}

///////////////////////////////////////////////////////////////////////////////

bool
FileUtility::utility_option( int code, bool& handled )
{
    handled = true;

    switch( code ) {
        case LC_LIST:
            _action = &FileUtility::actionList;
            break;

        case LC_OPTIMIZE:
            _action = &FileUtility::actionOptimize;
            break;

        case LC_DUMP:
            _action = &FileUtility::actionDump;
            break;

        default:
            handled = false;
            break;
    }

    return SUCCESS;
}

///////////////////////////////////////////////////////////////////////////////

}} // namespace mp4v2::util

///////////////////////////////////////////////////////////////////////////////

extern "C"
int main( int argc, char** argv )
{
    mp4v2::util::FileUtility util( argc, argv );
    return util.process();
}
