/*
 * $RCSfile: main.c,v $
 * $Date: 2006/01/28 12:22:22 $ - $Revision: 1.34 $
 *
 * This file is distributed as a part of MMSRIP ( MMS Ripper ).
 * Copyright (c) 2005-2006 Nicolas BENOIT
 *
 * This program 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, or (at your option) any
 * later version.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */


#define _GNU_SOURCE

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include "common.h"
#include "mms.h"
#include "error.h"

#ifdef HAVE_GETOPT_H
#include <getopt.h>
#endif

/*
 * options
 */
#if defined(SOLARIS) || defined(sun)
static char * options = "ahvtqko:d:g:";
#else
static char * options = "-ahvtqko:d:g:";
#endif

#ifdef HAVE_GETOPT_LONG
static struct option long_options[] = {
  {"about",   0, NULL, 'a'},
  {"version", 0, NULL, 'v'},
  {"help",    0, NULL, 'h'},
  {"test",    0, NULL, 't'},
  {"quiet",   0, NULL, 'q'},
  {"trick",   0, NULL, 'k'},
  {"output",  1, NULL, 'o'},
  {"delay",   1, NULL, 'd'},
  {"debug",   1, NULL, 'g'},
  {NULL,      0, NULL,  0 }
};
#endif

/*
 * usage
 */
void
usage ( void )
{
  fprintf ( stderr, "%s (%s) version %s\n\n", PROGRAM_SHORT_NAME, PROGRAM_FULL_NAME, PROGRAM_VERSION );
  fprintf ( stderr, "usage: %s <[-oFILE] stream url> ...\n\n", PROGRAM_SHORT_NAME );

#ifdef HAVE_GETOPT_LONG
  fprintf ( stderr, "General Options:\n\t"                                                      \
                    "-a, --about\t\tshow information about %s\n\t"                              \
                    "-h, --help\t\tshow this help\n\t"                                          \
                    "-v, --version\t\tshow version number\n\n"                                  \
                    "Program Behaviour:\n\t"                                                    \
                    "-oFILE, --output=FILE\toutput to specified file (can be repeated)\n\t"     \
                    "-gFILE, --debug=FILE\toutput debug info to specified file\n\t"             \
                    "-q, --quiet\t\tquiet mode (can be repeated)\n\t"                           \
                    "-dDELAY, --delay=DELAY\tsave the stream during DELAY seconds and exit\n\t" \
                    "-k, --trick\t\tattempt to trick recalcitrant servers\n\t"                  \
                    "-t, --test\t\ttest mode (check stream availability)\n\n", PROGRAM_SHORT_NAME );
#else
  fprintf ( stderr, "General Options:\n\t"                                       \
                    "-a\tshow information about %s\n\t"                          \
                    "-h\tshow this help\n\t"                                     \
                    "-v\tshow version number\n\n"                                \
                    "Program Behaviour:\n\t"                                     \
                    "-oFILE\toutput to specified file (can be repeated)\n\t"     \
                    "-gFILE\toutput debug info to specified file\n\t"            \
                    "-q\tquiet mode (can be repeated)\n\t"                       \
                    "-dDELAY\tsave the stream during DELAY seconds and exit\n\t" \
                    "-k\tattempt to trick recalcitrant servers\n\t"              \
                    "-t\ttest mode (check stream availability)\n\n", PROGRAM_SHORT_NAME );
#endif

  return;
}


/*
 * clear line
 */
static int is_xterm;

void
init_clear_line ( void )
{
  char *term = getenv ( "TERM" );

  if ( term == NULL )
    is_xterm = 0;
  else if ( strcmp(term,"xterm") && strcmp(term,"XTERM") )
    is_xterm = 0;
  else
    is_xterm = 1;
}


void
clear_line ( void )
{
  if ( is_xterm )
    fprintf ( stderr, "\r\33[2K\r" );
  else
    fprintf ( stderr, "\r                                                               \r" );
}


/*
 * main
 */
int
main ( int argc,
       char *argv[] )
{
  MMS *mms;
  FILE *f;
  FILE *stddebug = NULL;
  char *output_filename;
  STREAM_LIST *stream_list;
  STREAM_LIST *block;
  char c;
  int ret = MMS_RET_SUCCESS;
  int quiet = 0;
  int trick = MMS_TRICK_DISABLED;
  int retry = 0;
  int test = 0;
  int delay = 0;
  time_t end = 0;
  float speed = 0.0f;
  struct timeval speed_last_update;
  struct timeval now;
  float elapsed_time;
  ssize_t len_written;
  uint64_t total_len_written = 0;
  uint64_t old_total_len_written = 0;

  if ( ( stream_list = (STREAM_LIST *) malloc(sizeof(STREAM_LIST)) ) == NULL )
    {
      error ( "main", "early initialization failed" );
      return 1;
    }

  stream_list->next = NULL;
  stream_list->stream = NULL;
  stream_list->output = NULL;

  block = stream_list;

#ifdef HAVE_GETOPT_LONG
  while ( ( c = getopt_long(argc, argv, options, long_options, NULL) ) != -1 )
#elif defined(SOLARIS) || defined(sun)
  /* Implementation of getopt in Solaris is a bit strange, it returns -1 even if there are still args to parse... */
  while ( optind < argc )
#else
  while ( ( c = getopt(argc, argv, options) ) != -1 )
#endif
    {
#if defined(SOLARIS) || defined(sun)
      c = getopt ( argc, argv, options );
#endif
      switch (c)
        {
        case 'h':
          {
            fprintf ( stderr, "\n" );
            usage();
            return 0;
          }

        case 'v':
          {
            fprintf ( stderr, "%s version %s\n", PROGRAM_SHORT_NAME, PROGRAM_VERSION);
            return 0;
          }

        case 'a':
          {
            fprintf ( stderr, "\n" );
            fprintf ( stderr, "%s (%s) version %s\n\n", PROGRAM_SHORT_NAME, PROGRAM_FULL_NAME, PROGRAM_VERSION);
            fprintf ( stderr, "Written by %s.\n", PROGRAM_AUTHORS );
            fprintf ( stderr, "With a lot of help from %s.\n\n", PROGRAM_HELPERS );
            fprintf ( stderr, "This program is free software; you can redistribute it and/or\nmodify it under the terms "         \
                              "of the GNU General Public License\nas published by the Free Software Foundation; either version 2" \
                              ",\nor (at your option) any later version.\n\n" );
            return 0;
          }

        case 't':
          {
            test = 1;
            break;
          }

        case 'q':
          {
            quiet += 1;
            break;
          }

        case 'k':
          {
            trick = MMS_TRICK_ENABLED;
            break;
          }

        case 'o':
          {
            if ( optarg != NULL )
              {
                if ( block->stream != NULL )
                  {
                    if ( ( block->next = malloc ( sizeof(STREAM_LIST) ) ) == NULL )
                      {
                        error ( "main", "early initialization failed" );
                        ret = MMS_RET_ERROR;
                        goto clean;
                      }

                    block = block->next;
                    block->next = NULL;
                    block->stream = NULL;
                  }

                if ( block->output != NULL )
                  free ( block->output );

                block->output = (char *) strdup ( optarg );
              }

            break;
          }

        case 'g':
          {
            if ( optarg != NULL )
              {
                if ( stddebug != NULL )
                  fclose ( stddebug );

                if ( ( stddebug = fopen ( optarg, "w" ) ) == NULL )
                  {
                    if ( quiet < 2 )
#ifdef HAVE_VSNPRINTF
                      warning ( NULL, "unable to write debug info in \'%s\'", optarg );
#else
                      warning ( NULL, "unable to write debug info in specified file" );
#endif
                  }
                else
                  {
                    fprintf ( stddebug, "%s (%s) version %s\n\n", PROGRAM_SHORT_NAME, PROGRAM_FULL_NAME, PROGRAM_VERSION);
                    fprintf ( stddebug, "--> debug log begins now\n" );
                  }
              }

            break;
          }

        case 'd':
          {
            if ( optarg != NULL )
              {
                delay = atoi( optarg );

                if ( delay < 0 )
                  delay = 0;
              }

            break;
          }


#if defined(SOLARIS) || defined(sun)
        case -1:
          {
            if ( optind >= argc )
              break;
#else
        case 1:
          {
            if ( optarg != NULL )
              {
#endif
                if ( block->stream != NULL )
                  {
                    if ( ( block->next = malloc ( sizeof(STREAM_LIST) ) ) == NULL )
                      {
                        error ( "main", "early initialization failed" );
                        ret = MMS_RET_ERROR;
                        goto clean;
                      }

                    block = block->next;
                    block->next = NULL;
                    block->output = NULL;
                  }
#if defined(SOLARIS) || defined(sun)
                /* optind is not incremented when meeting something else than an option, so we do that... */
                block->stream = (char *) strdup ( argv[optind++] );
#else
                block->stream = (char *) strdup ( optarg );
              }
#endif
            break;
          }
        }
    }

  if ( stream_list->stream == NULL )
    {
      usage ( );
      ret = MMS_RET_ERROR;
      goto clean;
    }

  if ( !quiet )
    {
      fprintf ( stderr, "\n" );
      fprintf ( stderr, "%s (%s) version %s\n\n", PROGRAM_SHORT_NAME, PROGRAM_FULL_NAME, PROGRAM_VERSION );
      init_clear_line ( );
    }

  for ( block=stream_list; block!=NULL; block=(STREAM_LIST*)block->next )
    {
      if ( block->stream == NULL )
        {
          if ( quiet < 2 )
            {
              if ( block->output == NULL )
#ifdef HAVE_VSNPRINTF
                error ( "main", "invalid invocation of %s", PROGRAM_SHORT_NAME );
#else
                error ( "main", "invalid invocation of mmsrip" );
#endif
              else
#ifdef HAVE_VSNPRINTF
                error ( "main", "output to \'%s\' is not attached to any stream", block->output );
#else
                error ( "main", "one of the -o output is not attached to any stream" );
#endif
            }

          ret = MMS_RET_ERROR;
          goto clean;
        }

      if ( block->output == NULL )
        {
          char *tmp = strchr ( block->stream, '/' );
          char *interro_ptr = strchr ( block->stream, '?' );

          if ( interro_ptr == NULL )
            output_filename = strrchr ( block->stream, '/' );
          else
            {
              do  /* we look for the last '/' before the first '?' */
                {
                  output_filename = tmp;
                  tmp = strchr ( tmp+1, '/' );
                }
              while ( ( tmp < interro_ptr ) && ( tmp != NULL ) );
            }

          if ( output_filename == NULL )
            {
              if ( quiet < 2 )
#ifdef HAVE_VSNPRINTF
                error ( "main", "malformed url: \'%s\'", block->stream );
#else
                error ( "main", "malformed url" );
#endif

              ret = MMS_RET_ERROR;
              continue;
            }

          ++output_filename;

          if ( strlen ( output_filename ) == 0 )
            {
              if ( quiet < 2 )
#ifdef HAVE_VSNPRINTF
                error ( "main", "malformed url: \'%s\'", block->stream );
#else
                error ( "main", "malformed url" );
#endif

              ret = MMS_RET_ERROR;
              continue;
            }

          block->output = (char *) strdup ( output_filename );

          /* we clean filenames that look like 'stream.asf?digest=7Q2bjXo&provider=lala' */
          if ( ( interro_ptr = strchr(block->output,'?') ) != NULL )
            *interro_ptr = '\0';
        }
    }

  if ( ret != MMS_RET_SUCCESS )
    goto clean;

  if ( delay != 0 )
    end = time(NULL) + delay;

  for ( block=stream_list; block!=NULL; block=(STREAM_LIST*)(retry?block:block->next) )
    {
      output_filename = block->output;
      retry = 0;

      if ( !test )
        {
          if ( ( f = fopen ( output_filename, "w" ) ) == NULL )
            {
              if ( quiet < 2 )
#ifdef HAVE_VSNPRINTF
                error ( "main", "unable to write in \'%s\'", output_filename );
#else
                error ( "main", "unable to write in output file" );
#endif

              ret = MMS_RET_ERROR;
              continue;
            }
        }
      else
        f = NULL;

      if ( ( mms = mms_create ( block->stream, f, stddebug, trick, test?1:(quiet>>1) ) ) == NULL )
        {
          if ( quiet < 2 )
            error ( "main", "unable to create mms struct" );

          if ( !test )
            {
              fclose ( f );
              remove ( output_filename );
            }

          ret = MMS_RET_ERROR;
          continue;
        }

      if ( !quiet )
        {
          clear_line ( );
          fprintf ( stderr, "Connecting ...\r" );
        }

      if ( mms_connect ( mms ) != MMS_RET_SUCCESS )
        {
          if ( quiet < 2 )
            {
              clear_line ( );
              error ( "main", "unable to connect" );
            }

          mms_destroy ( mms );

          if ( !test )
            {
              fclose ( f );
              remove ( output_filename );
            }

          ret = MMS_RET_ERROR;
          continue;
        }

      if ( !quiet )
        {
          clear_line ( );
          fprintf ( stderr, "Handshaking ...\r" );
        }

      if ( mms_handshake ( mms ) != MMS_RET_SUCCESS )
        {
          if ( ( quiet < 2 ) && ( !test ) )
            {
              clear_line ( );
              error ( "main", "unable to handshake" );
            }

          mms_disconnect ( mms );
          mms_destroy ( mms );

          if ( !test )
            {
              fclose ( f );
              remove ( output_filename );
            }

          if ( !quiet )
            {
              clear_line ( );

              if ( !test )
                fprintf ( stderr, "Stream \'%s\' is not good.\n\n", block->stream );
              else
                fprintf ( stderr, "Stream \'%s\' is not good.\n", block->stream );
            }

          ret = MMS_RET_ERROR;
          continue;
        }

      if ( test )
        {
          if ( !quiet )
            {
              clear_line ( );
              fprintf ( stderr, "Stream \'%s\' is available.\n", block->stream );
            }

          mms_disconnect ( mms );
          mms_destroy ( mms );
          continue;
        }

      if ( !quiet )
        {
          clear_line ( );
          fprintf ( stderr, "Getting header ...\r" );
        }

      if ( ( len_written = mms_write_stream_header ( mms ) ) == MMS_RET_ERROR )
        {
          if ( quiet < 2 )
            {
              clear_line ( );
              error ( "main", "unable to write stream header" );
            }

          mms_disconnect ( mms );
          mms_destroy ( mms );
          fclose ( f );
          remove ( output_filename );
          ret = MMS_RET_ERROR;
          continue;
        }

      total_len_written = len_written;

      if ( !quiet )
        {
          clear_line ( );
          fprintf ( stderr, "Rip is about to start ...\r" );
        }

      if ( mms_begin_rip ( mms ) != MMS_RET_SUCCESS )
        {
          if ( quiet < 2 )
            {
              clear_line ( );
              error ( "main", "unable to begin the rip" );
            }

          mms_disconnect ( mms );
          mms_destroy ( mms );
          fclose ( f );
          remove ( output_filename );
          ret = -1;
          continue;
        }

      if ( mms->is_live == MMS_LIVE )
        {
          if ( !quiet )
            {
              clear_line ( );
              fprintf ( stderr, "%s: %d bytes written (--.- kbps)\r", output_filename, (ssize_t)total_len_written );
              gettimeofday ( &speed_last_update, NULL );
            }

          while ( 1 )
            {
              len_written = mms_write_stream_data ( mms );

              if ( len_written == 0 )
                break;
              else if ( len_written == MMS_RET_ERROR )
                {
                  if ( quiet < 2 )
                    {
                      clear_line ( );
                      error ( "main", "unable to write stream data" );
                    }

                  ret = MMS_RET_ERROR;
                  break;
                }
              else if ( len_written == MMS_RET_NO_AUTH )
                {
                  if ( trick == MMS_TRICK_DISABLED )
                    {
                      /* we retry with the trick enabled */
                      if ( quiet < 2 )
                        {
                          clear_line ( );
                          fprintf ( stderr, "\r*** retrying with the anti-recalcitrant servers trick enabled ***\n" );
                        }

                      trick = MMS_TRICK_ENABLED;
                      retry = 1;
                    }
                  else
                    {
                      /* it's definitely not working */
                      if ( quiet < 2 )
                        {
                          clear_line ( );
                          error ( "main", "unable to write stream data" );
                        }

                      ret = MMS_RET_NO_AUTH;
                    }

                  break;
                }

              total_len_written += len_written;

              if ( !quiet )
                {
                  gettimeofday ( &now, NULL );

                  if ( now.tv_sec > speed_last_update.tv_sec )
                    {
                      elapsed_time = (now.tv_sec - speed_last_update.tv_sec) + ((now.tv_usec - speed_last_update.tv_usec) / 1000000.0f);

                      if ( elapsed_time >= 1.0f )
                        {
                          speed = ( ( ((float) (total_len_written-old_total_len_written)) / elapsed_time) / 1024.0f );
                          old_total_len_written = total_len_written;
                          gettimeofday ( &speed_last_update, NULL );
                        }
                    }

                  clear_line ( );

                  if ( speed > 0.0f )
                    {
                      if ( (speed / 1024.0f) >= 1.0f )
                        fprintf ( stderr, "%s: %d bytes written (%.1f mbps)\r", output_filename, (ssize_t)total_len_written, speed/1024.0f );
                      else
                        fprintf ( stderr, "%s: %d bytes written (%.1f kbps)\r", output_filename, (ssize_t)total_len_written, speed );
                    }
                  else
                    fprintf ( stderr, "%s: %d bytes written (--.- kbps)\r", output_filename, (ssize_t)total_len_written );
                }

              fflush ( f );

              if ( delay != 0 )
                {
                  if ( end <= time(NULL) )
                    {
                      delay = -1;
                      break;
                    }
                }
            }

          /* for a live, we should rewrite the header */

          if ( ( ret == MMS_RET_SUCCESS ) && ( !quiet ) && ( !retry ) )
            {
              clear_line ( );
              fprintf ( stderr, "%s: %d bytes written\n", output_filename, (ssize_t)total_len_written );
            }
        }
      else
        {
          register const double coef = 100.0 / (double) mms->expected_file_size;

          if ( !quiet )
            {
              clear_line ( );
              fprintf ( stderr, "%s: %.2f%% (--.- kbps)\r", output_filename, (double) total_len_written * coef );
              gettimeofday ( &speed_last_update, NULL );
            }

          while ( 1 )
            {
              len_written = mms_write_stream_data ( mms );

              if ( len_written == 0 )
                break;
              else if ( len_written == MMS_RET_ERROR )
                {
                  if ( quiet < 2 )
                    {
                      clear_line ( );
                      error ( "main", "unable to write stream data" );
                    }

                  ret = MMS_RET_ERROR;
                  break;
                }
              else if ( len_written == MMS_RET_NO_AUTH )
                {
                  if ( trick == MMS_TRICK_DISABLED )
                    {
                      /* we retry with the trick enabled */
                      if ( quiet < 2 )
                        {
                          clear_line ( );
                          fprintf ( stderr, "\r*** retrying with the anti-recalcitrant servers trick enabled ***\n" );
                        }

                      trick = MMS_TRICK_ENABLED;
                      retry = 1;
                    }
                  else
                    {
                      /* it's definitely not working */
                      if ( quiet < 2 )
                        {
                          clear_line ( );
                          error ( "main", "unable to write stream data" );
                        }

                      ret = MMS_RET_NO_AUTH;
                    }

                  break;
                }

              total_len_written += len_written;

              if ( !quiet )
                {
                  gettimeofday ( &now, NULL );

                  if ( now.tv_sec > speed_last_update.tv_sec )
                    {
                      elapsed_time = (now.tv_sec - speed_last_update.tv_sec) + ((now.tv_usec - speed_last_update.tv_usec) / 1000000.0f);

                      if ( elapsed_time >= 1.0f )
                        {
                          speed = ( ( ((float) (total_len_written-old_total_len_written)) / elapsed_time) / 1024.0f );
                          old_total_len_written = total_len_written;
                          gettimeofday ( &speed_last_update, NULL );
                        }
                    }

                  clear_line ( );

                  if ( speed > 0.0f )
                    {
                      if ( (speed / 1024.0f) >= 1.0f )
                        fprintf ( stderr, "%s: %.2f%% (%.1f mbps)     \r", output_filename, (double) total_len_written * coef, speed/1024.0f );
                      else
                        fprintf ( stderr, "%s: %.2f%% (%.1f kbps)     \r", output_filename, (double) total_len_written * coef, speed );
                    }
                  else
                    fprintf ( stderr, "%s: %.2f%% (--.- kbps)     \r", output_filename, (double) total_len_written * coef );
                }

              fflush ( f );

              if ( delay != 0 )
                {
                  if ( end <= time(NULL) )
                    {
                      delay = -1;
                      break;
                    }
                }
            }

          if ( ( ret == MMS_RET_SUCCESS ) && ( !quiet ) && ( delay != -1 ) && ( !retry ) )
            {
              clear_line ( );
              fprintf ( stderr, "%s: 100.00%%\n", output_filename );
            }
        }

      mms_disconnect ( mms );
      mms_destroy ( mms );

      fclose ( f );

      if ( delay == -1 )
        break;
    }

 clean:
  if ( !quiet )
    fprintf ( stderr, "\n" );

  if ( stddebug != NULL )
    {
      fprintf ( stddebug, "\n\n--> debug log ends now\n" );
      fclose ( stddebug );
    }

  for ( block=stream_list; block!=NULL; )
    {
      STREAM_LIST *old;
      old = block;
      block = (STREAM_LIST *) block->next;

      if ( old->stream != NULL )
        free ( old->stream );

      if ( old->output != NULL )
        free ( old->output );

      free ( old );
    }

  return (ret<0) ? (ret*-1) : ret;
}
