// Qt headers
#include <QLocale>
#include <QCoreApplication>
#include <QMetaType>

#include "rssparse.h"
#include "programinfo.h"
#include "mythcorecontext.h"
#include "metadatacommon.h"
#include "mythlogging.h"
#include "mythlocale.h"
#include "mythmiscutil.h"

static int x0 = qRegisterMetaType< RefCountHandler<MetadataLookup> >();

// null constructor
MetadataLookup::MetadataLookup(void) :
    ReferenceCounter("MetadataLookup"),

    m_type(kMetadataVideo),
    m_subtype(kUnknownVideo),
    m_data(),
    m_step(kLookupSearch),
    m_automatic(false),
    m_handleimages(false),
    m_allowoverwrites(false),
    m_allowgeneric(false),
    m_dvdorder(false),
    m_host(),
    m_filename(),
    m_title(),
    m_network(),
    m_status(),
    m_categories(),
    m_userrating(0),
    m_ratingcount(0),
    m_language(),
    m_subtitle(),
    m_tagline(),
    m_description(),
    m_season(0),
    m_episode(0),
    m_chanid(0),
    m_channum(),
    m_chansign(),
    m_channame(),
    m_chanplaybackfilters(),
    m_recgroup(),
    m_playgroup(),
    m_seriesid(),
    m_programid(),
    m_storagegroup(),
    m_startts(),
    m_endts(),
    m_recstartts(),
    m_recendts(),
    m_programflags(0),
    m_audioproperties(0),
    m_videoproperties(0),
    m_subtitletype(0),
    m_certification(),
    m_countries(),
    m_popularity(0),
    m_budget(0),
    m_revenue(0),
    m_album(),
    m_tracknum(0),
    m_system(),
    m_year(0),
    m_releasedate(),
    m_lastupdated(),
    m_runtime(0),
    m_runtimesecs(0),
    m_inetref(),
    m_collectionref(),
    m_tmsref(),
    m_imdb(),
    m_iscollection(false),
    m_people(),
    m_studios(),
    m_homepage(),
    m_trailerURL(),
    m_artwork(),
    m_downloads()
{
}

// full constructor
MetadataLookup::MetadataLookup(
    MetadataType type,
    LookupType subtype,
    QVariant data,
    LookupStep step,
    bool automatic,
    bool handleimages,
    bool allowoverwrites,
    bool allowgeneric,
    bool preferdvdorder,
    const QString &host,
    const QString &filename,
    const QString &title,
    const QString &network,
    const QString &status,
    const QStringList &categories,
    const float userrating,
    uint ratingcount,
    const QString &language,
    const QString &subtitle,
    const QString &tagline,
    const QString &description,
    uint season,
    uint episode,
    uint chanid,
    const QString &channum,
    const QString &chansign,
    const QString &channame,
    const QString &chanplaybackfilters,
    const QString &recgroup,
    const QString &playgroup,
    const QString &seriesid,
    const QString &programid,
    const QString &storagegroup,
    const QDateTime &startts,
    const QDateTime &endts,
    const QDateTime &recstartts,
    const QDateTime &recendts,
    uint programflags,
    uint audioproperties,
    uint videoproperties,
    uint subtitletype,
    const QString &certification,
    const QStringList &countries,
    const float popularity,
    const uint budget,
    const uint revenue,
    const QString &album,
    uint tracknum,
    const QString &system,
    const uint year,
    const QDate &releasedate,
    const QDateTime &lastupdated,
    const uint runtime,
    const uint runtimesecs,
    const QString &inetref,
    const QString &collectionref,
    const QString &tmsref,
    const QString &imdb,
    const PeopleMap people,
    const QStringList &studios,
    const QString &homepage,
    const QString &trailerURL,
    const ArtworkMap artwork,
    DownloadMap downloads) :
    ReferenceCounter("MetadataLookup"),

    m_type(type),
    m_subtype(subtype),
    m_data(data),
    m_step(step),
    m_automatic(automatic),
    m_handleimages(handleimages),
    m_allowoverwrites(allowoverwrites),
    m_allowgeneric(allowgeneric),
    m_dvdorder(preferdvdorder),
    m_host(host),
    m_filename(filename),
    m_title(title),
    m_network(network),
    m_status(status),
    m_categories(categories),
    m_userrating(userrating),
    m_ratingcount(ratingcount),
    m_language(language),
    m_subtitle(subtitle),
    m_tagline(tagline),
    m_description(description),
    m_season(season),
    m_episode(episode),
    m_chanid(chanid),
    m_channum(channum),
    m_chansign(chansign),
    m_channame(channame),
    m_chanplaybackfilters(chanplaybackfilters),
    m_recgroup(recgroup),
    m_playgroup(playgroup),
    m_seriesid(seriesid),
    m_programid(programid),
    m_storagegroup(storagegroup),
    m_startts(startts),
    m_endts(endts),
    m_recstartts(recstartts),
    m_recendts(recendts),
    m_programflags(programflags),
    m_audioproperties(audioproperties),
    m_videoproperties(videoproperties),
    m_subtitletype(subtitletype),
    m_certification(certification),
    m_countries(countries),
    m_popularity(popularity),
    m_budget(budget),
    m_revenue(revenue),
    m_album(album),
    m_tracknum(tracknum),
    m_system(system),
    m_year(year),
    m_releasedate(releasedate),
    m_lastupdated(lastupdated),
    m_runtime(runtime),
    m_runtimesecs(runtimesecs),
    m_inetref(inetref),
    m_collectionref(collectionref),
    m_tmsref(tmsref),
    m_imdb(imdb),
    m_iscollection(false),
    m_people(people),
    m_studios(studios),
    m_homepage(homepage),
    m_trailerURL(trailerURL),
    m_artwork(artwork),
    m_downloads(downloads)
{
    QString manRecSuffix = QString(" (%1)").arg(QObject::tr("Manual Record"));
    m_base_title = title;
    m_base_title.replace(manRecSuffix,"");
}

// ProgramInfo-style constructor
MetadataLookup::MetadataLookup(
    MetadataType type,
    LookupType subtype,
    QVariant data,
    LookupStep step,
    bool automatic,
    bool handleimages,
    bool allowoverwrites,
    bool allowgeneric,
    bool preferdvdorder,
    const QString &host,
    const QString &filename,
    const QString &title,
    const QStringList &categories,
    const float userrating,
    const QString &subtitle,
    const QString &description,
    uint chanid,
    const QString &channum,
    const QString &chansign,
    const QString &channame,
    const QString &chanplaybackfilters,
    const QString &recgroup,
    const QString &playgroup,
    const QString &seriesid,
    const QString &programid,
    const QString &storagegroup,
    const QDateTime &startts,
    const QDateTime &endts,
    const QDateTime &recstartts,
    const QDateTime &recendts,
    uint programflags,
    uint audioproperties,
    uint videoproperties,
    uint subtitletype,
    const uint year,
    const QDate &releasedate,
    const QDateTime &lastupdated,
    const uint runtime,
    const uint runtimesecs) :
    ReferenceCounter("MetadataLookup"),

    m_type(type),
    m_subtype(subtype),
    m_data(data),
    m_step(step),
    m_automatic(automatic),
    m_handleimages(handleimages),
    m_allowoverwrites(allowoverwrites),
    m_allowgeneric(allowgeneric),
    m_dvdorder(preferdvdorder),
    m_host(host),
    m_filename(filename),
    m_title(title),
    m_categories(categories),
    m_userrating(userrating),
    m_ratingcount(0),
    m_subtitle(subtitle),
    m_description(description),
    m_season(0),
    m_episode(0),
    m_chanid(chanid),
    m_channum(channum),
    m_chansign(chansign),
    m_channame(channame),
    m_chanplaybackfilters(chanplaybackfilters),
    m_recgroup(recgroup),
    m_playgroup(playgroup),
    m_seriesid(seriesid),
    m_programid(programid),
    m_storagegroup(storagegroup),
    m_startts(startts),
    m_endts(endts),
    m_recstartts(recstartts),
    m_recendts(recendts),
    m_programflags(programflags),
    m_audioproperties(audioproperties),
    m_videoproperties(videoproperties),
    m_subtitletype(subtitletype),
    m_year(year),
    m_releasedate(releasedate),
    m_lastupdated(lastupdated),
    m_runtime(runtime),
    m_runtimesecs(runtimesecs),
    m_iscollection(false)
{
    m_tracknum = 0;
    m_popularity = 0;
    m_budget = 0;
    m_revenue = 0;
    QString manRecSuffix = QString(" (%1)").arg(QObject::tr("Manual Record"));
    m_base_title = title;
    m_base_title.replace(manRecSuffix,"");
}

// XBMC NFO-style constructor
MetadataLookup::MetadataLookup(
    MetadataType type,
    LookupType subtype,
    QVariant data,
    LookupStep step,
    bool automatic,
    bool handleimages,
    bool allowoverwrites,
    bool allowgeneric,
    bool preferdvdorder,
    const QString &host,
    const QString &filename,
    const QString &title,
    const QStringList &categories,
    const float userrating,
    const QString &subtitle,
    const QString &tagline,
    const QString &description,
    uint season,
    uint episode,
    const QString &certification,
    const uint year,
    const QDate releasedate,
    const uint runtime,
    const uint runtimesecs,
    const QString &inetref,
    const PeopleMap people,
    const QString &trailerURL,
    const ArtworkMap artwork,
    DownloadMap downloads) :
    ReferenceCounter("MetadataLookup"),

    m_type(type),
    m_subtype(subtype),
    m_data(data),
    m_step(step),
    m_automatic(automatic),
    m_handleimages(handleimages),
    m_allowoverwrites(allowoverwrites),
    m_allowgeneric(allowgeneric),
    m_dvdorder(preferdvdorder),
    m_host(host),
    m_filename(filename),
    m_title(title),
    m_categories(categories),
    m_userrating(userrating),
    m_ratingcount(0),
    m_subtitle(subtitle),
    m_tagline(tagline),
    m_description(description),
    m_season(season),
    m_episode(episode),
    m_chanid(0),
    m_programflags(0),
    m_audioproperties(0),
    m_videoproperties(0),
    m_subtitletype(0),
    m_certification(certification),
    m_popularity(0),
    m_budget(0),
    m_revenue(0),
    m_tracknum(0),
    m_year(year),
    m_releasedate(releasedate),
    m_runtime(runtime),
    m_runtimesecs(runtimesecs),
    m_inetref(inetref),
    m_iscollection(false),
    m_people(people),
    m_trailerURL(trailerURL),
    m_artwork(artwork),
    m_downloads(downloads)
{
    QString manRecSuffix = QString(" (%1)").arg(QObject::tr("Manual Record"));
    m_base_title = title;
    m_base_title.replace(manRecSuffix,"");
}

QList<PersonInfo> MetadataLookup::GetPeople(PeopleType type) const
{
    QList<PersonInfo> ret;
    // QMultiMap::values() returns items in reverse order which we need to
    // correct by iterating back over the list
    // See http://qt-project.org/doc/qt-4.8/qmultimap.html#details
    // Specifically "The items that share the same key are available from "
    //              "most recently to least recently inserted."
    QListIterator<PersonInfo> it(m_people.values(type));
    it.toBack();
    while (it.hasPrevious())
        ret.append(it.previous());

    return ret;
}

ArtworkList MetadataLookup::GetArtwork(VideoArtworkType type) const
{
    ArtworkList ret;

    // QMultiMap::values() returns items in reverse order which we need to
    // correct by iterating back over the list
    // See http://qt-project.org/doc/qt-4.8/qmultimap.html#details
    // Specifically "The items that share the same key are available from "
    //              "most recently to least recently inserted."
    QListIterator<ArtworkInfo> it(m_artwork.values(type));
    it.toBack();
    while (it.hasPrevious())
        ret.append(it.previous());

    return ret;
}

void MetadataLookup::toMap(InfoMap &metadataMap)
{
    metadataMap["filename"] = m_filename;
    metadataMap["title"] = m_title;
    metadataMap["network"] = m_network;
    metadataMap["status"] = m_status;
    metadataMap["category"] = m_categories.join(", ");
    metadataMap["userrating"] = QString::number(m_userrating);
    metadataMap["ratingcount"] = QString::number(m_ratingcount);
    metadataMap["language"] = m_language;
    metadataMap["subtitle"] = m_subtitle;
    metadataMap["tagline"] = m_tagline;
    metadataMap["description"] = m_description;
    metadataMap["season"] = QString::number(m_season);
    metadataMap["episode"] = QString::number(m_episode);
    metadataMap["chanid"] = QString::number(m_chanid);
    metadataMap["channum"] = m_channum;
    metadataMap["callsign"] = m_chansign;
    metadataMap["channame"] = m_channame;
    metadataMap["playbackfilters"] = m_chanplaybackfilters;
    metadataMap["recgroup"] = m_recgroup;
    metadataMap["playgroup"] = m_playgroup;
    metadataMap["seriesid"] = m_seriesid;
    metadataMap["programid"] = m_programid;
    metadataMap["storagegroup"] = m_storagegroup;
    metadataMap["startts"] = MythDate::toString(m_startts, MythDate::kDateFull);
    metadataMap["endts"] = MythDate::toString(m_endts, MythDate::kDateFull);
    metadataMap["recstartts"] = MythDate::toString(m_recstartts, MythDate::kDateFull);
    metadataMap["recendts"] = MythDate::toString(m_recendts, MythDate::kDateFull);
    metadataMap["certification"] = m_certification;
    metadataMap["countries"] = m_countries.join(", ");
    metadataMap["popularity"] = QString::number(m_popularity);
    metadataMap["budget"] = QString::number(m_budget);
    metadataMap["revenue"] = QString::number(m_revenue);
    metadataMap["album"] = m_album;
    metadataMap["tracknum"] = QString::number(m_tracknum);
    metadataMap["system"] = m_system;
    metadataMap["year"] = QString::number(m_year);

    metadataMap["releasedate"] = MythDate::toString(
        m_releasedate, MythDate::kDateFull);
    metadataMap["lastupdated"] = MythDate::toString(m_lastupdated, MythDate::kDateFull);

    metadataMap["runtime"] = QCoreApplication::translate("(Common)",
                                                         "%n minute(s)",
                                                         "",
                                                         m_runtime);


    metadataMap["runtimesecs"] = QCoreApplication::translate("(Common)",
                                                             "%n second(s)",
                                                             "",
                                                             m_runtimesecs);
    metadataMap["inetref"] = m_inetref;
    metadataMap["collectionref"] = m_collectionref;
    metadataMap["tmsref"] = m_tmsref;
    metadataMap["imdb"] = m_imdb;
    metadataMap["studios"] = m_studios.join(", ");
    metadataMap["homepage"] = m_homepage;
    metadataMap["trailer"] = m_trailerURL;
}

MetadataLookup* LookupFromProgramInfo(ProgramInfo *pginfo)
{
    uint runtimesecs = pginfo->GetRecordingStartTime()
                           .secsTo(pginfo->GetRecordingEndTime());
    uint runtime = (runtimesecs/60);

    MetadataLookup *ret = new MetadataLookup(kMetadataRecording, kUnknownVideo,
        qVariantFromValue(pginfo), kLookupData, false, false, false, false, false,
        pginfo->GetHostname(),pginfo->GetBasename(),pginfo->GetTitle(),
        QStringList() << pginfo->GetCategory(), pginfo->GetStars() * 10,
        pginfo->GetSubtitle(), pginfo->GetDescription(), pginfo->GetChanID(),
        pginfo->GetChanNum(), pginfo->GetChannelSchedulingID(),
        pginfo->GetChannelName(), pginfo->GetChannelPlaybackFilters(),
        pginfo->GetRecordingGroup(), pginfo->GetPlaybackGroup(),
        pginfo->GetSeriesID(), pginfo->GetProgramID(), pginfo->GetStorageGroup(),
        pginfo->GetScheduledStartTime(), pginfo->GetScheduledEndTime(),
        pginfo->GetRecordingStartTime(), pginfo->GetRecordingEndTime(),
        pginfo->GetProgramFlags(), pginfo->GetAudioProperties(),
        pginfo->GetVideoProperties(), pginfo->GetSubtitleType(),
        pginfo->GetYearOfInitialRelease(), pginfo->GetOriginalAirDate(),
        pginfo->GetLastModifiedTime(), runtime, runtimesecs);

    ret->SetSeason(pginfo->GetSeason());
    ret->SetEpisode(pginfo->GetEpisode());
    ret->SetInetref(pginfo->GetInetRef());

    return ret;
}

QDomDocument CreateMetadataXML(MetadataLookupList list)
{
    QDomDocument doc("MythMetadataXML");

    QDomElement root = doc.createElement("metadata");
    doc.appendChild(root);

    for (MetadataLookupList::const_iterator i = list.begin();
            i != list.end(); ++i)
    {
        CreateMetadataXMLItem(*i, root, doc);
    }

    return doc;
}

QDomDocument CreateMetadataXML(MetadataLookup *lookup)
{
    QDomDocument doc("MythMetadataXML");

    QDomElement root = doc.createElement("metadata");
    doc.appendChild(root);

    CreateMetadataXMLItem(lookup, root, doc);

    return doc;
}

QDomDocument CreateMetadataXML(ProgramInfo *pginfo)
{
    QDomDocument doc("MythMetadataXML");

    MetadataLookup *lookup = LookupFromProgramInfo(pginfo);
    if (lookup)
        doc = CreateMetadataXML(lookup);

    lookup->DecrRef();
    lookup = nullptr;

    return doc;
}

void CreateMetadataXMLItem(MetadataLookup *lookup,
                           QDomElement placetoadd,
                           QDomDocument docroot)
{
    if (!lookup)
        return;

    QDomElement item = docroot.createElement("item");
    placetoadd.appendChild(item);
    QString RFC822("ddd, d MMMM yyyy hh:mm:ss");

    // Language
    if (!lookup->GetLanguage().isEmpty())
    {
        QDomElement language = docroot.createElement("language");
        item.appendChild(language);
        language.appendChild(docroot.createTextNode(lookup->GetLanguage()));
    }
    // Title
    if (!lookup->GetTitle().isEmpty())
    {
        QDomElement title = docroot.createElement("title");
        item.appendChild(title);
        title.appendChild(docroot.createTextNode(lookup->GetTitle()));
    }
    // Subtitle
    if (!lookup->GetSubtitle().isEmpty())
    {
        QDomElement subtitle = docroot.createElement("subtitle");
        item.appendChild(subtitle);
        subtitle.appendChild(docroot.createTextNode(lookup->GetSubtitle()));
    }
    // Network
    if (!lookup->GetNetwork().isEmpty())
    {
        QDomElement network = docroot.createElement("network");
        item.appendChild(network);
        network.appendChild(docroot.createTextNode(lookup->GetNetwork()));
    }
    // Status
    if (!lookup->GetStatus().isEmpty())
    {
        QDomElement status = docroot.createElement("status");
        item.appendChild(status);
        status.appendChild(docroot.createTextNode(lookup->GetStatus()));
    }
    // Season
    if (lookup->GetSeason() > 0 || lookup->GetEpisode() > 0)
    {
        QDomElement season = docroot.createElement("season");
        item.appendChild(season);
        season.appendChild(docroot.createTextNode(
                           QString::number(lookup->GetSeason())));
    }
    // Episode
    if (lookup->GetSeason() > 0 || lookup->GetEpisode() > 0)
    {
        QDomElement episode = docroot.createElement("episode");
        item.appendChild(episode);
        episode.appendChild(docroot.createTextNode(
                           QString::number(lookup->GetEpisode())));
    }
    // Tagline
    if (!lookup->GetTagline().isEmpty())
    {
        QDomElement tagline = docroot.createElement("tagline");
        item.appendChild(tagline);
        tagline.appendChild(docroot.createTextNode(lookup->GetTagline()));
    }
    // Plot
    if (!lookup->GetDescription().isEmpty())
    {
        QDomElement desc = docroot.createElement("description");
        item.appendChild(desc);
        desc.appendChild(docroot.createTextNode(lookup->GetDescription()));
    }
    // Album Name
    if (!lookup->GetAlbumTitle().isEmpty())
    {
        QDomElement albumname = docroot.createElement("albumname");
        item.appendChild(albumname);
        albumname.appendChild(docroot.createTextNode(lookup->GetAlbumTitle()));
    }
    // Inetref
    if (!lookup->GetInetref().isEmpty())
    {
        QDomElement inetref = docroot.createElement("inetref");
        item.appendChild(inetref);
        inetref.appendChild(docroot.createTextNode(lookup->GetInetref()));
    }
    // Collectionref
    if (!lookup->GetCollectionref().isEmpty())
    {
        QDomElement collectionref = docroot.createElement("collectionref");
        item.appendChild(collectionref);
        collectionref.appendChild(docroot.createTextNode(lookup->GetCollectionref()));
    }
    // TMSref/SeriesID
    if (!lookup->GetTMSref().isEmpty())
    {
        QDomElement tmsref = docroot.createElement("tmsref");
        item.appendChild(tmsref);
        tmsref.appendChild(docroot.createTextNode(lookup->GetTMSref()));
    }
    // IMDB
    if (!lookup->GetIMDB().isEmpty())
    {
        QDomElement imdb = docroot.createElement("imdb");
        item.appendChild(imdb);
        imdb.appendChild(docroot.createTextNode(lookup->GetIMDB()));
    }
    // Home Page
    if (!lookup->GetHomepage().isEmpty())
    {
        QDomElement homepage = docroot.createElement("homepage");
        item.appendChild(homepage);
        homepage.appendChild(docroot.createTextNode(lookup->GetHomepage()));
    }
    // Trailer
    if (!lookup->GetTrailerURL().isEmpty())
    {
        QDomElement trailer = docroot.createElement("trailer");
        item.appendChild(trailer);
        trailer.appendChild(docroot.createTextNode(lookup->GetTrailerURL()));
    }
    // Chan ID
    if (lookup->GetChanId() > 0)
    {
        QDomElement chanid = docroot.createElement("chanid");
        item.appendChild(chanid);
        chanid.appendChild(docroot.createTextNode(
                           QString::number(lookup->GetChanId())));
    }
    // Channel Number
    if (!lookup->GetChanNum().isEmpty())
    {
        QDomElement channum = docroot.createElement("channum");
        item.appendChild(channum);
        channum.appendChild(docroot.createTextNode(lookup->GetChanNum()));
    }
    // Callsign
    if (!lookup->GetChanSign().isEmpty())
    {
        QDomElement callsign = docroot.createElement("chansign");
        item.appendChild(callsign);
        callsign.appendChild(docroot.createTextNode(lookup->GetChanSign()));
    }
    // Channel Name
    if (!lookup->GetChanName().isEmpty())
    {
        QDomElement channame = docroot.createElement("channame");
        item.appendChild(channame);
        channame.appendChild(docroot.createTextNode(lookup->GetChanName()));
    }
    // Playback Filters
    if (!lookup->GetChanPlaybackFilters().isEmpty())
    {
        QDomElement filters = docroot.createElement("filters");
        item.appendChild(filters);
        filters.appendChild(docroot.createTextNode(
                            lookup->GetChanPlaybackFilters()));
    }
    // Recording Group
    if (!lookup->GetRecGroup().isEmpty())
    {
        QDomElement recgroup = docroot.createElement("recgroup");
        item.appendChild(recgroup);
        recgroup.appendChild(docroot.createTextNode(lookup->GetRecGroup()));
    }
    // Playback Group
    if (!lookup->GetPlayGroup().isEmpty())
    {
        QDomElement playgroup = docroot.createElement("playgroup");
        item.appendChild(playgroup);
        playgroup.appendChild(docroot.createTextNode(lookup->GetPlayGroup()));
    }
    // SeriesID
    if (!lookup->GetSeriesId().isEmpty())
    {
        QDomElement seriesid = docroot.createElement("seriesid");
        item.appendChild(seriesid);
        seriesid.appendChild(docroot.createTextNode(lookup->GetSeriesId()));
    }
    // ProgramID
    if (!lookup->GetProgramId().isEmpty())
    {
        QDomElement programid = docroot.createElement("programid");
        item.appendChild(programid);
        programid.appendChild(docroot.createTextNode(lookup->GetProgramId()));
    }
    // Storage Group
    if (!lookup->GetStorageGroup().isEmpty())
    {
        QDomElement sgroup = docroot.createElement("storagegroup");
        item.appendChild(sgroup);
        sgroup.appendChild(docroot.createTextNode(lookup->GetStorageGroup()));
    }
    // Start TS
    if (!lookup->GetStartTS().isNull())
    {
        QDomElement startts = docroot.createElement("startts");
        item.appendChild(startts);
        startts.appendChild(docroot.createTextNode(
                                lookup->GetStartTS().toString(RFC822)));
    }
    // End TS
    if (!lookup->GetEndTS().isNull())
    {
        QDomElement endts = docroot.createElement("endts");
        item.appendChild(endts);
        endts.appendChild(docroot.createTextNode(
                          lookup->GetEndTS().toString(RFC822)));
    }
    // Rec Start TS
    if (!lookup->GetRecStartTS().isNull())
    {
        QDomElement recstartts = docroot.createElement("recstartts");
        item.appendChild(recstartts);
        recstartts.appendChild(docroot.createTextNode(
                          lookup->GetRecStartTS().toString(RFC822)));
    }
    // Rec End TS
    if (!lookup->GetRecEndTS().isNull())
    {
        QDomElement recendts = docroot.createElement("recendts");
        item.appendChild(recendts);
        recendts.appendChild(docroot.createTextNode(
                          lookup->GetRecEndTS().toString(RFC822)));
    }
    // Program Flags
    if (lookup->GetProgramFlags() > 0)
    {
        QDomElement progflags = docroot.createElement("programflags");
        item.appendChild(progflags);
        progflags.appendChild(docroot.createTextNode(
                          QString::number(lookup->GetProgramFlags())));
    }
    // Audio Properties
    if (lookup->GetAudioProperties() > 0)
    {
        QDomElement audioprops = docroot.createElement("audioproperties");
        item.appendChild(audioprops);
        audioprops.appendChild(docroot.createTextNode(
                          QString::number(lookup->GetAudioProperties())));
    }
    // Video Properties
    if (lookup->GetVideoProperties() > 0)
    {
        QDomElement videoprops = docroot.createElement("videoproperties");
        item.appendChild(videoprops);
        videoprops.appendChild(docroot.createTextNode(
                          QString::number(lookup->GetVideoProperties())));
    }
    // Subtitle Type
    if (lookup->GetSubtitleType() > 0)
    {
        QDomElement subprops = docroot.createElement("subtitletype");
        item.appendChild(subprops);
        subprops.appendChild(docroot.createTextNode(
                          QString::number(lookup->GetSubtitleType())));
    }
    // Release Date
    if (!lookup->GetReleaseDate().isNull())
    {
        QDomElement releasedate = docroot.createElement("releasedate");
        item.appendChild(releasedate);
        releasedate.appendChild(docroot.createTextNode(
                      lookup->GetReleaseDate().toString("yyyy-MM-dd")));
    }
    // Last Updated
    if (!lookup->GetLastUpdated().isNull())
    {
        QDomElement lastupdated = docroot.createElement("lastupdated");
        item.appendChild(lastupdated);
        lastupdated.appendChild(docroot.createTextNode(
                      lookup->GetLastUpdated().toString(RFC822)));
    }
    // User Rating
    if (lookup->GetUserRating() > 0)
    {
        QDomElement userrating = docroot.createElement("userrating");
        item.appendChild(userrating);
        userrating.appendChild(docroot.createTextNode(QString::number(
                       lookup->GetUserRating())));
    }
    // Rating Count
    if (lookup->GetRatingCount() > 0)
    {
        QDomElement ratingcount = docroot.createElement("ratingcount");
        item.appendChild(ratingcount);
        ratingcount.appendChild(docroot.createTextNode(QString::number(
                       lookup->GetRatingCount())));
    }
    // Track Number
    if (lookup->GetTrackNumber() > 0)
    {
        QDomElement tracknum = docroot.createElement("tracknum");
        item.appendChild(tracknum);
        tracknum.appendChild(docroot.createTextNode(QString::number(
                       lookup->GetTrackNumber())));
    }
    // Popularity
    if (lookup->GetPopularity() > 0)
    {
        QDomElement popularity = docroot.createElement("popularity");
        item.appendChild(popularity);
        popularity.appendChild(docroot.createTextNode(QString::number(
                       lookup->GetPopularity())));
    }
    // Budget
    if (lookup->GetBudget() > 0)
    {
        QDomElement budget = docroot.createElement("budget");
        item.appendChild(budget);
        budget.appendChild(docroot.createTextNode(QString::number(
                       lookup->GetBudget())));
    }
    // Revenue
    if (lookup->GetRevenue() > 0)
    {
        QDomElement revenue = docroot.createElement("revenue");
        item.appendChild(revenue);
        revenue.appendChild(docroot.createTextNode(QString::number(
                       lookup->GetRevenue())));
    }
    // Runtime
    if (lookup->GetRuntime() > 0)
    {
        QDomElement runtime = docroot.createElement("runtime");
        item.appendChild(runtime);
        runtime.appendChild(docroot.createTextNode(QString::number(
                       lookup->GetRuntime())));
    }
    // Runtimesecs
    if (lookup->GetRuntimeSeconds() > 0)
    {
        QDomElement runtimesecs = docroot.createElement("runtimesecs");
        item.appendChild(runtimesecs);
        runtimesecs.appendChild(docroot.createTextNode(QString::number(
                       lookup->GetRuntimeSeconds())));
    }

    if (lookup->GetCertification().size())
        AddCertifications(lookup, item, docroot);
    if (lookup->GetCategories().size())
        AddCategories(lookup, item, docroot);
    if (lookup->GetStudios().size())
        AddStudios(lookup, item, docroot);
    if (lookup->GetCountries().size())
        AddCountries(lookup, item, docroot);
}

void AddCertifications(MetadataLookup *lookup,
                       QDomElement placetoadd,
                       QDomDocument docroot)
{
    QString certstr = lookup->GetCertification();
    QDomElement certifications = docroot.createElement("certifications");
    placetoadd.appendChild(certifications);

    QDomElement cert = docroot.createElement("certification");
    certifications.appendChild(cert);
    cert.setAttribute("locale", gCoreContext->GetLocale()->GetCountryCode());
    cert.setAttribute("name", certstr);
}

void AddCategories(MetadataLookup *lookup,
                   QDomElement placetoadd,
                   QDomDocument docroot)
{
    QStringList cats = lookup->GetCategories();
    QDomElement categories = docroot.createElement("categories");
    placetoadd.appendChild(categories);

    for (QStringList::const_iterator i = cats.begin();
            i != cats.end(); ++i)
    {
        QDomElement cat = docroot.createElement("category");
        categories.appendChild(cat);
        cat.setAttribute("type", "genre");
        cat.setAttribute("name", *i);
    }
}

void AddStudios(MetadataLookup *lookup,
                   QDomElement placetoadd,
                   QDomDocument docroot)
{
    QStringList studs = lookup->GetStudios();
    QDomElement studios = docroot.createElement("studios");
    placetoadd.appendChild(studios);

    for (QStringList::const_iterator i = studs.begin();
            i != studs.end(); ++i)
    {
        QDomElement stud = docroot.createElement("studio");
        studios.appendChild(stud);
        stud.setAttribute("name", *i);
    }
}

void AddCountries(MetadataLookup *lookup,
                  QDomElement placetoadd,
                  QDomDocument docroot)
{
    QStringList counts = lookup->GetCountries();
    QDomElement countries = docroot.createElement("countries");
    placetoadd.appendChild(countries);

    for (QStringList::const_iterator i = counts.begin();
            i != counts.end(); ++i)
    {
        QDomElement count = docroot.createElement("country");
        countries.appendChild(count);
        count.setAttribute("name", *i);
    }
}

MetadataLookup* ParseMetadataItem(const QDomElement& item,
                                  MetadataLookup *lookup,
                                  bool passseas)
{
    if (!lookup)
        return new MetadataLookup();

    uint season = 0, episode = 0, chanid = 0, programflags = 0,
         audioproperties = 0, videoproperties = 0, subtitletype = 0,
         tracknum = 0, budget = 0, revenue = 0,
         year = 0, runtime = 0, runtimesecs = 0, ratingcount = 0;
    QString title, network, status, subtitle, tagline, description, certification,
        channum, chansign, channame, chanplaybackfilters, recgroup,
        playgroup, seriesid, programid, storagegroup, album, system,
        inetref, collectionref, tmsref, imdb, homepage, trailerURL, language;
    QStringList categories, countries, studios;
    float userrating = 0;
    float popularity = 0;
    QDate releasedate;
    QDateTime lastupdated, startts, endts, recstartts, recendts;
    PeopleMap people;
    ArtworkMap artwork;

    // Get the easy parses
    language = item.firstChildElement("language").text();
    title = Parse::UnescapeHTML(item.firstChildElement("title").text());
    network = Parse::UnescapeHTML(item.firstChildElement("network").text());
    status = Parse::UnescapeHTML(item.firstChildElement("status").text());
    subtitle = Parse::UnescapeHTML(item.firstChildElement("subtitle").text());
    tagline = Parse::UnescapeHTML(item.firstChildElement("tagline").text());
    description = Parse::UnescapeHTML(item.firstChildElement("description").text());
    album = Parse::UnescapeHTML(item.firstChildElement("albumname").text());
    inetref = item.firstChildElement("inetref").text();
    collectionref = item.firstChildElement("collectionref").text();
    tmsref = item.firstChildElement("tmsref").text();
    imdb = item.firstChildElement("imdb").text();
    homepage = item.firstChildElement("homepage").text();
    trailerURL = item.firstChildElement("trailer").text();

    // ProgramInfo specific parsing
    chanid = item.firstChildElement("chanid").text().toUInt();
    channum = item.firstChildElement("channum").text();
    chansign = item.firstChildElement("chansign").text();
    channame = item.firstChildElement("channame").text();
    chanplaybackfilters = item.firstChildElement("chanplaybackfilters").text();
    recgroup = item.firstChildElement("recgroup").text();
    playgroup = item.firstChildElement("playgroup").text();
    seriesid = item.firstChildElement("seriesid").text();
    programid = item.firstChildElement("programid").text();
    storagegroup = item.firstChildElement("storagegroup").text();
    startts = RFC822TimeToQDateTime(item.
                      firstChildElement("startts").text());
    endts = RFC822TimeToQDateTime(item.
                      firstChildElement("endts").text());
    recstartts = RFC822TimeToQDateTime(item.
                      firstChildElement("recstartts").text());
    recendts = RFC822TimeToQDateTime(item.
                      firstChildElement("recendts").text());
    programflags = item.firstChildElement("programflags").text().toUInt();
    audioproperties = item.firstChildElement("audioproperties").text().toUInt();
    videoproperties = item.firstChildElement("videoproperties").text().toUInt();
    subtitletype = item.firstChildElement("subtitletype").text().toUInt();

    QString tmpDate = item.firstChildElement("releasedate").text();
    if (!tmpDate.isEmpty())
        releasedate = QDate::fromString(tmpDate, "yyyy-MM-dd");
    lastupdated = RFC822TimeToQDateTime(item.
                      firstChildElement("lastupdated").text());

    userrating = item.firstChildElement("userrating").text().toFloat();
    ratingcount = item.firstChildElement("ratingcount").text().toUInt();
    tracknum = item.firstChildElement("tracknum").text().toUInt();
    popularity = item.firstChildElement("popularity").text().toFloat();
    budget = item.firstChildElement("budget").text().toUInt();
    revenue = item.firstChildElement("revenue").text().toUInt();
    year = item.firstChildElement("year").text().toUInt();
    if (!year && !releasedate.isNull())
        year = releasedate.toString("yyyy").toUInt();
    runtime = item.firstChildElement("runtime").text().toUInt();
    runtimesecs = item.firstChildElement("runtimesecs").text().toUInt();

    QDomElement systems = item.firstChildElement("systems");
    if (!systems.isNull())
    {
        QDomElement firstSystem = systems.firstChildElement("system");
        if (!firstSystem.isNull())
            system = firstSystem.text();
    }

    // TODO: Once TMDB supports certification per-locale, come back and match
    // locale of myth to certification locale.
    QDomElement certifications = item.firstChildElement("certifications");
    QList< QPair<QString,QString> > ratinglist;
    if (!certifications.isNull())
    {
        QDomElement cert = certifications.firstChildElement("certification");
        if (!cert.isNull())
        {
            while (!cert.isNull())
            {
                if (cert.hasAttribute("locale") && cert.hasAttribute("name"))
                {
                    QPair<QString,QString> newcert(cert.attribute("locale"),
                                             cert.attribute("name"));
                    ratinglist.append(newcert);
                }
                cert = cert.nextSiblingElement("certification");
            }
        }
    }
    // HACK: To go away when someone supports ratings by locale.
    if (!ratinglist.isEmpty())
        certification = ratinglist.takeFirst().second;

    // Categories
    QDomElement categoriesxml = item.firstChildElement("categories");
    if (!categoriesxml.isNull())
    {
        QDomElement cat = categoriesxml.firstChildElement("category");
        if (!cat.isNull())
        {
            while (!cat.isNull())
            {
                if (cat.hasAttribute("name"))
                    categories.append(cat.attribute("name"));
                cat = cat.nextSiblingElement("category");
            }
        }
    }

    // Countries
    QDomElement countriesxml = item.firstChildElement("countries");
    if (!countriesxml.isNull())
    {
        QDomElement cntry = countriesxml.firstChildElement("country");
        if (!cntry.isNull())
        {
            while (!cntry.isNull())
            {
                if (cntry.hasAttribute("name"))
                    countries.append(cntry.attribute("name"));
                cntry = cntry.nextSiblingElement("country");
            }
        }
    }

    // Studios
    QDomElement studiosxml = item.firstChildElement("studios");
    if (!studiosxml.isNull())
    {
        QDomElement studio = studiosxml.firstChildElement("studio");
        if (!studio.isNull())
        {
            while (!studio.isNull())
            {
                if (studio.hasAttribute("name"))
                    studios.append(studio.attribute("name"));
                studio = studio.nextSiblingElement("studio");
            }
        }
    }

    // People
    QDomElement peoplexml = item.firstChildElement("people");
    if (!peoplexml.isNull())
    {
        people = ParsePeople(peoplexml);
    }

    // Artwork
    QDomElement artworkxml = item.firstChildElement("images");
    if (!artworkxml.isNull())
    {
        artwork = ParseArtwork(artworkxml);
    }

    // Have to handle season and episode a little differently.
    // If the query object comes in with a season or episode number,
    // we want to pass that through.  However, if we are doing a title/subtitle
    // lookup, we need to parse for season and episode.
    if (passseas)
    {
        season = lookup->GetSeason();
        episode = lookup->GetEpisode();
    }

    if (lookup->GetPreferDVDOrdering())
    {
        if (!season)
        {
            season = item.firstChildElement("dvdseason").text().toUInt();
        }
        if (!episode)
        {
            episode = item.firstChildElement("dvdepisode").text().toUInt();
        }
    }

    if (!season)
    {
        season = item.firstChildElement("season").text().toUInt();
    }
    if (!episode)
    {
        episode = item.firstChildElement("episode").text().toUInt();
    }
    LOG(VB_GENERAL, LOG_INFO, QString("Result Found, Season %1 Episode %2")
        .arg(season).arg(episode));

    return new MetadataLookup(lookup->GetType(), lookup->GetSubtype(),
        lookup->GetData(), lookup->GetStep(), lookup->GetAutomatic(),
        lookup->GetHandleImages(), lookup->GetAllowOverwrites(),
        lookup->GetAllowGeneric(), lookup->GetPreferDVDOrdering(), lookup->GetHost(),
        lookup->GetFilename(), title, network, status, categories, userrating,
        ratingcount, language, subtitle, tagline, description, season,
        episode, chanid, channum, chansign, channame,
        chanplaybackfilters, recgroup, playgroup, seriesid, programid,
        storagegroup, startts, endts, recstartts, recendts, programflags,
        audioproperties, videoproperties, subtitletype, certification,
        countries, popularity, budget, revenue, album, tracknum, system, year,
        releasedate, lastupdated, runtime, runtimesecs, inetref, collectionref,
        tmsref, imdb, people, studios, homepage, trailerURL, artwork, DownloadMap());
}

MetadataLookup* ParseMetadataMovieNFO(const QDomElement& item,
                                  MetadataLookup *lookup)
{
    if (!lookup)
        return new MetadataLookup();

    uint year = 0, runtime = 0, runtimesecs = 0,
         season = 0, episode = 0;
    QString title, subtitle, tagline, description,
        inetref, trailer, certification;
    float userrating = 0;
    QDate releasedate;
    QStringList categories;
    PeopleMap people;
    ArtworkMap artwork;

    // Get the easy parses
    QString titletmp;
    if (item.tagName() == "movie")
        title = Parse::UnescapeHTML(item.firstChildElement("title").text());
    else if (item.tagName() == "episodedetails")
        subtitle = Parse::UnescapeHTML(item.firstChildElement("title").text());
    userrating = item.firstChildElement("rating").text().toFloat();
    year = item.firstChildElement("year").text().toUInt();
    season = item.firstChildElement("season").text().toUInt();
    episode = item.firstChildElement("episode").text().toUInt();
    description = Parse::UnescapeHTML(item.firstChildElement("plot").text());
    tagline = Parse::UnescapeHTML(item.firstChildElement("tagline").text());
    inetref = item.firstChildElement("id").text();
    trailer = item.firstChildElement("trailer").text();
    certification = item.firstChildElement("mpaa").text();
    categories.append(item.firstChildElement("genre").text());

    QString tmpDate = item.firstChildElement("releasedate").text();
    if (!tmpDate.isEmpty())
        releasedate = QDate::fromString(tmpDate, "yyyy-MM-dd");
    else if (year > 0)
        releasedate = QDate::fromString(QString::number(year), "yyyy");

    runtime = item.firstChildElement("runtime").text()
                                               .remove(QRegExp("[A-Za-z]"))
                                               .trimmed().toUInt();
    runtimesecs = runtime * 60;

    QDomElement actor = item.firstChildElement("actor");
    if (!actor.isNull())
    {
        while (!actor.isNull())
        {
            PersonInfo info;
            info.name = actor.firstChildElement("name").text();
            info.role = actor.firstChildElement("role").text();
            info.thumbnail = actor.firstChildElement("thumb").text();
            people.insert(kPersonActor, info);
            actor = actor.nextSiblingElement("actor");
        }
    }

    QString director = item.firstChildElement("director").text();
    if (!director.isEmpty())
    {
        PersonInfo info;
        info.name = director;
        people.insert(kPersonDirector, info);
    }

    return new MetadataLookup(lookup->GetType(), lookup->GetSubtype(),
        lookup->GetData(), lookup->GetStep(),
        lookup->GetAutomatic(), lookup->GetHandleImages(),
        lookup->GetAllowOverwrites(), lookup->GetAllowGeneric(),
        lookup->GetPreferDVDOrdering(), lookup->GetHost(),
        lookup->GetFilename(), title, categories,
        userrating, subtitle, tagline, description, season, episode,
        certification, year, releasedate, runtime, runtimesecs,
        inetref, people, trailer, artwork, DownloadMap());
}

PeopleMap ParsePeople(QDomElement people)
{
    PeopleMap ret;

    QDomElement person = people.firstChildElement("person");
    if (!person.isNull())
    {
        while (!person.isNull())
        {
            if (person.hasAttribute("job"))
            {
                QString jobstring = person.attribute("job");
                PeopleType type;
                if (jobstring.toLower() == "actor")
                    type = kPersonActor;
                else if (jobstring.toLower() == "author")
                    type = kPersonAuthor;
                else if (jobstring.toLower() == "producer")
                    type = kPersonProducer;
                else if (jobstring.toLower() == "executive producer")
                    type = kPersonExecProducer;
                else if (jobstring.toLower() == "director")
                    type = kPersonDirector;
                else if (jobstring.toLower() == "cinematographer")
                    type = kPersonCinematographer;
                else if (jobstring.toLower() == "composer")
                    type = kPersonComposer;
                else if (jobstring.toLower() == "editor")
                    type = kPersonEditor;
                else if (jobstring.toLower() == "casting")
                    type = kPersonCastingDirector;
                else if (jobstring.toLower() == "artist")
                    type = kPersonArtist;
                else if (jobstring.toLower() == "album artist")
                    type = kPersonAlbumArtist;
                else if (jobstring.toLower() == "guest star")
                    type = kPersonGuestStar;
                else
                    type = kPersonActor;

                PersonInfo info;
                if (person.hasAttribute("name"))
                    info.name = person.attribute("name");
                if (person.hasAttribute("character"))
                    info.role = person.attribute("character");
                if (person.hasAttribute("thumb"))
                    info.thumbnail = person.attribute("thumb");
                if (person.hasAttribute("url"))
                    info.url = person.attribute("url");

                ret.insert(type,info);
            }
            person = person.nextSiblingElement("person");
        }
    }
    return ret;
}

ArtworkMap ParseArtwork(QDomElement artwork)
{
    ArtworkMap ret;

    QDomElement image = artwork.firstChildElement("image");
    if (!image.isNull())
    {
        while (!image.isNull())
        {
            if (image.hasAttribute("type"))
            {
                QString typestring = image.attribute("type");
                VideoArtworkType type;
                if (typestring.toLower() == "coverart")
                    type = kArtworkCoverart;
                else if (typestring.toLower() == "fanart")
                    type = kArtworkFanart;
                else if (typestring.toLower() == "banner")
                    type = kArtworkBanner;
                else if (typestring.toLower() == "screenshot")
                    type = kArtworkScreenshot;
                else if (typestring.toLower() == "poster")
                    type = kArtworkPoster;
                else if (typestring.toLower() == "back cover")
                    type = kArtworkBackCover;
                else if (typestring.toLower() == "inside cover")
                    type = kArtworkInsideCover;
                else if (typestring.toLower() == "cd image")
                    type = kArtworkCDImage;
                else
                    type = kArtworkCoverart;

                ArtworkInfo info;
                if (image.hasAttribute("thumb"))
                    info.thumbnail = image.attribute("thumb");
                if (image.hasAttribute("url"))
                    info.url = image.attribute("url");
                if (image.hasAttribute("width"))
                    info.width = image.attribute("width").toUInt();
                else
                    info.width = 0;
                if (image.hasAttribute("height"))
                    info.height = image.attribute("height").toUInt();
                else
                    info.height = 0;

                ret.insert(type,info);
            }
            image = image.nextSiblingElement("image");
        }
    }
    return ret;
}

int editDistance( const QString& s, const QString& t )
{
#define D( i, j ) d[(i) * n + (j)]
    int i;
    int j;
    int m = s.length() + 1;
    int n = t.length() + 1;
    int *d = new int[m * n];
    int result;

    for ( i = 0; i < m; i++ )
      D( i, 0 ) = i;
    for ( j = 0; j < n; j++ )
      D( 0, j ) = j;
    for ( i = 1; i < m; i++ )
    {
        for ( j = 1; j < n; j++ )
        {
            if ( s[i - 1] == t[j - 1] )
                D( i, j ) = D( i - 1, j - 1 );
            else
            {
                int x = D( i - 1, j );
                int y = D( i - 1, j - 1 );
                int z = D( i, j - 1 );
                D( i, j ) = 1 + qMin( qMin(x, y), z );
            }
        }
    }
    result = D( m - 1, n - 1 );
    delete[] d;
    return result;
#undef D
}

QString nearestName(const QString& actual, const QStringList& candidates)
{
    int deltaBest = 10000;
    int numBest = 0;
    int tolerance = gCoreContext->GetNumSetting("MetadataLookupTolerance", 5);
    QString best;

    QStringList::ConstIterator i = candidates.begin();
    while ( i != candidates.end() )
    {
        QString candidate = *i;
        int delta = editDistance( actual.toLower(), candidate.toLower() );
        if ( delta < deltaBest )
        {
            deltaBest = delta;
            numBest = 1;
            best = *i;
        }
        else if ( delta == deltaBest )
        {
            numBest++;
        }
        ++i;
    }

    if ( numBest == 1 && deltaBest <= tolerance &&
       actual.length() + best.length() >= 5 )
        return best;
    else
        return QString();
}

QDateTime RFC822TimeToQDateTime(const QString& t)
{
    QMap<QString, int> TimezoneOffsets;

    if (t.size() < 20)
        return QDateTime();

    QString time = t.simplified();
    short int hoursShift = 0, minutesShift = 0;

    QStringList tmp = time.split(' ');
    if (tmp.isEmpty())
        return QDateTime();
    if (tmp. at(0).contains(QRegExp("\\D")))
        tmp.removeFirst();
    if (tmp.size() != 5)
        return QDateTime();
    QString ltimezone = tmp.takeAt(tmp.size() -1);
    if (ltimezone.size() == 5)
    {
        bool ok;
        int tz = ltimezone.toInt(&ok);
        if(ok)
        {
            hoursShift = tz / 100;
            minutesShift = tz % 100;
        }
    }
    else
        hoursShift = TimezoneOffsets.value(ltimezone, 0);

    if (tmp.at(0).size() == 1)
        tmp[0].prepend("0");
    tmp [1].truncate(3);

    time = tmp.join(" ");

    QDateTime result;
    if (tmp.at(2).size() == 4)
        result = QLocale::c().toDateTime(time, "dd MMM yyyy hh:mm:ss");
    else
        result = QLocale::c().toDateTime(time, "dd MMM yy hh:mm:ss");
    if (result.isNull() || !result.isValid())
        return QDateTime();
    result = result.addSecs(hoursShift * 3600 * (-1) + minutesShift *60 * (-1));
    result.setTimeSpec(Qt::UTC);
    return result;
}


