/* test rig for libNW functions */
/* caution: will delete the contents of whatever you tell it is the
   test device */
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <glib.h>
#include <unistd.h>

#include <libnw.h>

/* need this for frobbing the test file */
#include <common.h>

#define TEST_DEVICE "/var/tmp/NW-S23"
#define TEST_FILE   "test.mp3"

/*
 * The original MP3 code pads out short reads with zeros, which is
 * fine and sensible for streaming but makes no sense, really, for
 * file reads. This switches it on and off and adjusts the expected
 * test results accordingly.
 */
#ifdef PAD_SHORT_READ
#define TEST_FILE_SIZE 703040
#define TEST_FILE_MS    43521
#define TEST_FILE_FRAMES 1682
#else
#define TEST_FILE_SIZE 702622
#define TEST_FILE_MS    43495
#define TEST_FILE_FRAMES 1681
#endif

void progress( double percent, void *context ) {
  fprintf( stderr, "\r done %.2f%%", percent * 100 );
}

/*
 * optional args: /path/to/device, testfile, and API version to test
 * (v1 or v2)
 */
int main( int argc, char *argv[] ) {
  nw_device_t *dev = NULL;
  nw_track_t *track = NULL;
  FILE *fp = NULL;
  GList *players = NULL;
  int retval = 1, i;
  guint16 test, test2;
  gchar *testdev = NULL;
  gchar *testfile = NULL;
  gchar *buffer = NULL;
  nw_track_t *tptr = NULL;
  struct stat statbuf;
  int nbytes = 0;
  long offset = 0;
  unsigned test_file_size = 0;
  unsigned test_file_ms = 0;
  unsigned test_file_frames = 0;
  NWFILE *mfp = NULL;
  nw_api_t api = NW_GUESS;
  guint32 frate = 0, framerates[15], flen;
  mp3file mp3f;
  struct frame *frm;
  id3_utf8_t *filename = NULL;
  signed long taglen;

  if ( argc > 1 && strlen( argv[1] )) {
    testdev = g_strdup( argv[1] );
  } else {
    testdev = g_strdup( TEST_DEVICE );
  }

  if ( argc > 2 && strlen( argv[2] )) {
    testfile = g_strdup( argv[2] );
  } else {
    testfile = g_strdup( TEST_FILE );
  }

  if ( argc > 3 ) {
    if ( !strcmp( argv[3], "v1" )) {
      api = NW_MPLE;
    } else {
      api = NW_MPLE_V2;
    }
  }

  /* count the frames and such in the test file */
  if (( fp = fopen( testfile, "rb" )) == NULL ) {
    perror( testfile );
    goto out;
  }

  mp3f.fp = fp;
  mp3f.fsizeold = 0;
  mp3f.bsbuf = mp3f.bsspace[1];
  mp3f.bsnum = 0;

  for ( i = 0; i < 15; i++ ) {
    framerates[i] = 0;
  }

  flen = 0;

  while( mpg123_read_frame( &mp3f, &frm )) {
    if ( flen == 0 ) {
      flen = framesize( frm );
      frate = tabsel_123[lsf(frm)][2][bitrate_index(frm)];
    }

    test_file_frames++;
    framerates[15 - bitrate_index(frm)]++;

    test_file_size += 4; /* frame header */
    test_file_size += framesize( frm );
  }

  for ( i = 0; i < 15; i++ ) {
    test_file_ms += (double)( flen * framerates[i] ) /
      (double)( frate * 125 );
  }
  test_file_ms *= 1000;

  fprintf( stderr, "expect : size %u frames %u ms %u\n",
           test_file_size, test_file_frames, test_file_ms );

  fclose( fp );
  fp = NULL;

  /* test for available devices */
  fprintf( stderr, "scanning for devices..." );
  players = nw_get_devs( NULL );
  fprintf( stderr, "done\n" );
  if ( g_list_length( players ) == 0 ) {
    fprintf( stderr, "no NW players found\n" );
  } else {
    GList *p;
    for ( p = players; p != NULL; p = g_list_next( p )) {
      nw_device_t *pl = p->data;
      fprintf( stderr, " %s (%s)\n",
               (gchar *)nw_get_attr( pl, "device" ),
               nw_get_attr( pl, "mountpoint" ) == NULL ? "unmounted" :
               (gchar *)nw_get_attr( pl, "mountpoint" ));
      nw_dev_free( p->data );
    }
    g_list_free( players );
  }

  fprintf( stderr, "Using %s for tests\n", testdev );
  dev = nw_dev_init( testdev, api );
  if ( dev == NULL ) {
    perror( "nw_dev_init" );
    goto out;
  }

  /* this is a hack to stop the progress bar from trying to display
     when I compile from within emacs */
  if ( getenv( "EMACS" ) == NULL ) {
    nw_set_progress_func( dev, progress );
  } else {
    fprintf( stderr, "running under EMACS, will not use progress function\n" );
  }

  if ( nw_parse_directory( dev ) == NULL ) {
    if ( errno && errno != ENOENT ) {
      perror( "parsing directory" );
      goto out;
    } else {
      /* empty device */
    }
  }

  /* make sure the device is empty */
  fprintf( stderr, "Making sure device is clean..." );
  while( g_list_length( nw_get_folderlist( dev ))) {
    fprintf( stderr, "." );
    if ( nw_del_folder( dev, 1 ) == FALSE ) {
      perror( "nw_del_folder" );
      goto out;
    }
  }
  fprintf( stderr, "done\n" );

  if ( nw_parse_directory( dev ) == NULL ) {
    if ( errno && errno != ENOENT ) {
      perror( "parsing directory" );
      goto out;
    }
  } else {
    fprintf( stderr, "expecting directory to be empty at this point\n" );
    goto out;
  }

  fprintf( stderr, "FOLDER TESTS\n" );

  test = nw_add_folder( dev, "TEST", 1 );

  if ( test == 1 ) {
    fprintf( stderr, " added folder at position %d\n", test );
  } else {
    perror( " first folder not added at position 1" );
    goto out;
  }

  if ( nw_get_folder( dev, "TEST", 1 ) != test ) {
    perror( "nw_get_folder failed" );
  }

  test2 = nw_add_folder( dev, "TEST2", 0 );
  if ( test2 == 2 ) {
    fprintf( stderr, " added folder 2 at position %d\n", test2 );
  } else {
    perror( " second folder not added at position 2" );
    goto out;
  }

  test = nw_del_folder( dev, test );
  if ( !test ) {
    perror( " deleting first folder" );
    goto out;
  } else {
    fprintf( stderr, " deleted first folder\n" );
  }

  test = nw_add_folder( dev, "TEST", 0 );
  if ( test != 2 ) {
    perror( " first folder not re-added at position 2" );
    goto out;
  } else {
    fprintf( stderr, " first folder re-added at position %d\n", test );
  }

  test = nw_mv_folder( dev, test, 1 );
  if ( test != 1 ) {
    perror( " moving first folder" );
    goto out;
  } else {
    fprintf( stderr, " moved first folder to position %d\n", test );
  }

  if ( nw_get_folder( dev, "TEST", 1 ) != 1 ) {
    perror( " folder not where I expected" );
    goto out;
  }

  test = nw_ren_folder( dev, test, "TEST3" );
  if ( test != 1 ) {
    perror( " renaming folder" );
    goto out;
  } else {
    fprintf( stderr, " renamed TEST folder to TEST3\n" );
  }

  /* end of folder tests; now let's test some files */
  fprintf( stderr, "\nTRACK TESTS\n" );

  test2 = nw_add_track( dev, testfile, NULL, 0, 0 );
  if ( test2 == 0 ) {
    perror( "adding track failed" );
    goto out;
  } else {
    fprintf( stderr, " added %s to device as track %d\n", testfile, test2 );
  }

  /* pull the metadata */
  /* obviously these depend on a very specific test track! */
  if (( tptr = nw_get_track_ptr( dev, test2 )) == NULL ) {
    perror( "fetching track pointer" );
    goto out;
  }
  if ( nw_track_size( tptr ) != test_file_size ) {
    fprintf( stderr, "  *** size mismatch (%u != %u)\n",
             nw_track_size( tptr ), test_file_size );
  }
  if ( nw_track_time( tptr ) != test_file_ms ) {
    fprintf( stderr, "  *** time mismatch (%u != %u)\n",
             nw_track_time( tptr ), test_file_ms );
  }
  if ( nw_track_frames( tptr ) != test_file_frames ) {
    fprintf( stderr, "  *** frames mismatch (%u != %u)\n",
             nw_track_frames( tptr ), test_file_frames );
  }
  /* fixme check metadata */
  fprintf( stderr, " verified file transfer\n" );

  test2 = nw_del_track( dev, test2 );
  if ( test2 == FALSE ) {
    perror( " deleting track" );
    goto out;
  } else {
    fprintf( stderr, " deleted track\n" );
  }

  test2 = nw_add_track( dev, testfile, NULL, 0, 0 );
  if ( test2 == 0 ) {
    perror( " re-adding track" );
    goto out;
  } else {
    fprintf( stderr, " added %s to device as track %d\n", testfile, test2 );
  }

  /* give the second track some slightly different metadata to
     differentiate it */
  track = nw_get_metadata_NAME( testfile );
  filename = nw_get_tag( track, NW_FILENAME );

  if ( track == NULL ) {
    perror( "getting metadata" );
    goto out;
  } else {
    id3_utf8_t *title = nw_get_tag( track, ID3_FRAME_TITLE  );
    id3_utf8_t *artist = nw_get_tag( track, ID3_FRAME_ARTIST  );

    fprintf( stderr, "file: %s\ntitle: %s\n artist: %s\n", filename, title,
             artist );
    g_free( title );
    g_free( artist );
  }
  filename[1] = 'a';
  nw_set_tag( track, NW_FILENAME, filename );
  test2 = nw_add_track( dev, testfile, track, 0, 0 );
  g_free( filename );
  filename = NULL;

  if ( test2 == 0 ) {
    perror( " re-adding track" );
    goto out;
  } else {
    fprintf( stderr, " added %s to device as track %d\n", testfile, test2 );
  }

  /* swap the tracks around */
  fprintf( stderr, " swapping tracks\n" );
  test2 = nw_mv_track( dev, test2, 0, 1 );
  if ( test2 == 0 ) {
    perror( " moving track" );
    goto out;
  } else {
    fprintf( stderr, " moved track 2 to position %d in its own folder\n",
             test2 );
  }

  fprintf( stderr, " moving track to other folder\n" );
  test2 = nw_mv_track( dev, 1, 2, 0 );
  if ( test2 == 0 ) {
    perror( " moving track" );
    goto out;
  } else {
    fprintf( stderr, " moved track to other folder\n" );
  }

  /* now overwrite the metadata on track 1 so that it matches track 1 */
  test2 = nw_ren_track( dev, 1, track );
  if ( test2 == 0 ) {
    perror( " renaming track" );
    goto out;
  } else {
    fprintf( stderr, " renamed track\n" );
  }

  /* filesystem interface */
  fprintf( stderr, "\nFILESYSTEM TESTS\n" );

  if ( nw_stat( dev, 1, &statbuf ) == -1 ) {
    perror( " stat" );
    goto out;
  } else {
    fprintf( stderr, " file stat ok, file size is %ld\n", statbuf.st_size );
  }

  mfp = nw_fopen( dev, 0, 1, NULL, "rb" );
  if ( mfp != NULL ) {
    fprintf( stderr, " file open ok\n" );
  } else {
    perror( " file open" );
    goto out;
  }

  /* fixme: verify _CUR and _END as well */
  offset = nw_fseek( mfp, 10, SEEK_SET );
  if ( offset == -1 ) {
    perror( " file seek" );
    goto out;
  } else {
    fprintf( stderr, " file seek ok\n" );
  }

  if ( nw_ftell( mfp ) != 10 ) {
    if ( errno ) {
      perror( " file tell" );
    } else {
      fprintf( stderr, " file pointer not at %ld\n", offset );
    }
    goto out;
  } else {
    fprintf( stderr, " file tell ok\n" );
  }

  /* push the pointer back to 0 */
  offset = nw_fseek( mfp, 0, SEEK_SET );
  if ( offset == -1 ) {
    perror( " file seek" );
    goto out;
  } else {
    fprintf( stderr, " file seek ok\n" );
  }

  if (( tptr = nw_get_track_ptr( dev, 1 )) == NULL ) {
    perror( " failed to get tptr" );
    goto out;
  }

  if (( fp = fopen( "retrieved.mp3", "wb" )) != NULL ) {
  } else {
    perror( " copying" );
    goto out;
  }

  buffer = g_malloc0( ID3_TAG_QUERYSIZE );
  nbytes = nw_fread( buffer, 1, ID3_TAG_QUERYSIZE, mfp );
  if ( nbytes != ID3_TAG_QUERYSIZE ) {
    fprintf( stderr, " failed to get id3 header: %s\n", strerror( errno ));
    goto out;
  }

  taglen = id3_tag_query( (guint8 *)buffer, ID3_TAG_QUERYSIZE );

  if ( taglen ) {
    size_t total = 0;
    buffer = g_realloc( buffer, taglen );

    errno = 0;
    nbytes = nw_fread( &buffer[ID3_TAG_QUERYSIZE], taglen - ID3_TAG_QUERYSIZE,
                       1, mfp );

    if ( nbytes != taglen - ID3_TAG_QUERYSIZE ) {
      if ( errno ) {
        perror( " file read" );
      } else {
        fprintf( stderr, " file read got %d, expected %ld\n", nbytes, taglen );
      }
      goto out;
    } else {
      fprintf( stderr, " file read (id3 data) ok\n" );
      total = taglen;
    }

    fwrite( buffer, taglen, 1, fp );

    while( total < statbuf.st_size ) {
      size_t wanted = MIN( taglen, statbuf.st_size - total );
      errno = 0;
      nbytes = nw_fread( buffer, 1, wanted, mfp );
      total += nbytes;
      if ( nbytes != wanted ) {
        if ( errno ) {
          perror( " file read" );
        } else {
          fprintf( stderr, " file read got %d, expected %d\n", nbytes,
                   wanted );
          fprintf( stderr, " short read: got %d of %ld\n", total,
                   statbuf.st_size );
        }
        goto out;
      } else {
        if ( getenv( "EMACS" ) == NULL ) {
          progress( (double)total / (double)statbuf.st_size, NULL );
        }
      }

      fwrite( buffer, taglen, 1, fp );
    }
  } else {
    fprintf( stderr, " no tag found\n" );
    goto out;
  }

  fclose( fp );
  fp = NULL;
  fprintf( stderr, "\n" );

  nw_fclose( mfp );

  /* file write tests */
  mfp = nw_fopen( dev, 2, 0, "testwrite.mp3", "wb" );
  if ( mfp != NULL ) {
    fprintf( stderr, " file open (write) ok\n" );
  } else {
    perror( " file open (write)" );
    goto out;
  }

  if (( fp = fopen( testfile, "rb" )) == NULL ) {
    perror( " fopen" );
    goto out;
  }

  buffer = g_realloc( buffer, 1024 );

  while(( nbytes = fread( buffer, 1, 1024, fp )) > 0 ) {
    int written = nw_fwrite( buffer, 1, nbytes, mfp );
    if ( written != nbytes ) {
      fprintf( stderr, "write mismatch: got %d, expected %d\n",
               written, nbytes );
    }
  }

  nw_fclose( mfp );
  mfp = NULL;

  fprintf( stderr, "completed all tests successfully\n" );
  retval = 0;

 out:
  /* Clean up after ourselves! */
  unlink( "retrieved.mp3" );

  if ( track != NULL ) {
    nw_track_free( track );
  }

  if ( buffer != NULL ) {
    g_free( buffer );
  }

  if ( fp != NULL ) {
    fclose( fp );
  }

  if ( mfp != NULL ) {
    nw_fclose( mfp );
  }

  if ( testdev != NULL ) {
    g_free( testdev );
  }

  if ( testfile != NULL ) {
    g_free( testfile );
  }

  if ( dev != NULL ) {
    nw_dev_free( dev );
  }

  exit( retval );
}

