/**
 * ==========================================================================
 * Copyright (c) 2003-2009  Martin Hauner
 *               2010       http://subcommander.tigris.org
 *
 * This file is part of Subcommander.
 *
 * Subcommander  is free software: you  can redistribute  it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 2 of the License,  or (at your option)
 * any later version.
 *
 * Subcommander  is  distributed  in  the  hope  that  it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS  FOR A  PARTICULAR PURPOSE. See the  GNU General Public License
 * for more details.
 *
 * You should have  received a copy of the  GNU General Public License  along
 * with Subcommander. If not, see <http://www.gnu.org/licenses/>.
 * ==========================================================================
 */

// sys
#include <algorithm>
#include <assert.h> 

// apr
#include <apr_pools.h>
#include <apr_getopt.h>
#include <apr_strings.h>

// svn
#include <svn_diff.h>

// sc
#include "config.h"
#include "MainWindow.h"
#include "Diff3Widget.h"
#include "DiffParam.h"
#include "ConfigManager.h"
#include "Settings.h"
#include "sublib/MacStyle.h"
#include "sublib/Utility.h"
#include "sublib/MsgHandler.h"
#include "sublib/ErrorDialog.h"
#include "sublib/IconFactory.h"
#include "util/apr.h"
#include "util/Exception.h"

// qt
#include <QtGui/QApplication>

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

typedef struct sc_options_state
{
  sc_options_state()
  {
    help       = false;
    debug      = false;
    whitespace = true;
    encoding   = "*"; // means => APR_LOCALE_CHARSET;

    labels[0] = 0;
    labels[1] = 0;
    labels[2] = 0;
  }

  bool        help;
  bool        debug;
  bool        whitespace;     // diff whitespace
  sc::String  encoding;
  const char* labels[3];

} sc_options_state_t;


typedef int (*cmd_func)( sc_options_state_t *optState, apr_getopt_t* opt );


const int MaxAliases = 3;
const int MaxOptions = 10;

typedef struct sc_command
{
  const char*  name;
  cmd_func     func;
  const char*  aliases[MaxAliases];
  int          options[MaxOptions];
  const char*  desc;
  const char*  usage;

} sc_command_t;

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

const sc_command_t* lookupCommand( const sc_command_t table[], const char* command )
{
  assert(table);
  if( ! command )
  {
    return 0;
  }

  for( int i = 0; table[i].name; i++ )
  {
    if( strcasecmp(command, table[i].name) == 0 ) // stricmp
    {
      return &table[i];
    }

    for( int a = 0; a < MaxAliases; a++ )
    {
      if( ! table[i].aliases[a] )
      {
        break;
      }

      if( strcasecmp( command, table[i].aliases[a] ) == 0 ) //stricmp
      {
        return &table[i];
      }
    }
  }
  return 0;
}

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

int nop( sc_options_state_t *optState, apr_getopt_t* os );
int help( sc_options_state_t *optState, apr_getopt_t* os );
int diff( sc_options_state_t *optState, apr_getopt_t* os );
int diff3( sc_options_state_t *optState, apr_getopt_t* os );

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

const int WhitespaceOff = 300;
const int WhitespaceOn  = 301;

const apr_getopt_option_t sc_options[] =
{
  { "help",        'h',           0, "show help on a subcommand" },
  { NULL,          '?',           0, "show help on a subcommand" },
  { "w-",          WhitespaceOff, 0, "whitespaces off (ignore)" },
  { "w+",          WhitespaceOn,  0, "whitespaces on" },
  { "encoding",    'e',           1, "encoding/codepage (iconv)" },
  { "label",       'L',           1, "label for a given file (per file)" },
  { "debug",       'd',           0, "print debug info to stderr" },
  { 0, 0, 0, 0 }
};

const sc_command_t sc_commands[] =
{
  {
    "diff", diff, {"diff2", 0}, { 'e', 'L', WhitespaceOn, WhitespaceOff, '\0' },
    "display differences of two files.\n",
    "usage: diff original modified\n"
  },
  {
    "diff3", diff3, {"merge", 0}, { 'e', 'L', WhitespaceOn, WhitespaceOff,'\0' },
    "visually merge differences of modifed and latest.\n",
    "usage: diff3 original modified latest merged\n"
  },
  {
    "help", help, {"?", "h", 0}, {0},
    "display this usage message.\n",
    "usage: help [SUBCOMMAND...]\n"
  },
  { 0
  }
};

sc_command_t nullCommand = 
{
    "null", nop, {0}, {0},
    "do nothing.\n"
};

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

const char helpHeader[] = 
  "usage: submerge <subcommand> [options] [args]\n"
  "Type 'submerge help <subcommand>' for help on a specific subcommand.\n"
  "\n"
  "Available subcommands:\n";

const char helpFooter[] =
  "\n"
  "Subcommander is a Subversion gui client, diff and merge tool.\n"
  "For additional information, see http://subcommander.tigris.org/\n"
  "\n"
  "Subversion is a tool for revision control.\n"
  "For additional information, see http://subversion.tigris.org/\n"
  "\n";

/////////////////////////////////////////////////////////////////////
// cmd_funcs

const int Failure = -1;
const int Success = 0;
const int Close   = 1;

int nop( sc_options_state_t *optState, apr_getopt_t* os )
{
  return Success;
}

void printfAliases( const sc_command_t* cmd )
{
  bool firstOpt = true;

  for( int cntAl = 0; cntAl < MaxAliases; cntAl++ )
  {
    if( ! cmd->aliases[cntAl] )
    {
      break;
    }

    if( firstOpt )
    {
      printf( " (" );
      firstOpt = false;
    }
    else
    {
      printf( ", " );
    }

    printf( "%s", cmd->aliases[cntAl] );
   }
   if( !firstOpt )
   {
     printf( ")" );
   }
}

const apr_getopt_option_t* getOption( int option )
{
  int i = 0;
  while( sc_options[i].optch != 0 )
  {
    if( sc_options[i].optch == option )
    {
      return &sc_options[i];
    }
    i++;
  }
  return NULL;
}

void printfOptions( const sc_command_t* cmd )
{
  if( cmd->options[0] )
  {
    printf( "Valid options:\n" );
  }

  for( int cnt = 0; cnt < MaxOptions; cnt++ )
  {
    if( ! cmd->options[cnt] )
    {
      break;
    }

    sc::String text;
    char       tmp[200];

    const apr_getopt_option_t* opt = getOption(cmd->options[cnt]);

    bool optch = false;
    if( opt->optch < 255 )
    {
      sprintf( tmp, "-%c ", opt->optch );
      text += tmp;
      optch = true;
    }

    if( opt->name && optch )
    {
      sprintf( tmp, "[--%s] ", opt->name );
      text += tmp;
    }
    else if( opt->name )
    {
      sprintf( tmp, "--%s ", opt->name );
      text += tmp;
    }

    if( opt->has_arg )
    {
      text += "arg";
    }

    printf( "  %-25s : %s\n", (const char*)text,
      opt->description ? opt->description : "missing description!"
      );
  }
}

int help( sc_options_state_t *optState, apr_getopt_t* os )
{
  const char* firstArg = 0;

  if( ! optState || ! os )
  {
    goto short_help;
  }

  firstArg = os->argv[os->ind++];
  if( firstArg )
  {
    const sc_command_t* command = lookupCommand( sc_commands, firstArg );

    if( !command )
    {
      fprintf( stderr, "unknown command: %s\n", firstArg );
      goto short_help;
    }

    printf( "%s: %s", command->name, command->desc );
    printf( "%s", command->usage );
    printf( "%s", "\n\n" );
    printfOptions( command );
  }
  else
  {
    printf( "%s", helpHeader );

    for( int cntCmd = 0; sc_commands[cntCmd].name; cntCmd++ )
    {
      printf( "  %s", sc_commands[cntCmd].name );
      printfAliases( &sc_commands[cntCmd] );
      printf( "\n" );
    }

    printf( "%s", helpFooter );
  }

  return Success;

short_help:
  fprintf( stderr, "type 'sm help' for usage.\n" ); 
  return Failure;
}

int help()
{
  return help( NULL, NULL );
}

int diff( sc_options_state_t *optState, apr_getopt_t* os )
{
  if( os->argc - os->ind != 2 )
  {
    // todo print usage
    return Failure;
  }

  MainWindow*  mw = (MainWindow*)qApp->mainWidget();

  DiffParamPtr param( new DiffParam() );
  param->_type       = DiffParam::Diff;
  param->_original   = FileDataPtr( new FileData( sc::String(os->argv[os->ind]), sc::String(optState->encoding) ) );
  param->_modified   = FileDataPtr( new FileData( sc::String(os->argv[os->ind+1]), sc::String(optState->encoding) ) );
  param->_encoding   = optState->encoding;
  param->_whitespace = optState->whitespace;
  param->_activeDiff = 0;
  param->_originalLabel = optState->labels[0];
  param->_modifiedLabel = optState->labels[1];

  const sc::Error* err = mw->diff( param );

  if( err )
  {
    return Close;
  }

  return Success;
}

int diff3( sc_options_state_t *optState, apr_getopt_t* os )
{
  if( os->argc - os->ind != 4 )
  {
    return Failure;
  }

  MainWindow*  mw = (MainWindow*)qApp->mainWidget();

  DiffParamPtr param( new DiffParam() );
  param->_type       = DiffParam::Diff3;
  param->_original   = FileDataPtr( new FileData( sc::String(os->argv[os->ind]), sc::String(optState->encoding) ) );
  param->_modified   = FileDataPtr( new FileData( sc::String(os->argv[os->ind+1]), sc::String(optState->encoding) ) );
  param->_latest     = FileDataPtr( new FileData( sc::String(os->argv[os->ind+2]), sc::String(optState->encoding) ) );
  param->_merged     = os->argv[os->ind+3];
  param->_encoding   = optState->encoding;
  param->_whitespace = optState->whitespace;
  param->_activeDiff = 0;
  param->_originalLabel = optState->labels[0];
  param->_modifiedLabel = optState->labels[1];
  param->_latestLabel   = optState->labels[2];

  const sc::Error* err = mw->diff3( param );

  if( err )
  {
    return Close;
  }

  return Success;
}

///////////////////////////////////////////////////////////////////////////////
// entry point

static apr_pool_t* pool;

void cleanup()
{
  apr::destroyPool(pool);
}

int main( int argc, char* argv[] )
{
  QApplication* app = 0;
  try
  {
    setLocale();
    
    // init stacktrace
    initStackProcess();

    // init apr
    apr::initialize(argc,argv);

    // init qt
    app = new QApplication( argc, argv );
    setAppName("submerge");
    Q_INIT_RESOURCE(sublib);
    
#ifndef _MACOSX
    // set icon
    app->setWindowIcon(IconFactory::createIcon("Logo_Icon_16x16.png"));
#endif // _MACOSX
    
    // read config
    ConfigManager config;
    Settings::setup(config);
    config.load();

    // set font
    // if we set the font allthough it is the same font as the default (system) font
    // we get a different font in popup menus. Looks like a bug in Qt 3.
    if( qApp->font() != config.getGeneralFont() )
    {
      QApplication::setFont( config.getGeneralFont() );
    }

    // setup locale path
    enableLocale( config.getL10n() );

    // setup gui
    MainWindow* mw = new MainWindow(&config);
    app->setMainWidget(mw);
    mw->show();

    // create pool for parsing command line
    pool = apr::createPool();
    atexit( cleanup );

    // parse options...
    apr_getopt_t*       os = 0;
    sc_options_state_t  optState;
    apr_status_t        status;

    optState.whitespace = config.getOptWhitespace();

    status = apr_getopt_init( &os, pool, argc, argv );
    os->interleave = 1;

    while( true )
    {
      int         option_ch;
      const char* option_arg;
      
      status = apr_getopt_long( os, sc_options, &option_ch, &option_arg );
      if( APR_STATUS_IS_EOF(status) )
      {
        break;
      }
      else if( status != APR_SUCCESS )
      {
        help();
        return EXIT_FAILURE;
      }

      char* dup = apr_pstrdup( pool, option_arg );
      switch( option_ch )
      {
      case 'e':
        {
          optState.encoding = dup;
          break;
        }
      case 'd':
        {
          optState.debug = true;
          break;
        }
      case WhitespaceOff:
        {
          optState.whitespace = false;
          break;
        }
      case WhitespaceOn:
        {
          optState.whitespace = true;
          break;
        }
      case 'L':
        {
          static int cnt = 0;

          if( cnt < 3 )
          {
            optState.labels[cnt] = dup;
            cnt++;
          }
          break;
        }
      case 'h':
      case '?':
        {
          optState.help = true;
          break;
        }
      }
    }

    if( optState.debug )
    {
      fprintf( stderr, "submerge dbg: arguments,\n" );
      for( int i=0; i < argc; i++ )
      {
        fprintf( stderr, "%02d: %s\n", i, argv[i] );
      }
    }

    // lookup the command
    const sc_command_t* command = NULL;

    if( optState.help )
    {
       command = lookupCommand( sc_commands, "help" );
    }

    if( !command )
    {
      const char* firstArg = os->argv[os->ind++];
      command  = lookupCommand( sc_commands, firstArg );

      if( !command )
      {
        command = &nullCommand;

        //if( appType == atConsole )
        //{
          //help();
          //return EXIT_FAILURE;
        //}
      }
    }

    if( optState.debug )
    {
      fprintf( stderr, "submerge dbg: running cmd %s\n", command->name );
    }

    // at last, call the command....
    int res = command->func( &optState, os );

    // if it was a help command, just return.
    if( optState.help || strcmp(command->name, "help")==0 )
    {
      return res;
    }

    // run the gui
    if( res == Success )
    {
      int result = app->exec();

      delete mw;
      delete app;

      config.save();

      return result;
    }
    else if( res == Close )
    {
      delete app;
      return EXIT_SUCCESS;
    }
    else
    {
      fprintf( stderr, "submerge: failed to run %s command.\n", command->name );
      return EXIT_FAILURE;
    }
  }
  catch( sc::Exception& e )
  {
    ErrorDialog* dlg = new ErrorDialog(e);
    dlg->exec();

    return EXIT_FAILURE;
  }
}
