/* -*- c++ -*-
 *
 * fileinfo.cpp
 *
 * Copyright (C) 2003 Petter Stokke <ummo@hellokitty.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */


#include <kdebug.h>
#include <klocale.h>
#include <qregexp.h>

#include "fileinfo.h"
#include "donkeymessage.h"



FileInfo::FileInfo(DonkeyMessage* msg, int proto)
{
    first = true;
    updateFileInfo(msg, proto);
}

FileInfo::FileInfo(const FileInfo& fi)
{
    num = fi.fileNo();
    first = true;
    updateFileInfo(&fi);
}

FileInfo::~FileInfo()
{
}

void FileInfo::updateFileInfo(const FileInfo* fi)
{
    network = fi->fileNetwork();
    names = fi->fileNames();
    size = fi->fileSize();
    downloaded = fi->fileDownloaded();
    if (first) {
	firstdown = downloaded;
	first = false;
	firsttime = time(0);
    }
    nlocations = fi->fileNLocations();
    nclients = fi->fileNClients();
    state = fi->fileState();
    abortedmsg = fi->fileAbortedMsg();
    chunks = fi->fileChunks();
    availability = fi->fileAvailability();
    speed = fi->fileSpeed();
    chunks_age = fi->fileChunksAge();
    age = fi->fileAge();
    format = fi->fileFormat();
    formatinfo = fi->fileFormatInfo();
    name = fi->fileName();
    lastseen = fi->fileLastSeen();
    priority = fi->filePriority();
    comment = fi->fileComment();
    uids = fi->fileUids();
}

void FileInfo::updateFileInfo(DonkeyMessage* msg, int proto)
{
    int i,j;
    num = msg->readInt32();
    network = msg->readInt32();
    j = msg->readInt16();
    names.clear();
    for (i=0; i<j; i++)
        names.append(msg->readString());
    QByteArray md4(16);
    for (i=0; i<16; i++)
        md4[i] = msg->readInt8();
    size = msg->readInt64();
    downloaded = msg->readInt64();
    if (first) {
	firstdown = downloaded;
	first = false;
	firsttime = time(0);
    }
    nlocations = msg->readInt32();
    nclients = msg->readInt32();
    state = (State)msg->readInt8();
    if (state == 6)
        abortedmsg = msg->readString();

    chunks = msg->readByteArray();

    availability.clear();
    j = msg->readInt16();
    for (i=0; i<j; i++) {
        int av_net = msg->readInt32();
        availability.replace(av_net, msg->readByteArray());
    }

    speed = msg->readFloat();

    j = msg->readInt16();
    chunks_age.clear();
    for (i=0; i<j; i++) {
        time_t a = msg->readDate();
        if (a < 0) a = 0x7fffffff + a; // FIXME: Is this hack still relevant?
        chunks_age.append(a);
    }

    age = msg->readDate();
    if (age < 0) age = 0x7fffffff + age; // FIXME: Is this hack still relevant?

    format = msg->readInt8();
    switch (format) {
    case 1:
    {
        QString foo = msg->readString();
        QString bar = msg->readString();
        formatinfo = foo + " " + bar;
    } break;
    case 2:
    {
        QString foo = msg->readString();
        int i = msg->readInt32();
        int j = msg->readInt32();
        int k = msg->readInt32();
        int l = msg->readInt32();
        formatinfo = i18n("AVI codec resolution framerate bitrate", "AVI %1 %2x%3 %4fps rate %5")
            .arg(foo).arg(i).arg(j).arg((double)k / 1000.0, 0, 'f', 2).arg(l);
    } break;
    case 3:
    {
        QString foo = msg->readString();
        QString bar = msg->readString();
        QString baz = msg->readString();
        QString quux = msg->readString();
        msg->readString();
        int i = msg->readInt32();
        msg->readInt32();
        formatinfo = i18n("MP3 title artist album year track", "MP3 Ti:%1 Ar:%2 Al:%3 Yr:%4 Tr:%5")
            .arg(foo).arg(bar).arg(baz).arg(quux).arg(i);
    } break;
    case 4:
    {
        formatinfo = i18n("Ogg format header", "Ogg");
        int i = msg->readInt16();
        for (; i; i--) {
            int streamNo = msg->readInt32();
            int foo = msg->readInt8();
            QString streamType;
            switch (foo) {
                case 0: streamType = i18n("Ogg stream type: Video", "Video"); break;
                case 1: streamType = i18n("Ogg stream type: Audio", "Audio"); break;
                case 2: streamType = i18n("Ogg stream type: Text", "Text"); break;
                case 3: streamType = i18n("Ogg stream type: Index", "Index"); break;
                case 4: streamType = i18n("Ogg stream type: Vorbis", "Vorbis"); break;
                case 5: streamType = i18n("Ogg stream type: Theora", "Theora"); break;
                default: streamType = i18n("Ogg stream type: Unknown", "Unknown"); break;
            }
            int j = msg->readInt16();
            QStringList tags;
            for (; j; j--) {
                int type = msg->readInt8();
                switch (type) {
                    case 0: tags.append(i18n("Ogg codec tag", "Codec:%1").arg(msg->readString())); break;
                    case 1: tags.append(i18n("Ogg bytes per sample tag", "BPS:%1").arg(msg->readInt32())); break;
                    case 2: tags.append(i18n("Ogg duration tag", "Dur:%1").arg(msg->readInt32())); break;
                    case 3: tags.append(i18n("Ogg subtitle tag", "Sub")); break;
                    case 4: tags.append(i18n("Ogg index tag", "Index")); break;
                    case 5: tags.append(i18n("Ogg audio channels tag", "AudCh:%1").arg(msg->readInt32())); break;
                    case 6: tags.append(i18n("Ogg audio sample rate tag", "SmpRt:%1").arg(msg->readFloat())); break;
                    case 7: tags.append(i18n("Ogg audio block align tag", "BlkAl:%1").arg(msg->readInt32())); break;
                    case 8: tags.append(i18n("Ogg audio average bytes per second tag", "AvgBPS:%1").arg(msg->readFloat())); break;
                    case 9: tags.append(i18n("Ogg Vorbis version tag", "VorbisVer:%1").arg(msg->readFloat())); break;
                    case 10: tags.append(i18n("Ogg Vorbis sample rate tag", "SmpRt:%1").arg(msg->readFloat())); break;
                    case 11: {
                        QStringList bitRates;
                        int k = msg->readInt16();
                        for (; k; k--) {
                            int rateType = msg->readInt8();
                            switch (rateType) {
                                case 0: bitRates.append(i18n("Ogg Vorbis maximum bitrate tag", "Max:%1").arg(msg->readFloat())); break;
                                case 1: bitRates.append(i18n("Ogg Vorbis nominal bitrate tag", "Nom:%1").arg(msg->readFloat())); break;
                                case 2: bitRates.append(i18n("Ogg Vorbis minimum bitrate tag", "Min:%1").arg(msg->readFloat())); break;
                                default: break;
                            }
                        }
                        tags.append(i18n("Ogg Vorbis bitrates tag", "BitRate:%1").arg(bitRates.join(i18n("Ogg Vorbis bitrate tag separator", ","))));
                    } break;
                    case 12: tags.append(i18n("Ogg Vorbis block size 0 tag", "BlkSz0:%1").arg(msg->readInt32())); break;
                    case 13: tags.append(i18n("Ogg Vorbis block size 1 tag", "BlkSz1:%1").arg(msg->readInt32())); break;
                    case 14: tags.append(i18n("Ogg video width tag", "Wt:%1").arg(msg->readFloat())); break;
                    case 15: tags.append(i18n("Ogg video height tag", "Ht:%1").arg(msg->readFloat())); break;
                    case 16: tags.append(i18n("Ogg video sample rate tag", "SmpRt:%1").arg(msg->readFloat())); break;
                    case 17: tags.append(i18n("Ogg video aspect ratio tag", "Aspect:%1").arg(msg->readFloat())); break;
                    case 18: {
                        QString cs;
                        switch (msg->readInt8()) {
                            case 1: cs = i18n("Ogg Theora CS tag value: Rec470M", "Rec470M"); break;
                            case 2: cs = i18n("Ogg Theora CS tag value: Rec470BG", "Rec470BG"); break;
                            default: cs = i18n("Ogg Theora CS tag value: undefined", "Undef"); break;
                        }
                        tags.append(i18n("Ogg Theora CS tag", "CS:%1").arg(cs));
                    } break;
                    case 19: tags.append(i18n("Ogg Theora quality tag", "Qual:%1").arg(msg->readInt32())); break;
                    case 20: tags.append(i18n("Ogg Theora average bytes per second tag", "AvgBPS:%1").arg(msg->readInt32())); break;
                    default: break;
                }
            }
            formatinfo += i18n("Ogg stream block", " %1:%2 [%3]").arg(streamNo).arg(streamType)
                          .arg(tags.join(i18n("Ogg stream tag separator", " ")));
        }
    } break;
    default:
        formatinfo = i18n("Unknown format");
        break;
    }

    name = msg->readString();
    lastseen = msg->readInt32(); // FIXME: Is this relative?
    priority = msg->readInt32();
    if (priority > 0x40000000) priority -= 0x80000000;
    comment = msg->readString();
    uids.clear();
    if (proto >= 31) {
        for (i = msg->readInt16(); i; i--)
            uids.append(msg->readString());
    } else uids.append(QString("urn:ed2k:") + md4ToString(md4));
}

const int FileInfo::fileNo() const
{
    return num;
}

const int FileInfo::fileNetwork() const
{
    return network;
}

const QString& FileInfo::fileName() const
{
    return name;
}

const QStringList& FileInfo::fileNames() const
{
    return names;
}

const QStringList& FileInfo::fileUids() const
{
     return uids;
}

QString FileInfo::fileUid() const
{
    return uids.first();
}

QString FileInfo::fileUid(const QString& type) const
{
    QRegExp match(QString("^urn:") + type + ":");
    QStringList results = uids.grep(match);
    if (!results.count()) return QString::null;
    QString result(results.first());
    result.replace(match, "");
    return result;
}

const int64 FileInfo::fileSize() const
{
    return size;
}

const int64 FileInfo::fileDownloaded() const
{
    return downloaded;
}

const int64 FileInfo::fileFirstDownloaded() const
{
    return firstdown;
}

const int FileInfo::fileNLocations() const
{
    return nlocations;
}

const int FileInfo::fileNClients() const
{
    return nclients;
}

const FileInfo::State FileInfo::fileState() const
{
    return state;
}

const QString& FileInfo::fileAbortedMsg() const
{
    return abortedmsg;
}

const QByteArray& FileInfo::fileChunks() const
{
    return chunks;
}

const QMap<int,QByteArray>& FileInfo::fileAvailability() const
{
    return availability;
}

const double FileInfo::fileSpeed() const
{
    return speed;
}

const QValueList<time_t>& FileInfo::fileChunksAge() const
{
    return chunks_age;
}

const time_t FileInfo::fileAge() const
{
    return age;
}

const time_t FileInfo::fileFirstTime() const
{
    return firsttime;
}

const int FileInfo::fileFormat() const
{
    return format;
}

const QString& FileInfo::fileFormatInfo() const
{
    return formatinfo;
}

const int FileInfo::fileLastSeen() const
{
    return lastseen;
}

const int FileInfo::filePriority() const
{
    return priority;
}

const QMap<int,QString>& FileInfo::fileSources() const
{
    return sources;
}

const QString& FileInfo::fileComment() const
{
    return comment;
}


void FileInfo::setFileName(const QString& newname)
{
    name = newname;
}

void FileInfo::addSource(int source)
{
    if (!sources.contains(source))
        sources.insert(source, QString::null);
}

void FileInfo::removeSource(int source)
{
    sources.remove(source);
}

void FileInfo::updateAvailability(int source, const QString& avail)
{
    sources.insert(source, avail);
}

void FileInfo::updateDownloadStatus(DonkeyMessage* msg, int)
{
    downloaded = msg->readInt64();
    speed = msg->readFloat();
    if (msg->opcode() >= 46)
        lastseen = msg->readInt32();
}


QString FileInfo::md4ToString(const QByteArray& hash)
{
    char foo[33], bar[16];
    int i;
    foo[0] = 0;
    for (i=0; i<16; i++) {
        sprintf(bar, "%02x", (unsigned char)hash[i]);
        strcat(foo, bar);
    }
    return QString(foo).upper();
}

static const QString hexmap = "0123456789ABCDEF";

QByteArray FileInfo::stringToMd4(const QString& hash)
{
    QByteArray out(16);
    if (hash.length() != 32) return out;
    QString in = hash.upper();
    int b;
    for (int i=0; i<(int)in.length(); i+=2) {
        b = hexmap.find(in[i]) << 4;
        b |= hexmap.find(in[i+1]);
        out[i>>1] = b;
    }
    return out;
}

