#include <stdio.h>
#include <stdlib.h>
#include <glib.h>
#include <libnw.h>
#include <string.h>
#include <errno.h>
#include <iconv.h>

/*
 * convert a UTF16BE string to UTF8 and print it. Because iconv messes
 * with the i/o pointers, we need to keep two copies of each.
 */
gchar *convert_to_utf8( char *inbuffer, size_t maxlen ) {
  iconv_t cd = (iconv_t) -1;
  size_t outbytesleft, inbytesleft;
  size_t status;
  char *outbuffer = NULL, *outbuf;
  char *inbuf = inbuffer;

  /* Find out how many bytes to convert. Inbound data will be an array
     of 16bit values, either null-terminated or running right up
     against maxlen. */
  for ( inbytesleft = 0; inbytesleft < maxlen; inbytesleft++ ) {
    if ( inbuf[inbytesleft * 2 + 1] == 0 &&
         inbuf[inbytesleft * 2] == 0 ) {
      break;
    }
  }

  /* insane, but possible: UTF8 can use up to six bytes to represent a
     character. The +1 is for a trailing NULL */
  outbytesleft = inbytesleft * 6 + 1;
  outbuffer = g_malloc( outbytesleft );
  memset( outbuffer, 0, outbytesleft );
  outbuf = outbuffer;

  /* convert 16bit size to 8bit size */
  inbytesleft *= 2;

  cd = iconv_open( "UTF8", "UTF16BE" );
  if ( cd != (iconv_t)(-1)) {
    status = iconv( cd, &inbuf, &inbytesleft, &outbuf, &outbytesleft );
    if ( status == -1 ) {
      perror( "conv" );
      exit( 1 );
    }

    goto out;
  }

  /* if we get here, some part of the conversion failed */
  perror( "conv" );
  exit( 1 );

 out:
  if ( cd != (iconv_t) -1 ) {
    iconv_close( cd );
  }

  return outbuffer;
}

/*
 *
 */
int main( int argc, char *argv[] ) {
  nw_device_t *dev;
  char *devstring;
  char *sourcefile = NULL, *destfile = NULL;
  int sourcetrack = -1;
  guint32 msn = 0;
  gboolean gotmsn = FALSE;

  if ( argc < 3 ) {
    fprintf( stderr, "usage: %s from to\n", argv[0] );
    exit( 1 );
  } else if ( argc == 4 ) {
    msn = atol( argv[3] );
    gotmsn = TRUE;
  }

  /* this is full of stupid */
  if ( g_str_has_suffix( argv[1], ".dat" ) ||
       g_str_has_suffix( argv[1], ".DAT" ) ||
       g_str_has_suffix( argv[1], ".oma" ) ||
       g_str_has_suffix( argv[1], ".OMA" )) {
    /* device -> filesystem */
    char *tmp1, *tmp2 = g_path_get_dirname( argv[1] ); /* /mnt/point/esys/nw-mp3 */
    tmp1 = g_path_get_dirname( tmp2 ); /* ...esys */
    g_free( tmp2 );
    devstring = g_path_get_dirname( tmp1 ); /* /mnt/point */
    g_free( tmp1 );

    /* mpXXXX.dat */
    sourcefile = g_path_get_basename( argv[1] );
    if ( g_ascii_strncasecmp( "mp", sourcefile, 2 ) == 0 &&
         strlen( sourcefile ) == 10 ) {
      int c;

      sourcetrack = 0;

      for ( c = 2; c < 6; c++ ) {
        char v = sourcefile[c];
        if ( v > 'F' ) {
          v -= ( 'a' - 10 );
        } else if ( v > '9' ) {
          v -= ( 'A' - 10 );
        } else {
          v -= '0';
        }

        sourcetrack |= ( v << (( 5 - c ) * 4 ));
      }
    } else {
      /* v2 */
      int c;

      dprintf( stderr, "parsing sourcefile %s\n", sourcefile );

      sourcetrack = 0;

      for ( c = 1; c < 8; c++ ) {
        char v = sourcefile[c];

        if ( v > 'F' ) {
          v -= ( 'a' - 10 );
        } else if ( v > '9' ) {
          v -= ( 'A' - 10 );
        } else {
          v -= '0';
        }

        sourcetrack |= ( v << (( 7 - c ) * 4 ));
      }
    }
    g_free( sourcefile );
    sourcefile = NULL;

    destfile = g_strdup( argv[2] );
  } else {
    /* filesystem -> device */
    devstring = g_strdup( argv[1] );
    sourcefile = g_strdup( argv[2] );
  }

#ifdef DEBUG
  fprintf( stderr, "copying " );
  if ( sourcefile != NULL ) {
    fprintf( stderr, "%s to ", sourcefile );
  } else {
    fprintf( stderr, "%s, track %d to ", devstring, sourcetrack );
  }
  if ( destfile != NULL ) {
    fprintf( stderr, "%s\n", destfile );
  } else {
    fprintf( stderr, "%s\n", devstring );
  }
#endif

  dev = nw_dev_init( devstring, NW_GUESS );
  g_free( devstring );

  if ( dev == NULL ) {
    fprintf( stderr, "failed to init %s: %s\n", devstring, strerror( errno ));
    exit( 1 );
  }

  if ( nw_parse_directory( dev ) == NULL ) {
    fprintf( stderr, "failed to get folder list: %s\n", strerror( errno ));
    exit( 1 );
  }
  /*
  if ( gotmsn != FALSE ) {
    dev->msn = msn;
  }
  */

  if ( sourcetrack != -1 ) { /* device -> filesystem */
    nw_track_t *tptr = nw_get_track_ptr( dev, sourcetrack );
    NWFILE *mfp;
    FILE *fp;
    char buffer[BUFSIZ];
    int nbytes, total = 0;
    struct stat statbuf;

    if ( tptr == NULL ) {
      fprintf( stderr, "failed to get track: %s\n", strerror( errno ));
      exit( 1 );
    }

    if ( g_file_test( destfile, G_FILE_TEST_IS_DIR )) {
      /* need to get the filename from the device */
      id3_utf8_t *fname = nw_get_tag( tptr, NW_FILENAME );

      if ( fname == NULL || !strlen( fname )) {
        fname = g_strdup_printf( "Track %d.mp3", sourcetrack );
      }

      destfile = g_strdup_printf( "%s/%s", destfile, fname );
    }

    if (( mfp = nw_fopen( dev, 0, sourcetrack, NULL, "rb" )) == NULL ) {
      fprintf( stderr, "nw_open: %s\n", strerror( errno ));
      exit( 1 );
    }

    if (( fp = fopen( destfile, "wb" )) == NULL ) {
      fprintf( stderr, "fopen %s: %s\n", destfile, strerror( errno ));
      exit( 1 );
    }

    nw_stat( dev, sourcetrack, &statbuf );
    total = statbuf.st_size;

    while( total ) {
      nbytes = nw_fread( buffer, 1, BUFSIZ, mfp );

      /* manpage: if an error occurs, the return value is a short item
         count (or zero) */
      if ( nbytes == 0 ) {
        break;
      }
      fwrite( buffer, 1, nbytes, fp );
      total -= nbytes;
    }
  }

  nw_dev_free( dev );

  return 0;
}

