/* load files onto the Network Walkman */
#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"

void usage( void ) {
  if ( errno ) {
    perror( "mple-load" );
  } else {
    fprintf( stderr, "usage: %s -m /device/path -p {file.mp3|directory} [-a album] [-s serial]\n", "mple-load" );
  }
  exit( 1 );
}

void progress( double percent, void *context ) {
  char *filename = (char *)context;

  fprintf( stdout, "\r%s: % 6.2f%%", filename, percent * 100 );
}

/* pulled from mp3filemanager */
/*
 * find all files in a tree. Dumb, susceptible to loops, etc.
 * Just be careful, eh?
 */
GList *findfiles( const gchar *start, GList *files ) {
  GDir *dir;
  GError *error;

  if ( g_file_test( start, G_FILE_TEST_IS_DIR )) {
    const gchar *readname;

    dir = g_dir_open( start, 0, &error );
    if ( dir == NULL ) {
      return files;
    }
    while(( readname = g_dir_read_name( dir )) != NULL ) {
      gchar *newname;

      /* these appear to be helpfully removed for me, but just in
         case... */
      if (( strcmp( readname, "." ) == 0 ) ||
          ( strcmp( readname, ".." ) == 0 )) {
        continue;
      }

      /* this leaks memory pretty badly, but apparently by design. */
      newname = g_build_path( "/", start, readname, NULL );

      /* testing here saves some recursion */
      if ( g_file_test( newname, G_FILE_TEST_IS_DIR )) {
        files = findfiles( newname, files );
        g_free( newname );
      } else {
        files = g_list_append( files, newname ); /* gets freed at
                                                    cleanup */
      }
    }
    g_dir_close( dir );
  } else {
    files = g_list_append( files, g_strdup( start ));
  }

  return files;
}

struct load_context {
  nw_device_t *dev;
  guint16 folder;
};

void load( gpointer data, gpointer user_data ) {
  struct load_context *context = (struct load_context *)user_data;
  gchar *path = (gchar *)data;
  guint16 newtrack;
  nw_attr_t *attrpath;

  attrpath = g_new0( nw_attr_t, 1 );
  attrpath->type = NW_STRING;
  attrpath->data.gc = g_strdup( g_basename( path ));

  nw_set_attr( context->dev, "context", attrpath );
  fprintf( stdout, "%s:", g_basename( path ));
  errno = 0;
  newtrack = nw_add_track( context->dev, path, NULL, context->folder, 0 );
  if ( newtrack ) {
    fprintf( stdout, " written as track %d\n", newtrack );
  } else {
    if ( errno == EINVAL ) {
      /* EINVAL signals either that the format's invalid or you
         screwed up the dev or filename arguments. Given that we've
         already checked the latter... */
      fprintf( stdout, " not a valid MP3 file\n" );
    } else {
      fprintf( stdout, " %s\n", strerror( errno ));
      /* could check if it's a fatal error here, such as out of disk
         space, and pass it back in the context structure... */
    }
  }
}

void clean( gpointer data, gpointer user_data ) {
  g_free( data );
}

int main( int argc, char *argv[] ) {
  struct stat statbuf;
  struct load_context context;
  nw_device_t *dev;
  guint16 folder = 0;
  gchar *mountpoint = NULL, *path = NULL, *album = NULL;
  int val = 0;
  guint32 msn = 0;
  gboolean msn_provided = FALSE;
  GList *files = NULL;
  nw_attr_t *attrtmp = NULL, *attrmtpt = NULL;

  errno = 0; /* safety clown says ... */

  while (( val = getopt( argc, argv, "m:p:a:s:" )) != -1 ) {
    switch( val ) {
    case '?':
    case ':':
      usage();

    case 's':
      msn_provided = TRUE;
      if ( strncmp( optarg, "0x", 2 ) == 0 ) {
        guint8 c;
        if ( strlen( optarg ) < 10 ) {
          fprintf( stderr, "MSN must be 8 hex digits\n" );
          exit( 1 );
        }
        /* urgh */
        for ( c = 2; c < strlen( optarg ); c++ ) {
          char v = optarg[c];
          if ( v > 'F' ) {
            v -= ( 'a' - 10 );
          } else if ( v > '9' ) {
            v -= ( 'A' - 10 );
          } else {
            v -= '0';
          }

          msn |= ( v << (( c - 2 ) * 4 ));
        }
      } else {
        msn = atol( optarg );
      }
      break;

    case 'm':
      mountpoint = optarg;
      break;

    case 'p':
      path = optarg;
      break;

    case 'a':
      album = optarg;
      break;

    default:
      fprintf( stderr, "wtf?" );
      usage();
    }
  }

  /* old usage: parameters are /mount/point /path/to/mp3 [folder] */
  if (( optind == argc - 2 ) || ( optind == argc - 3 )) {
    mountpoint = argv[optind];
    path = argv[optind + 1];
    if ( optind == argc - 3 ) {
      album = argv[optind + 2];
    }
  }

  /* one param -> file to load */
  if (( optind == argc - 1 )) {
    path = argv[optind];
  }

  /* sanity check */
  if ( mountpoint == NULL ) {
    /* Go for auto-probe */
    GList *devs;
    guint32 d = 0;

    fprintf( stderr, "no device specified, auto-probing..." );
    devs = nw_get_devs( NULL );
    if ( g_list_length( devs ) == 0 ) {
      fprintf( stderr, "no device found\n" );
      exit( 1 );
    } else {
      fprintf( stderr, "done\n" );
    }
    if ( g_list_length( devs ) > 1 ) {
      fprintf( stderr, "More than one device found, please be more specific : \n" );

      while( g_list_nth( devs, d )) {
        nw_device_t *dv = g_list_nth_data( devs, d );

        if (( attrtmp = nw_get_attr( dev, "device" )) != NULL ) {
          fprintf( stderr, " %s", NW_STR_ATTR( attrtmp ));
          if (( attrmtpt = nw_get_attr( dv, "mountpoint" )) != NULL ) {
            fprintf( stderr, " (mounted at %s)", NW_STR_ATTR( attrmtpt ));
          }
          fprintf( stderr, "\n" );
        } else {
          fprintf( stderr, " [attr read failed]\n" );
        }

        nw_dev_free( dv );
        d++;
      }
      g_list_free( devs );
      exit( 1 );
    } else {
      dev = g_list_nth_data( devs, d );

      if (( attrtmp = nw_get_attr( dev, "device" )) != NULL ) {
        fprintf( stderr, "Using device %s at %p",
                 NW_STR_ATTR( attrtmp ), dev );
        if (( attrmtpt = nw_get_attr( dev, "mountpoint" )) != NULL ) {
          fprintf( stderr, " (mounted at %s)", NW_STR_ATTR( attrtmp ));
        }
        fprintf( stderr, "\n" );
      } else {
        if ( errno == ENOENT ) {
          fprintf( stderr, "No device in device structure!\n" );
        } else {
          fprintf( stderr, "device entry appears to be null\n" );
        }
        exit( 1 );
      }
    }
  } else {
    dev = nw_dev_init( mountpoint, NW_GUESS );
  }

  if ( dev == NULL ) {
    fprintf( stderr, "No device specified or found\n" );
    exit( 1 );
  }

  if ( msn_provided ) {
    /*dev->msn = msn;
      dev->aux |= MPLE_MSN; xxx */
  } else {
    /* xxx not applicable to v2, really */
    if ( nw_get_attr( dev, "media serial number" ) == NULL ) {
      if ( nw_get_attr( dev, "mountpoint" ) == NULL ) {
        fprintf( stderr, "unable to identify device %s\n", mountpoint );
        exit( 1 );
      }
    }
  }

  nw_set_progress_func( dev, progress );

  /* if the album parameter is set, get or create a folder with that
     name. Otherwise, use the first folder, or if there are no
     folders, create a folder called New Folder and use that. */
  if ( album == NULL ) {
    if ( nw_parse_directory( dev ) == NULL ) {
      folder = nw_add_folder( dev, "New Folder", 0 );
    } else {
      folder = 1;
    }
  } else {
    if (( folder = nw_get_folder( dev, album, 0 )) == 0 ) {
      folder = nw_add_folder( dev, album, 0 );
      if ( folder ) {
        fprintf( stdout, "created folder %s as #%d\n", album, folder );
      } else {
        perror( "mple_add_folder" );
        exit( 1 );
      }
    }

    if ( !folder ) {
      fprintf( stderr, "mple_add/get_folder: %s\n", strerror( errno ));
      exit( 1 );
    }
  }

  /* find out if we're dealing with a file or a directory */
  if ( stat( path, &statbuf ) != 0 ) {
    fprintf( stderr, "%s: %s\n", path, strerror( errno ));
    exit( 1 );
  }

  if ( !S_ISDIR( statbuf.st_mode )) {
    files = g_list_append( files, g_strdup( path ));
    /* path gets g_free'd later, hence strdup */
  } else {
    files = findfiles( path, files );
  }

  if ( g_list_length( files ) == 0 ) {
    fprintf( stderr, "no files found\n" );
    exit( 1 );
  }

  context.dev = dev;
  context.folder = folder;

  g_list_foreach( files, load, (gpointer)&context );

  g_list_foreach( files, clean, NULL );
  g_list_free( files );

  nw_dev_free( dev );

  return 0;
}

