#include <iostream> 
#include <fstream> 
#include <uuid/uuid.h>
#include <id3/tag.h>
#include <string.h>

using namespace std;

/* This is a horrid little program I wrote to add (and fetch) UFID
   tags in MP3 files.  It currently creates tags as UUIDs only, and
   requires libuuid and libid3 */

/* This needs refactoring, badly */

/* Copyright 2005,2011 Daniel Risacher */
/* Available for license under the GPL */

/* 2011-05-18 bugfix to add UFID tags when file already has a tag with another namespace url */


int add_ufid (char *filename, const char *url, uuid_t uuid, bool replace) ;
ID3_Frame* ID3_AddUFID(ID3_Tag *tag, const char *url, uuid_t uuid, bool replace); 
size_t ID3_RemoveUFID(ID3_Tag *tag, const char *url);
uuid_t *retrieve_ufid (char* filename, const char *url) ;
size_t ID3_DeleteAllUFID(const char *filename) ;
int print_hex (const uchar* buf, int num);
void list_ufid (char* filename);


int main (int argc, char** argv, char** envp) 
{
  uuid_t myUUID;
  char uuid_string[37];
  uuid_generate(myUUID);
  uuid_unparse(myUUID, uuid_string);
  
  for (int i=1; i< argc; i++) {
    if (!strncmp(argv[i], "-h", 3) ||
	!strncmp(argv[i], "-?", 3) ||
	!strncmp(argv[i], "--usage", 8)) {
      cout << argv[0] << " -s owner-url filename  (to set a UFID, but not replace one)" << endl;
      cout << argv[0] << " -r owner-url filename  (to replace or set a UFID)" << endl;
      cout << argv[0] << " -g owner-url filename  (to get the UFID)" << endl;
      cout << argv[0] << " -d filename  (to delete all UFIDs)" << endl;
      cout << argv[0] << " -l filename  (to list all UFIDs)" << endl;
    } else if (!strncmp(argv[i], "-l", 3)) {
      
      list_ufid(argv[i+1]);
      
    } else if (!strncmp(argv[i], "-d", 3)) {
      
      ID3_DeleteAllUFID(argv[i+1]);
      
    } else if (!strncmp(argv[i], "-s", 3)) {
      
      add_ufid (argv[i+2], argv[i+1], myUUID, 0) ;
      
    } else if (!strncmp(argv[i], "-r", 3)) {
      
      cout << uuid_string << endl;
      add_ufid (argv[i+2], argv[i+1], myUUID, 1) ;
      
    } 
    if ((!strncmp(argv[i], "-s", 3)) || (!strncmp(argv[i], "-g", 3))) {
      uuid_t* uuid_ptr;
      uuid_ptr = retrieve_ufid (argv[i+2], argv[i+1]) ;
      if (uuid_ptr) {
	uuid_unparse(*uuid_ptr, uuid_string);
	cout << uuid_string << endl;
      } else { 
	cout << "no matching UFID found" << endl;
      }
    }
  }
  return 0;
}

int print_hex (const uchar* buf, int num)
{
  for (int i=0; i<num; i++) {
    printf("%.2x ", buf[i]);
  }
}

void list_ufid (char* filename) 
{
  ID3_Tag tag;
  uuid_t* uuid = NULL;
  ID3_Frame* frame = NULL;
  tag.Link(filename);
  ID3_Tag::Iterator* iter = tag.CreateIterator();
  while (NULL != (frame = iter->GetNext())) {
    if ((frame->GetID() == ID3FID_UNIQUEFILEID)) {
      cout << frame->GetField(ID3FN_OWNER)->GetRawText() << "::";
      if (sizeof(uuid_t) == frame->GetField(ID3FN_DATA)->Size()) {
	char uuid_string[37];
	uuid = (uuid_t*) frame->GetField(ID3FN_DATA)->GetRawBinary();
	uuid_unparse(*uuid, uuid_string);
	cout << uuid_string << endl;
      } else {
	print_hex(frame->GetField(ID3FN_DATA)->GetRawBinary(),
		  frame->GetField(ID3FN_DATA)->Size());
	cout << endl;
      }
    }
  }
}

uuid_t *retrieve_ufid (char* filename, const char *url) 
{
  ID3_Tag tag;
  uuid_t* uuid = NULL;
  ID3_Frame* frame = NULL;
  tag.Link(filename);
  ID3_Tag::Iterator* iter = tag.CreateIterator();
  while (NULL != (frame = iter->GetNext())) {
    if ((frame->GetID() == ID3FID_UNIQUEFILEID)) {
      if (!strcmp(url, frame->GetField(ID3FN_OWNER)->GetRawText())) {
	if (sizeof(uuid_t) == frame->GetField(ID3FN_DATA)->Size()) {
	  uuid = (uuid_t*) frame->GetField(ID3FN_DATA)->GetRawBinary();
	  return uuid;
	} else {
	  cerr << "warning: existing UFID isn't a UUID" << endl;
	  exit (1);
	}
      }
    }
  }
  return NULL;
}

int add_ufid (char *filename, const char *url, uuid_t uuid, bool replace) 
{
  ID3_Tag tag;
  tag.Link(filename);
  if (ID3_AddUFID(&tag, url, uuid, replace)) {
    tag.Update();
  }
  return 0;

}

ID3_Frame* ID3_AddUFID(ID3_Tag *tag, const char *url, uuid_t uuid, bool replace)
{
  ID3_Frame* frame = NULL;
  if (NULL != tag && NULL != url && NULL != uuid && strlen(url) > 0)
    {
      if (replace) {
	  ID3_RemoveUFID(tag, url);
      } 
      if (replace || tag->Find(ID3FID_UNIQUEFILEID, ID3FN_URL, url) == NULL) {
	frame = new ID3_Frame(ID3FID_UNIQUEFILEID);
	if (frame) {
	  frame->GetField(ID3FN_OWNER)->Set(url);
	  // I think this should be ID3FN_ID, according to the standard
	  // but that doesn't work
	  frame->GetField(ID3FN_DATA)->Set(uuid, 16);
	  tag->AttachFrame(frame);
	  return frame;
	}
      }
    }
  return NULL;
}

size_t ID3_RemoveUFID(ID3_Tag *tag, const char *url)
{
  size_t num_removed = 0;
  ID3_Frame *frame = NULL;
  
  if (NULL == tag)
    {
      return num_removed;
    }
  
  ID3_Tag::Iterator* iter = tag->CreateIterator();
  while (NULL != (frame = iter->GetNext())) {
    if ((frame->GetID() == ID3FID_UNIQUEFILEID)) {
      if (ID3_Field* field = frame->GetField(ID3FN_OWNER)) {
	if (!strcmp(url, field->GetRawText())) {
	  cerr << "Warning: replacing old UFID tag!" << endl;
	  frame = tag->RemoveFrame(frame);
	  delete frame;
	  num_removed++;
	} else { 
	  cerr << "Warning: UFID tag found, but with another owner!" << endl;
	}
      }
    }
  }
  return num_removed;
}

size_t ID3_DeleteAllUFID(const char *filename)
{
  ID3_Tag tag;
  tag.Link(filename);
  size_t num_removed = 0;
  ID3_Frame *frame = NULL;
  
  ID3_Tag::Iterator* iter = tag.CreateIterator();
  while (NULL != (frame = iter->GetNext())) {
    if ((frame->GetID() == ID3FID_UNIQUEFILEID)) {
      frame = tag.RemoveFrame(frame);
      delete frame;
      num_removed++;
    }
  } 
  tag.Update();
  return num_removed;
}

