/* ------------------------------------------------------------------

   libofa -- the Open Fingerprint Architecture library

   Public Domain (PD) 2006 Predixis Corporation
   No rights reserved.

-------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <map>
#include <expat.h>
#include <curl/curl.h>
#include <curl/types.h>
#include <curl/easy.h>
using namespace std;

#include "protocol.h"

const char *url = "http://ofa.musicdns.org/ofa/1/track"; 
const char *userAgent = "libofa_example";
const char *unknown = "unknown";

// Lookup by fingerprint
const char *request_format = 
    "lkt=1&"        // Lookup by print
    "cid=%s&"       // Client ID
    "cvr=%s&"       // Client Version
    "fpt=%s&"       // Fingerprint
    "rmd=%d&"       // m = 1: return metadata; m = 0: only return id 
    "brt=%d&"       // bitrate (kbps)
    "fmt=%s&"       // File extension (e.g. mp3, ogg, flac)
    "dur=%ld&"      // Length of track (milliseconds)
    "art=%s&"       // Artist name. If there is none, send "unknown"
    "ttl=%s&"       // Track title. If there is none, send "unknown"
    "alb=%s&"       // Album name. If there is none, send "unknown"
    "tnm=%d&"       // Track number in album. If there is none, send "0"
    "gnr=%s&"       // Genre. If there is none, send "unknown"
    "yrr=%s&"       // Year. If there is none, send "0"
    "enc=%s&"       // Encoding. e = true: ISO-8859-15; e = false: UTF-8 (default). Optional.
    "\r\n";

// Lookup by PUID (Most fields drop out)
const char *request_format2 =
    "lkt=2&"         // Lookup by PUID
    "cid=%s&"       // Client ID
    "cvr=%s&"       // Client Version
    "pid=%s&"       // PUID 
    "rmd=%d&"       // m = 1: return metadata; m = 0: only return id 
    "brt=%d&"       // bitrate (kbps)
    "fmt=%s&"       // File extension (e.g. mp3, ogg, flac)
    "dur=%ld&"      // Length of track (milliseconds)
    "art=%s&"       // Artist name. If there is none, send "unknown"
    "ttl=%s&"       // Track title. If there is none, send "unknown"
    "alb=%s&"       // Album name. If there is none, send "unknown"
    "tnm=%d&"       // Track number in album. If there is none, send "0"
    "gnr=%s&"       // Genre. If there is none, send "unknown"
    "yrr=%s&"       // Year. If there is none, send "0"
    "enc=%s&"       // Encoding. e = true: ISO-8859-15; e = false: UTF-8 (default). Optional.
    "\r\n";


// --------------------------------------------------------------------
// HTTP POST support using standard curl calls
// --------------------------------------------------------------------
size_t data_callback(void *ptr, size_t size, size_t num, void *arg)
{
    string *str = (string *)arg;
    (*str) += string((const char *)ptr, size * num);
    return size * num;
}

long http_post(const string &url, const string &userAgent, const string &postData, string &doc)
{
  CURL              *curl;
  long               ret = 0;
  struct curl_slist *headerlist=NULL;

  headerlist = curl_slist_append(headerlist, "Expect:"); 

  curl_global_init(CURL_GLOBAL_ALL);
  curl = curl_easy_init();
  curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
  curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&doc);
  curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, data_callback);
  curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
  curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, postData.length());
  curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
  curl_easy_setopt(curl, CURLOPT_POST, 1);
  curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str());
  curl_easy_perform(curl);
  curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &ret);
  curl_easy_cleanup(curl);

  curl_slist_free_all (headerlist);

  return ret;
}

// --------------------------------------------------------------------
// XML Parsing support 
// --------------------------------------------------------------------

struct ParseInfo
{
    string path;
    string pcdata;
    TrackInformation *info;
};

void begin_element(void *data, const XML_Char *el, const XML_Char **attr)
{
    map<string, string> attrs;

    for(; *attr;)
    {
        char *key = (char *)*(attr++);
        char *value = (char *)*(attr++);
        attrs[string(key)] = string(value);
    }

    ((ParseInfo *)data)->path += "/";
    ((ParseInfo *)data)->path += el;
    if (((ParseInfo *)data)->path == "/metadata/track/puid-list/puid")
         ((ParseInfo *)data)->info->setPUID(attrs["id"]);

    ((ParseInfo *)data)->pcdata = "";
}

void end_element(void *data, const XML_Char *el)
{
    string::size_type pos;

    if (((ParseInfo *)data)->path == "/metadata/track/title")
         ((ParseInfo *)data)->info->setTrack(((ParseInfo *)data)->pcdata);
    if (((ParseInfo *)data)->path == "/metadata/track/artist/name")
         ((ParseInfo *)data)->info->setArtist(((ParseInfo *)data)->pcdata);

    pos = ((ParseInfo *)data)->path.rfind("/");
    if (pos != string::npos)
       ((ParseInfo *)data)->path = ((ParseInfo *)data)->path.substr(0, pos);
}

void pc_data(void *data, const XML_Char *charData, int len)
{
    char *temp;

    temp = new char[len + 1];
    strncpy(temp, (char *)charData, len);
    temp[len] = 0;
    ((ParseInfo *)data)->pcdata += string(temp);
    delete temp;
}

bool parse_xml(const string &doc, TrackInformation *info, string &err) 
{
    ParseInfo pinfo;

    err = "";
    pinfo.info = info;
    XML_Parser parser = XML_ParserCreate(NULL);
    XML_SetUserData(parser, (void *)&pinfo);
    XML_SetElementHandler(parser, ::begin_element, ::end_element);
    XML_SetCharacterDataHandler(parser, ::pc_data);
    int ret = XML_Parse(parser, doc.c_str(), doc.length(), 1);

    if (ret)
    {
        XML_ParserFree(parser);
        return true;
    }

    err = string(XML_ErrorString(XML_GetErrorCode(parser)));
    char num[10];
    sprintf(num, "%d", XML_GetCurrentLineNumber(parser));
    err += string(" on line ") + string(num);
    XML_ParserFree(parser);

    return false;
}

// --------------------------------------------------------------------
// Retrieve metadata for fingerprint
// --------------------------------------------------------------------

#define DB printf("%s:%d\n", __FILE__, __LINE__);
// Returns true on success
bool retrieve_metadata(TrackInformation *info) 
{
    if (!info)
	return false;

    // All metadata fields must be provided before this call if the
    // information is available, as part of the Terms of Service.
    // This helps create a better database for all users of the system.
    //
    // If the fields are not available, you can use default values.
    // Here we check for fields which have no default values.
    if (info->getClientKey().length() == 0)
	return false;
    if (info->getClientVersion().length() == 0)
	return false;

    bool lookupByPrint = false;
    if (info->getPUID().length() == 0) {
	// Lookup by fingerprint
	if (info->getPrint().length() == 0)
	    return false;
	if (info->getFormat().length() == 0)
	    return false;
	if (info->getLengthInMS() == 0)
	    return false;

        lookupByPrint = true;
    }

    // Sloppily estimate the size of the resultant URL. Err on the side of making the string too big.
    int bufSize = strlen(lookupByPrint ? request_format : request_format2) +
            info->getClientKey().length() + 
            info->getClientVersion().length() +
            (lookupByPrint ? info->getPrint().length() : info->getPUID().length()) + 
            16 + // info->getMetadataFlag() ? 1 : 0, 
            16 + // info->getBitrate(),
            16 + //info->getFormat().c_str(), 
            16 + //info->getLengthInMS(), 
            ((info->getArtist().c_str() == 0) ? strlen(unknown) : info->getArtist().length()) +
            ((info->getTrack().c_str() == 0) ?  strlen(unknown) : info->getTrack().length()) +
            ((info->getAlbum().c_str() == 0) ?  strlen(unknown) : info->getAlbum().length()) +
            16 + // info->getTrackNum() + 
            ((info->getGenre().c_str() == 0) ?  strlen(unknown) : info->getGenre().length()) +
            ((info->getYear().c_str() == 0) ? 1 : info->getYear().length()) +
            info->getEncoding().length();
        
    char *buf = new char[bufSize];
    sprintf(buf, lookupByPrint ? request_format : request_format2, 
            info->getClientKey().c_str(), 
            info->getClientVersion().c_str(),
            lookupByPrint ? info->getPrint().c_str() : info->getPUID().c_str(), 
            info->getMetadataFlag() ? 1 : 0, 
            info->getBitrate(),
            info->getFormat().c_str(), 
            info->getLengthInMS(), 
            (info->getArtist().length() == 0) ? unknown : info->getArtist().c_str(),
            (info->getTrack().length() == 0) ? unknown : info->getTrack().c_str(),
            (info->getAlbum().length() == 0) ? unknown : info->getAlbum().c_str(),
            info->getTrackNum(), 
            (info->getGenre().length() == 0) ? unknown : info->getGenre().c_str(),
            (info->getYear().length() == 0) ? "0" : info->getYear().c_str(),
            info->getEncoding().c_str());

    //printf("request: %s\n\n", buf);
    string response;
    long ret = http_post(url, userAgent, buf, response);
    delete [] buf;

    if (ret != 200)
    {
        //printf("Error: %ld\n", ret);
        //printf("response: %s\n\n", response.c_str());
        return false;
    }
    //printf("response: %s\n\n", response.c_str());

    unsigned int q = response.find("<?xml");
    if (q != string::npos) {
        response = response.substr(q);
    }
    string err;

    info->setTrack("");
    info->setArtist("");
    info->setPUID("");
    if (parse_xml(response, info, err))
    {
        //printf(" Title: %s\n", info->getTrack().c_str());
        //printf("Artist: %s\n", info->getArtist().c_str());
        //printf("  PUID: %s\n", info->getPUID().c_str());
    }
    else
    {
        //printf("Error: %s\n", err.c_str());

        // Clears title if it wasn't returned
        info->setTrack("");

        // Clears artists if it wasn't returned
        info->setArtist("");
    }

    return true;
}
