/*
    This file is part of libqobex.

    Copyright (c) 2003 Mathias Froehlich <Mathias.Froehlich@web.de>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include <qstring.h>
#include <qstringlist.h>
#include <qcstring.h>
#include <qfile.h>

#include <qobex/qobexintransport.h>
#include <qobex/qobexserialtransport.h>
#include <qobex/qobexbfbtransport.h>
#include <qobex/qobexericssontransport.h>
#ifdef HAVE_QOBEX_BLUETOOTH
#include <qobex/qobexbttransport.h>
#endif
#ifdef HAVE_QOBEX_IRDA
#include <qobex/qobexirdatransport.h>
#endif
#include <qobex/qobexuuid.h>

#include "client.h"

#define ARGV0 "qobexclient"

void print_usage()
{
  fprintf( stderr, "\n" );
  fprintf( stderr, "  " ARGV0 " - Swiss army knife for obex testing/development.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "  usage:\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "  " ARGV0 " [-ghlnpv] [-d dest]  [-D dumpfile] [-f localfile] [-m mimetype]\n" );
  fprintf( stderr, "    [-s speed] [-t transport] [-u uuid]\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -a secret\n" );
  fprintf( stderr, "      Authentication secret to use for authenticatiion\n" );
  fprintf( stderr, "      with the server.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -c\n" );
  fprintf( stderr, "      This option toggels \"cd into\" mode.\n" );
  fprintf( stderr, "      The default is to use the argument to put and get\n" );
  fprintf( stderr, "      in the name header uf the requests. But \"cd into\"\n" );
  fprintf( stderr, "      means that the client first sends a chain of set\n" );
  fprintf( stderr, "      path commands before doing the actual operation.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -d dest\n" );
  fprintf( stderr, "      Sets the destination of the operation. The argument\n" );
  fprintf( stderr, "      of dest depends on the transport.\n" );
  fprintf( stderr, "      For serial connections this shouold be the\n" );
  fprintf( stderr, "      device name. Defaults to /dev/ttyS0.\n" );
  fprintf( stderr, "      For ip transports use the ip address or hostname.\n" );
  fprintf( stderr, "      Defaults to localhost.\n" );
  fprintf( stderr, "      For irda or bluetooth specify a hardware address.\n" );
  fprintf( stderr, "      If no hardware address is given available devices\n" );
  fprintf( stderr, "      are discovered. The transport takes the first device\n" );
  fprintf( stderr, "      which offers the required service.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -D dumpfile\n" );
  fprintf( stderr, "      Dump raw obex packets into dumpfile.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -f localfile\n" );
  fprintf( stderr, "      The local filename to use. Defaults to stdout/stdin.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -g\n" );
  fprintf( stderr, "      Do a get request.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -h\n" );
  fprintf( stderr, "      Print this help.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -l\n" );
  fprintf( stderr, "      Request a folder listing.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -m mimetype\n" );
  fprintf( stderr, "      Specify a mimetype (type header) for a get request.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -n\n" );
  fprintf( stderr, "      Do not use OBEX connect or disconnect requests.\n" );
  fprintf( stderr, "      FIXME implement this in the client code.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -p\n" );
  fprintf( stderr, "      Do a put request.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -r realm\n" );
  fprintf( stderr, "      Authentication realm to use.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -s speed\n" );
  fprintf( stderr, "      Sets the speed if serial connections. Note that\n" );
  fprintf( stderr, "      the given integer must match a valid serial speed.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -t transport\n" );
  fprintf( stderr, "      Sets the transport to use. Valid values are:\n" );
  fprintf( stderr, "        serial\t\tPlain serial connections.\n" );
  fprintf( stderr, "        bfb\t\tThe SIEMENS serial cable protocol.\n" );
  fprintf( stderr, "        siemens\t\tThe SIEMENS serial cable protocol.\n" );
  fprintf( stderr, "        ericsson\tThe ERICSSON serial cable protocol.\n" );
#ifdef HAVE_QOBEX_BLUETOOTH
  fprintf( stderr, "        bt\t\tBluetooth.\n" );
  fprintf( stderr, "        bluetooth\tBluetooth.\n" );
#endif
#ifdef HAVE_QOBEX_IRDA
  fprintf( stderr, "        ir\t\tIrDA\n" );
  fprintf( stderr, "        irda\t\tIrDA\n" );
#endif
  fprintf( stderr, "        in\t\tTCP/IP\n" );
  fprintf( stderr, "        ip\t\tTCP/IP\n" );
  fprintf( stderr, "        tcp\t\tTCP/IP\n" );
  fprintf( stderr, "      Default is irda.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -u uuid\n" );
  fprintf( stderr, "      Sets OBEX uuid for use in the connect packet.\n" );
  fprintf( stderr, "      Valid values are:\n" );
  fprintf( stderr, "        inbox\t\tConnect without uuid.\n" );
  fprintf( stderr, "        fbs\t\tConnect with the folder browsing service uuid.\n" );
  fprintf( stderr, "        irmcsync\tConnect with the IrMCSync uuid.\n" );
  fprintf( stderr, "        syncml\t\tConnect with the SyncML uuid.\n" );
  fprintf( stderr, "        syncmlsync\tConnect with the SyncMLSync uuid.\n" );
  fprintf( stderr, "        syncmldm\tConnect with the SyncMLDM uuid.\n" );
  fprintf( stderr, "        s45\t\tConnect with the SIEMENS s45 uuid.\n" );
  fprintf( stderr, "      Default is fbs.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "    -v\n" );
  fprintf( stderr, "      Verbose mode. Prints information about the\n" );
  fprintf( stderr, "      received packages.\n" );
  fprintf( stderr, "\n" );
  fprintf( stderr, "\n" );
}

extern char *optarg;
extern int optind;

int main( int argc, char **argv )
{
  int speed = 0;
  QString dest;

  enum Transport { In, Bluetooth, IrDA, BFB, Ericsson, Serial };
  Transport ta = IrDA;

  enum Operation { Get, Put };
  Operation op = Get;

  enum Uuid { inbox, FBS, IrMCSync, SyncML, SyncMLSync, SyncMLDM, S45 };
  Uuid uuid = inbox;

  QFile file;
  QFile dumpFile;
  QString mimetype;

  QString name;

  QString realm;
  QString auth;

  bool openobex = false;

  bool verbose = false;

  bool connectedOperation = true;

  bool cdInto = false;
  bool folderBrowsingMode = false;

  bool initiateAuth = false;
  
  int c;
  while ((c = getopt( argc, argv, "a:cd:D:f:ghilm:nopr:s:t:u:v" )) != EOF)
    switch ( c ) {
    case 'a':
      auth = QFile::decodeName( optarg );
      break;
    case 'c':
      cdInto = true;
      break;
    case 'd':
      dest = QFile::decodeName( optarg );
      break;
    case 'D':
      dumpFile.setName( QFile::decodeName( optarg ) );
      dumpFile.open( IO_WriteOnly );
      break;
    case 'f':
      {
	QString name = QFile::decodeName( optarg );
	if ( name !=  "-" )
	  file.setName( name );
      }
      break;
    case 'g':
      op = Get;
      break;
    case 'h':
      print_usage();
      exit( EXIT_SUCCESS );
    case 'i':
      initiateAuth = true;
      break;
    case 'l':
      mimetype = "x-obex/folder-listing";
      cdInto = true;
      folderBrowsingMode = true;
      uuid = FBS;
      break;
    case 'm':
      mimetype = QFile::decodeName( optarg );
      break;
    case 'o':
      openobex = true;
      break;
    case 'n':
      connectedOperation = false;
      break;
    case 'p':
      op = Put;
      break;
    case 'r':
      realm = QFile::decodeName( optarg );
      break;
    case 's':
      speed = atoi( optarg );
      break;
    case 't':
      {
	QString ts = optarg;
	if ( ts == "serial" ) {
	  ta = Serial;
	} else if ( ts == "bfb" || ts == "siemens" ) {
	  ta = BFB;
	} else if ( ts == "ericsson" ) {
	  ta = Ericsson;
#ifdef HAVE_QOBEX_BLUETOOTH
	} else if ( ts == "bt" || ts == "bluetooth" ) {
	  ta = Bluetooth;
#endif
#ifdef HAVE_QOBEX_IRDA
	} else if ( ts == "ir" || ts == "irda" ) {
	  ta = IrDA;
#endif
	} else if ( ts == "in" || ts == "ip" || ts == "tcp" ) {
	  ta = In;
	} else {
	  fprintf( stderr, "Invalid transport argument.\n" );
	  exit( EXIT_FAILURE );
	}
      }
      break;
    case 'u':
      {
	QString us = optarg;
	if ( us == "inbox" ) {
	  uuid = inbox;
	} else if ( us == "fbs" || us == "FBS" || us == "folderbrowsing" ) {
	  uuid = FBS;
	} else if ( us == "irmcsync" ) {
	  uuid = IrMCSync;
	} else if ( us == "syncml" ) {
	  uuid = SyncML;
	} else if ( us == "syncmlsync" ) {
	  uuid = SyncMLSync;
	} else if ( us == "syncmldm" ) {
	  uuid = SyncMLDM;
	} else if ( us == "s45" || us == "siemens" ) {
	  uuid = S45;
	} else {
	  fprintf( stderr, "Invalid uuid argument.\n" );
	  exit( EXIT_FAILURE );
	}
      }
      break;
    case 'v':
      verbose = true;
      break;
    default:
      print_usage();
      exit( EXIT_FAILURE );
    }
  if ( optind < argc )
    name = QFile::decodeName( argv[optind] );

  QObexTransport* transport = 0;
  switch ( ta ) {
  case Serial:
    {
      QObexSerialTransport* serialTransport = new QObexSerialTransport;
      if ( !dest.isEmpty() )
	serialTransport->setDevice( dest );
      if ( 0 < speed )
	serialTransport->setSpeed( speed );
      transport = serialTransport;
    }
    break;
  case BFB:
    {
      QObexBfbTransport* bfbTransport = new QObexBfbTransport;
      if ( !dest.isEmpty() )
	bfbTransport->setDevice( dest );
      if ( 0 < speed )
	bfbTransport->setSpeed( speed );
      transport = bfbTransport;
    }
    break;
  case Ericsson:
    {
      QObexEricssonTransport* ericssonTransport = new QObexEricssonTransport;
      if ( !dest.isEmpty() )
	ericssonTransport->setDevice( dest );
      if ( 0 < speed )
	ericssonTransport->setSpeed( speed );
      transport = ericssonTransport;
    }
    break;
#ifdef HAVE_QOBEX_BLUETOOTH
  case Bluetooth:
    {
      QObexBtTransport* btTransport = new QObexBtTransport;
      if ( !dest.isEmpty() )
	btTransport->setDestAddress( dest );
      switch ( uuid ) {
      case FBS:
      case S45:
	btTransport->setUuid( QObexBtTransport::ObexFileServ );
	break;
      case IrMCSync:
	btTransport->setUuid( QObexBtTransport::IrMCServ );
	break;
      default:
	btTransport->setUuid( QObexBtTransport::ObexPushServ );
	break;
      }
      transport = btTransport;
    }
    break;
#endif
#ifdef HAVE_QOBEX_IRDA
  case IrDA:
    {
      QObexIrDATransport* irdaTransport = new QObexIrDATransport;
      if ( !dest.isEmpty() )
	irdaTransport->setDestAddress( dest );
      transport = irdaTransport;
    }
    break;
#endif
  case In:
    {
      QObexInTransport* inTransport = new QObexInTransport;
      if ( dest.isEmpty() )
	inTransport->setHost( "localhost" );
      else
	inTransport->setHost( dest );
      transport = inTransport;
    }
    break;
  }
  if ( !transport ) {
    fprintf( stderr, "Ops, transport is zero!?\n" );
    exit( EXIT_FAILURE );
  }
  transport->setBlocking( true );

  if ( file.name().isEmpty() ) {
    if ( op == Get ) {
      if ( !file.open( IO_WriteOnly, stdout ) ) {
	fprintf( stderr, "Can not open stdout for writing\n" );
	exit( EXIT_FAILURE );
      }
    } else {
      if ( !file.open( IO_ReadOnly, stdin ) ) {
	fprintf( stderr, "Can not open stdin for reading\n" );
	exit( EXIT_FAILURE );
      }
    }
  } else {
    if ( op == Get ) {
      if ( !file.open( IO_WriteOnly ) ) {
	fprintf( stderr, "Can not open %s for writing\n",
		 (const char *)QFile::encodeName( file.name() ) );
	exit( EXIT_FAILURE );
      }
    } else {
      if ( !file.open( IO_ReadOnly ) ) {
	fprintf( stderr, "Can not open %s for reading\n",
		 (const char *)QFile::encodeName( file.name() ) );
	exit( EXIT_FAILURE );
      }
    }
  }

  client obex( transport, &file, &dumpFile, verbose );
  obex.setOpenObexSupport( openobex );
  switch ( uuid ) {
  case FBS:
    obex.setUuid( QObexUuidFBS );
    break;
  case IrMCSync:
    obex.setUuid( QObexUuidIrMCSync );
    break;
  case SyncML:
    obex.setUuid( QObexUuidSyncML );
    break;
  case SyncMLSync:
    obex.setUuid( QObexUuidSyncMLSync );
    break;
  case SyncMLDM:
    obex.setUuid( QObexUuidSyncMLDM );
    break;
  case S45:
  default:
    obex.setUuid( QByteArray() );
    break;
  }

  obex.setInitiateAuthentication( initiateAuth );
  if ( !auth.isEmpty() )
    obex.setServerSecret( auth );
  if ( !realm.isEmpty() )
    obex.setClientRealm( realm );

  if ( connectedOperation ) {
    obex.connectClient();
    if ( !obex.isConnected() ) {
      fprintf( stderr, "Can not connect to OBEX server!\n" );
      exit( EXIT_FAILURE );
    }
  }

  if ( verbose )
    fprintf( stderr, "Server is %sauthenticated!\n",
	     obex.serverIsAuthenticated() ? "" : "not " );

  if ( cdInto ) {
    QStringList dirChain = QStringList::split( "/", name );
  
    // compute the common first part of the path.
    QStringList::Iterator it;
    for ( it = dirChain.begin(); it != dirChain.end(); ) {
      QStringList::Iterator cur = it;
      if ( folderBrowsingMode ) {
	++it;
	obex.setPath( *cur );
      } else {
	if ( ++it == dirChain.end() )
	  name = *it;
	else
	  obex.setPath( *cur );
      }
    }
    if ( folderBrowsingMode )
      name = QString::null;
  }

  if ( op == Get ) {
    if ( mimetype.isEmpty() )
      obex.get( name );
    else
      obex.get( name, mimetype );
  } else {
    obex.put( name );
  }

  file.close();

  if ( connectedOperation )
    obex.disconnectClient();

  return EXIT_SUCCESS;
}
