// This file is a part of the xMule Project.
//
// Copyright (c) 2004 Theodore R. Smith (hopeseekr@xmule.ws / http://www.xmule.ws/)
// DSA-1024 Fingerprint: 10A0 6372 9092 85A2 BB7F 907B CB8B 654B E33B F1ED
//
// Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.xmule-project.net )
//
// 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., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "config.h"

// Test if we have _GNU_SOURCE before the next step will mess up
// setting __USE_GNU that we need for string.h to define basename
// (only needed for gcc-2.95 compatibility, gcc 3.2 always defines it)
#include <wx/setup.h>

// Mario Sergio Fujikawa Ferreira <lioux@FreeBSD.org>
// to detect if this is a *BSD system
#if defined(HAVE_SYS_PARAM_H)
#include <sys/param.h>
#endif

#ifdef PRECOMP
#	include "xmule-headers.h"
#else
#include "SharedFileList.h"                 // Needed for Module's Prototype(s)

#include "AddFileThread.h"                  // Needed for CAddFileThread - audited 4 Nov 2004
#include "CMemFile.h"                       // CMemFile - audited 5 Nov 2004
#	include "DownloadQueue.h"
#include "KnownFileList.h"                  // Needed for CKnownFile::FindKnownFile
#	include "muuli_wdr.h"
#include "otherfunctions.h"                 // Needed for GetResString - audited 5 Nov 2004
#include "packets.h"                        // Packet
#include "PartFile.h"                       // Needed for PR_NORMAL - audited 5 Nov 2004
#include "Preferences.h"                    // CPreferences::GetCatCount
#include "server.h"                         // Needed for CServer - audited 5 Nov 2004
#include "SharedFilesCtrl.h"                // Needed for CSharedFileList::ShowFile
#include "sockets.h"                        // Needed for CServerConnect - audited 5 Nov 2004
#	include "UploadQueue.h"
#include "xmule.h"                          // Needed for theApp
#	include "xmuleDlg.h"
#endif

#include <DynPrefs/DynPrefs.h>              // Needed for DynamicPreferences

#include <wx/msgdlg.h>                      // Needed for wxMessageBox
#include <time.h>
#include <wx/filename.h>
#include <sys/types.h>
#include <sys/stat.h>
//#include <unistd.h>

// *BSD compatibility
// required for basename(1) definition
#if (defined(BSD) && (BSD >= 199103))
#include <libgen.h>
#endif

// so we get basename(1) only if we have _GNU_SOURCE or BSD
// what about other constellations ? -- let's try this:
#ifndef __USE_GNU
#include <libgen.h>
#endif

CSharedFileList::CSharedFileList(CPreferences *in_prefs, CServerConnect *in_server, CKnownFileList *in_filelist)
{
    app_prefs = in_prefs;
    server = in_server;
    filelist = in_filelist;
    output = 0;
    FindSharedFiles();
}

CSharedFileList::~CSharedFileList()
{
}

void CSharedFileList::FindSharedFiles()
{
    /* Abort loading if we are shutting down. */
    if (!theApp.xmuledlg->IsRunning())
    {
        return;
    }
    /* All part files are automatically shared. */
    if (!m_Files_map.empty())
    {
        list_mut.Lock();
        m_Files_map.clear();
        list_mut.Unlock();
        theApp.downloadqueue->AddPartFilesToShare();
    }
    /* Global incoming dir and all category incoming directories are automatically shared. */
    AddFilesFromDirectory(const_cast<char *>(theApp.dynprefs->Get<wxString>("incoming-directory").c_str()));
    for (int i = 1 ; i < theApp.glob_prefs->GetCatCount() ; i++)
    {
        AddFilesFromDirectory(theApp.glob_prefs->GetCatPath(i));
    }
    //for (POSITION pos = app_prefs->shareddir_list.GetHeadPosition();pos != 0;app_prefs->shareddir_list.GetNext(pos))
    // remove bogus entries first
    for (int ij = 0 ; ij < app_prefs->shareddir_list.GetCount() ; ij++)
    {
        if (!wxFileName::DirExists(app_prefs->shareddir_list.Item(ij)))
        {
            app_prefs->shareddir_list.RemoveAt(ij);
            --ij;
        }
    }
    for (int ii = 0 ; ii < app_prefs->shareddir_list.GetCount() ; ii++)
    {
        AddFilesFromDirectory((char *) app_prefs->shareddir_list.Item(ii) .GetData());
    }
    uint32_t newFiles = CAddFileThread::GetCount();
    if (!newFiles)
    {
        theApp.xmuledlg->AddLogLine(false, GetResString(IDS_SHAREDFOUND), m_Files_map.size());
    }
    else
    {
        theApp.xmuledlg->AddLogLine(false, GetResString(IDS_SHAREDFOUNDHASHING), m_Files_map.size(), newFiles);
    }
}

void CSharedFileList::AddFilesFromDirectory(const wxString& directory)
{
    wxString searchpath = directory;
    
    // Kry - Add slash only if it's not already in string to avoid double-slash
    if (directory[directory.length() - 1] != wxT('/'))
    {
        searchpath += wxT("/*");
    }
    else
    {
        searchpath += wxT("*");
    }

    wxString fname = wxFindFirstFile(searchpath, wxFILE);

    if (fname.IsEmpty() == true)
    {
        return;
    }

    while (fname.IsEmpty() == false)
    {
        wxFileName fName(fname);
        wxDateTime accTime, modTime, crtTime;
        fName.GetTimes( &accTime, &modTime, &crtTime);
        uint32_t fdate = modTime.GetTicks();
        int koko;
        struct stat sbf;
        stat(fname.GetData(), &sbf);
        koko = sbf.st_size;
        CKnownFile *toadd = filelist->FindKnownFile((char *) fName.GetFullName() .GetData(), fdate, koko);
        //theApp.Yield();
        if (toadd)
        {
            if (m_Files_map.find(CCKey(toadd->GetFileHash())) == m_Files_map.end())
            {
                toadd->SetPath(directory);
                output->ShowFile(toadd);
                list_mut.Lock();
                m_Files_map[CCKey(toadd->GetFileHash()) ] = toadd;
                list_mut.Unlock();
            }
            else
            {
                if (wxStrcmp(fName.GetFullName() .GetData(), toadd->GetFileName()))
                printf("Warning: File '%s' already shared as '%s'\n", fName.GetFullName() .GetData(), toadd->GetFileName());
            }
        }
        else
        {
            //not in knownfilelist - start adding thread to hash file
            CAddFileThread::AddFile(directory, fName.GetFullName() .GetData());
        }
        fname = wxFindNextFile();
    }
}

void CSharedFileList::SafeAddKFile(CKnownFile *toadd, bool bOnlyAdd)
{
    // TODO: Check if the file is already known - only with another date
    //CSingleLock sLock(&list_mut,true);
    list_mut.Lock();
    if (m_Files_map.find(CCKey(toadd->GetFileHash())) != m_Files_map.end())
    {
        list_mut.Unlock();
        return;
    }
    m_Files_map[CCKey(toadd->GetFileHash()) ] = toadd;
    //sLock.Unlock();
    list_mut.Unlock();
    if (bOnlyAdd)
    {
        output->ShowFile(toadd);
        return;
    }
    if (output)
    {
        output->ShowFile(toadd);
    }
    // offer new file to server
    if (!server->IsConnected())
    {
        return;
    }
    CMemFile *files = new CMemFile(100);
    uint32_t filecount = 1;
    files->Write( &filecount, 4);
    CreateOfferedFilePacket(toadd, files);
    Packet *packet = new Packet(files);
    packet->opcode = OP_OFFERFILES;

    CServer *cur_server=server->GetCurrentServer();
    if (cur_server)
    {
        if(cur_server->GetTCPFlags()&0x00000001)
        {
            packet->PackPacket();
        }
    }

    delete files;
    theApp.uploadqueue->AddUpDataOverheadServer(packet->size);
#if defined(__DEBUG__)
    //printf("CSharedFileList::SafeAddKFile--> OP_OFFERFILES size=%d\n", packet->size);
#endif
    server->SendPacket(packet, true);
}

// removes first occurrence of 'toremove' in 'list'
void CSharedFileList::RemoveFile(CKnownFile *toremove)
{
    output->RemoveFile(toremove);
    m_Files_map.erase(CCKey(toremove->GetFileHash()));
    delete toremove;
}

#define GetDlgItem(X) (wxStaticCast(wxWindow::FindWindowById((X)),wxButton))
void CSharedFileList::Reload(bool sendtoserver, bool firstload)
{
    // Madcat - Disable reloading if reloading already in progress.
    // Kry - Fixed to let non-english language users use the 'Reload' button :P
    if (GetDlgItem(IDC_RELOADSHAREDFILES)->GetLabel() == GetResString(IDS_SF_RELOAD))
    {
        GetDlgItem(IDC_RELOADSHAREDFILES)->SetLabel(wxT("Loading..."));
        output->DeleteAllItems();
        this->FindSharedFiles();
        if ((output) && (firstload == false))
        output->ShowFileList(this);
        if (sendtoserver)
        SendListToServer();
        GetDlgItem(IDC_RELOADSHAREDFILES)->SetLabel(GetResString(IDS_SF_RELOAD));
    }
    output->InitSort();
}

void CSharedFileList::SetOutputCtrl(CSharedFilesCtrl *in_ctrl)
{
    output = in_ctrl;
    output->ShowFileList(this);
}

void CSharedFileList::SendListToServer()
{
    if (m_Files_map.empty() || !server->IsConnected())
    return;
    CMemFile *files = new CMemFile();
    uint32_t filecount = m_Files_map.size();
    files->Write( &filecount, 4);
    for (CKnownFileMap::iterator pos = m_Files_map.begin() ;
    pos != m_Files_map.end() ; pos++)
    {
        CreateOfferedFilePacket(pos->second, files);
    }
    Packet *packet = new Packet(files);
    packet->opcode = OP_OFFERFILES;

    CServer *cur_server=server->GetCurrentServer();
    if (cur_server)
    {
        if(cur_server->GetTCPFlags()&0x00000001)
        {
            packet->PackPacket();
        }
    }

    delete files;
    theApp.uploadqueue->AddUpDataOverheadServer(packet->size);
#if defined(__DEBUG__)
    //printf("CSharedFileList::SendListToServer--> OP_OFFERFILES size=%d\n", packet->size);
#endif
    server->SendPacket(packet, true);
}

CKnownFile *CSharedFileList::GetFileByIndex(int index)
{
    int count = 0;
    for (CKnownFileMap::iterator pos = m_Files_map.begin() ;
    pos != m_Files_map.end() ; pos++)
    {
        if (index == count) return pos->second;
        count++;
    }
    return 0;
}

void CSharedFileList::CreateOfferedFilePacket(CKnownFile *cur_file, CMemFile *files)
{
    files->Write(cur_file->GetFileHash(), 16);
    char *buffer = new char[6];
    memset(buffer, 0, 6);
    files->Write(buffer, 6);
    delete[] buffer;
    files->Write(cur_file->GetFileTypePtr(), 4);
    CTag *nametag = new CTag(FT_FILENAME, cur_file->GetFileName());
    nametag->WriteTagToFile(files);
    delete nametag;
    CTag *sizetag = new CTag(FT_FILESIZE, cur_file->GetFileSize());
    sizetag->WriteTagToFile(files);
    delete sizetag;
    //TODO add tags for documents mp3 etc
}

uint64_t CSharedFileList::GetDatasize()
{
    uint64_t fsize;
    fsize = 0;
    for (CKnownFileMap::iterator pos = m_Files_map.begin() ;
    pos != m_Files_map.end() ; pos++)
    {
        fsize += pos->second->GetFileSize();
    }
    return fsize;
}

CKnownFile *CSharedFileList::GetFileByID(unsigned char *filehash)
{
    CCKey tkey(filehash);
    if (m_Files_map.find(tkey) != m_Files_map.end())
    return m_Files_map[tkey];
    else
    return 0;
}

short CSharedFileList::GetFilePriorityByID(unsigned char *filehash)
{
    CKnownFile *tocheck = GetFileByID(filehash);
    if (tocheck)
    return tocheck->GetUpPriority();
    else
    // file doesn't exist:
    return - 10;
}

#if 0
CAddFileThread::CAddFileThread(): wxThread(wxTHREAD_DETACHED)
{
}

wxThread::ExitCode CAddFileThread::Entry()
{
    while (1)
    {
        g_lockWaitingForHashList.Lock();
        if (g_endWaitingForHashList)
        {
            g_endedWaitingForHashList.Signal();
            g_lockWaitingForHashList.Unlock();
            return 0;
        }
        if (g_sWaitingForHashList.IsEmpty())
        {
            // Unlocks g_lockWaitingForHashList:
            g_runWaitingForHashList.Wait();
            g_lockWaitingForHashList.Unlock();
            continue;
        }
        UnknownFile_Struct *hashfile = g_sWaitingForHashList.RemoveHead();
        g_lockWaitingForHashList.Unlock();
        CKnownFile *newrecord = new CKnownFile();
        printf("Sharing %s/%s\n", hashfile->directory, hashfile->name);
        newrecord->CreateFromFile(hashfile->directory, hashfile->name);
        g_lockWaitingForHashList.Lock();
        if (g_endWaitingForHashList)
        {
            g_endedWaitingForHashList.Signal();
            g_lockWaitingForHashList.Unlock();
            return 0;
        }
        // TODO: Possible race condition between unlocking and wxPostEvent.
        g_lockWaitingForHashList.Unlock();
        wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED, TM_FINISHEDHASHING);
        evt.SetClientData(newrecord);
        evt.SetInt((int) hashfile->partfile_Owner);
        wxPostEvent(theApp.xmuledlg, evt);
        free(hashfile->name);
        free(hashfile->directory);
        delete hashfile;
    }
}

#endif

void CSharedFileList::UpdateItem(CKnownFile *toupdate)
{
    output->UpdateItem(toupdate);
}

void CSharedFileList::GetSharedFilesByDirectory(const char *directory,
CTypedPtrList < CPtrList, CKnownFile *> &list)
{
    for (CKnownFileMap::iterator pos = m_Files_map.begin() ;
    pos != m_Files_map.end() ; pos++)
    {
        CKnownFile *cur_file = pos->second;
        if (0 != strcmp(cur_file->GetPath(), directory))
        continue;
        list.AddTail(cur_file);
    }
}
