#include "ReposLog.hpp"

#include "LogCache.hpp"
#include "svnqt/info_entry.hpp"
#include "svnqt/svnqttypes.hpp"
#include "svnqt/client.hpp"
#include "svnqt/context_listener.hpp"
#include "svnqt/cache/DatabaseException.hpp"

#include <tqsqldatabase.h>

/*!
    \fn svn::cache::ReposLog::ReposLog(svn::Client*aClient,const TQString&)
 */
svn::cache::ReposLog::ReposLog(svn::Client*aClient,const TQString&aRepository)
    :m_Client(),
              m_Database(0),
              m_ReposRoot(aRepository),m_latestHead(svn::Revision::UNDEFINED)
{
    m_Client=aClient;
    ContextP ctx = m_Client->getContext();
    if (!aRepository.isEmpty()) {
        m_Database = LogCache::self()->reposDb(aRepository);
    }
}


/*!
    \fn svn::cache::ReposLog::latestHeadRev()
 */
svn::Revision svn::cache::ReposLog::latestHeadRev()
{
    if (!m_Client||m_ReposRoot.isEmpty()) {
        return svn::Revision::UNDEFINED;
    }
    if (!m_Database) {
        m_Database = LogCache::self()->reposDb(m_ReposRoot);
        if (!m_Database) {
            return svn::Revision::UNDEFINED;
        }
    }
    /// no catch - exception has go trough...
    qDebug("Getting headrev");
    svn::InfoEntries e = m_Client->info(m_ReposRoot,svn::DepthEmpty,svn::Revision::HEAD,svn::Revision::HEAD);
    if (e.count()<1||e[0].reposRoot().isEmpty()) {
        return svn::Revision::UNDEFINED;
    }
    qDebug("Getting headrev done");
    return e[0].revision();
}


/*!
    \fn svn::cache::ReposLog::latestCachedRev()
 */
svn::Revision svn::cache::ReposLog::latestCachedRev()
{
    if (m_ReposRoot.isEmpty()) {
        return svn::Revision::UNDEFINED;
    }
    if (!m_Database) {

        m_Database = LogCache::self()->reposDb(m_ReposRoot);
        if (!m_Database) {
            return svn::Revision::UNDEFINED;
        }
    }
    TQString q("select revision from 'logentries' order by revision DESC limit 1");
    TQSqlQuery _q(TQString(), m_Database);
    if (!_q.exec(q)) {
        qDebug(_q.lastError().text().TOUTF8().data());
        return svn::Revision::UNDEFINED;
    }
    int _r;
    if (_q.isActive() && _q.next()) {
        //qDebug("Sel result: %s",_q.value(0).toString().TOUTF8().data());
        _r = _q.value(0).toInt();
    } else {
        qDebug(_q.lastError().text().TOUTF8().data());
        return svn::Revision::UNDEFINED;
    }
    return _r;
}

bool svn::cache::ReposLog::checkFill(svn::Revision&start,svn::Revision&end,bool checkHead)
{
    if (!m_Database) {
        m_Database = LogCache::self()->reposDb(m_ReposRoot);
        if (!m_Database) {
            return false;
        }
    }
    ContextP cp = m_Client->getContext();
    long long icount=0;

    svn::Revision _latest=latestCachedRev();
//    qDebug("Latest cached rev: %i",_latest.revnum());
//    qDebug("End revision is: %s",end.toString().TOUTF8().data());

    if (checkHead && _latest.revnum()>=latestHeadRev().revnum()) {
        return true;
    }

    start=date2numberRev(start);
    end=date2numberRev(end);

    // both should now one of START, HEAD or NUMBER
    if (start==svn::Revision::HEAD || (end==svn::Revision::NUMBER && start==svn::Revision::NUMBER && start.revnum()>end.revnum())) {
        svn::Revision tmp = start;
        start = end;
        end = tmp;
    }
    svn::Revision _rstart=_latest.revnum()+1;
    svn::Revision _rend = end;
    if (_rend==svn::Revision::UNDEFINED) {
//        qDebug("Setting end to Head");
        _rend=svn::Revision::HEAD;
    }
    // no catch - exception should go outside.
    if (_rstart==0){
        _rstart = 1;
    }
//    qDebug("Getting log %s -> %s",_rstart.toString().TOUTF8().data(),_rend.toString().TOUTF8().data());
    if (_rend==svn::Revision::HEAD) {
        _rend=latestHeadRev();
    }

    if (_rend==svn::Revision::HEAD||_rend.revnum()>_latest.revnum()) {
        LogEntriesMap _internal;
//        qDebug("Retrieving from network.");
        if (!m_Client->log(m_ReposRoot,_rstart,_rend,_internal,svn::Revision::UNDEFINED,true,false)) {
            return false;
        }
        LogEntriesMap::ConstIterator it=_internal.begin();

        for (;it!=_internal.end();++it) {
            _insertLogEntry((*it));
            if (cp && cp->getListener()) {
                //cp->getListener()->contextProgress(++icount,_internal.size());
                if (cp->getListener()->contextCancel()) {
                    throw DatabaseException(TQString("Could not retrieve values: User cancel."));
                }
            }
        }
    }
    return true;
}

bool svn::cache::ReposLog::fillCache(const svn::Revision&_end)
{
    svn::Revision end = _end;
    svn::Revision start = latestCachedRev().revnum()+1;
    return checkFill(start,end,false);
}

/*!
    \fn svn::cache::ReposLog::simpleLog(const svn::Revision&start,const svn::Revision&end,LogEntriesMap&target)
 */
bool svn::cache::ReposLog::simpleLog(LogEntriesMap&target,const svn::Revision&_start,const svn::Revision&_end,bool noNetwork)
{
    if (!m_Client||m_ReposRoot.isEmpty()) {
        return false;
    }
    target.clear();
    ContextP cp = m_Client->getContext();

    svn::Revision end = _end;
    svn::Revision start = _start;
    if (!noNetwork) {
        if (!checkFill(start,end,true)) {
            return false;
        }
    } else {
        end=date2numberRev(end,noNetwork);
        start=date2numberRev(start,noNetwork);
    }

    if (end==svn::Revision::HEAD) {
        end = latestCachedRev();
    }
    if (start==svn::Revision::HEAD) {
        start=latestCachedRev();
    }
    static TQString sCount("select count(*) from logentries where revision<=? and revision>=?");
    static TQString sEntry("select revision,author,date,message from logentries where revision<=? and revision>=?");
    static TQString sItems("select changeditem,action,copyfrom,copyfromrev from changeditems where revision=?");

    TQSqlQuery bcount(TQString(),m_Database);
    bcount.prepare(sCount);

    TQSqlQuery bcur(TQString(),m_Database);
    bcur.prepare(sEntry);

    TQSqlQuery cur(TQString(),m_Database);
    cur.prepare(sItems);

    bcount.bindValue(0,TQ_LLONG(end.revnum()));
    bcount.bindValue(1,TQ_LLONG(start.revnum()));
    if (!bcount.exec()) {
        qDebug(bcount.lastError().text().TOUTF8().data());
        throw svn::cache::DatabaseException(TQString("Could not retrieve count: ")+bcount.lastError().text());
        return false;
    }
    bcount.next();
    if (bcount.value(0).toLongLong()<1) {
        // we didn't found logs with this parameters
        return false;
    }

    bcur.bindValue(0,TQ_LLONG(end.revnum()));
    bcur.bindValue(1,TQ_LLONG(start.revnum()));

    if (!bcur.exec()) {
        qDebug(bcur.lastError().text().TOUTF8().data());
        throw svn::cache::DatabaseException(TQString("Could not retrieve values: ")+bcur.lastError().text());
        return false;
    }
    TQ_LLONG revision;
    while(bcur.next()) {
        revision = bcur.value(0).toLongLong();
        cur.bindValue(0,revision);
        if (!cur.exec()) {
            qDebug(cur.lastError().text().TOUTF8().data());
            throw svn::cache::DatabaseException(TQString("Could not retrieve values: ")+cur.lastError().text()
                    ,cur.lastError().number());
            return false;
        }
        target[revision].revision=revision;
        target[revision].author=bcur.value(1).toString();
        target[revision].date=bcur.value(2).toLongLong();
        target[revision].message=bcur.value(3).toString();
        while(cur.next()) {
            LogChangePathEntry lcp;
            TQString ac = cur.value(1).toString();
            lcp.action=ac[0].latin1();
            lcp.copyFromPath=cur.value(2).toString();
            lcp.path= cur.value(0).toString();
            lcp.copyFromRevision=cur.value(3).toLongLong();
            target[revision].changedPaths.push_back(lcp);
        }
        if (cp && cp->getListener()) {
            if (cp->getListener()->contextCancel()) {
                throw svn::cache::DatabaseException(TQString("Could not retrieve values: User cancel."));
                return false;
            }
        }
    }
    return true;
}


/*!
    \fn svn::cache::ReposLog::date2numberRev(const svn::Revision&)
 */
svn::Revision svn::cache::ReposLog::date2numberRev(const svn::Revision&aRev,bool noNetwork)
{
    if (aRev!=svn::Revision::DATE) {
        return aRev;
    }
    if (!m_Database) {
        return svn::Revision::UNDEFINED;
    }
    static TQString _q("select revision from logentries where date<? order by revision desc");
    TQSqlQuery query("select revision,date from logentries order by revision desc limit 1",m_Database);

    if (query.lastError().type()!=TQSqlError::None) {
        qDebug(query.lastError().text().TOUTF8().data());
    }
    bool must_remote=!noNetwork;
    if (query.next()) {
        if (query.value(1).toLongLong()>=aRev.date()) {
            must_remote=false;
        }
    }
    if (must_remote) {
        svn::InfoEntries e = (m_Client->info(m_ReposRoot,svn::DepthEmpty,aRev,aRev));;
        if (e.count()<1||e[0].reposRoot().isEmpty()) {
            return aRev;
        }
        return e[0].revision();
    }
    query.prepare(_q);
    query.bindValue(0,TQ_LLONG(aRev.date()));
    query.exec();
    if (query.lastError().type()!=TQSqlError::None) {
        qDebug(query.lastError().text().TOUTF8().data());
    }
    if (query.next()) {
        return query.value(0).toInt();
    }
    // not found...
    if (noNetwork) {
        return svn::Revision::UNDEFINED;
    }
    svn::InfoEntries e = (m_Client->info(m_ReposRoot,svn::DepthEmpty,svn::Revision::HEAD,svn::Revision::HEAD));;
    if (e.count()<1||e[0].reposRoot().isEmpty()) {
        return svn::Revision::UNDEFINED;
    }
    return e[0].revision();
}


/*!
    \fn svn::cache::ReposLog::insertLogEntry(const svn::LogEntry&)
 */
bool svn::cache::ReposLog::_insertLogEntry(const svn::LogEntry&aEntry)
{
    TQSqlRecord *buffer;

    m_Database->transaction();
    TQ_LLONG j = aEntry.revision;
    static TQString qEntry("insert into logentries (revision,date,author,message) values (?,?,?,?)");
    static TQString qPathes("insert into changeditems (revision,changeditem,action,copyfrom,copyfromrev) values (?,?,?,?,?)");
    TQSqlQuery _q(TQString(),m_Database);
    _q.prepare(qEntry);
    _q.bindValue(0,j);
    _q.bindValue(1,aEntry.date);
    _q.bindValue(2,aEntry.author);
    _q.bindValue(3,aEntry.message);
    if (!_q.exec()) {
        m_Database->rollback();
        qDebug("Could not insert values: %s",_q.lastError().text().TOUTF8().data());
        qDebug(_q.lastQuery().TOUTF8().data());
        throw svn::cache::DatabaseException(TQString("Could not insert values: ")+_q.lastError().text(),_q.lastError().number());
    }
    _q.prepare(qPathes);
    svn::LogChangePathEntries::ConstIterator cpit = aEntry.changedPaths.begin();
    for (;cpit!=aEntry.changedPaths.end();++cpit){
        _q.bindValue(0,j);
        _q.bindValue(1,(*cpit).path);
        _q.bindValue(2,TQString(TQChar((*cpit).action)));
        _q.bindValue(3,(*cpit).copyFromPath);
        _q.bindValue(4,TQ_LLONG((*cpit).copyFromRevision));
        if (!_q.exec()) {
            m_Database->rollback();
            qDebug("Could not insert values: %s",_q.lastError().text().TOUTF8().data());
            qDebug(_q.lastQuery().TOUTF8().data());
            throw svn::cache::DatabaseException(TQString("Could not insert values: ")+_q.lastError().text(),_q.lastError().number());
        }
    }
        m_Database->commit();
    return true;
}

bool svn::cache::ReposLog::insertLogEntry(const svn::LogEntry&aEntry)
{
    return _insertLogEntry(aEntry);
}


/*!
    \fn svn::cache::ReposLog::log(const svn::Path&,const svn::Revision&start, const svn::Revision&end,const svn::Revision&peg,svn::LogEntriesMap&target, bool strictNodeHistory,int limit))
 */
bool svn::cache::ReposLog::log(const svn::Path&what,const svn::Revision&_start, const svn::Revision&_end,const svn::Revision&_peg,svn::LogEntriesMap&target, bool strictNodeHistory,int limit)
{
    static TQString s_q("select logentries.revision,logentries.author,logentries.date,logentries.message from logentries where logentries.revision in (select changeditems.revision from changeditems where (changeditems.changeditem='%1' or changeditems.changeditem GLOB '%2/*') %3 GROUP BY changeditems.revision) ORDER BY logentries.revision DESC");

    static TQString s_e("select changeditem,action,copyfrom,copyfromrev from changeditems where changeditems.revision='%1'");

    svn::Revision peg = date2numberRev(_peg,true);
    svn::Revision end = date2numberRev(_end,true);
    svn::Revision start = date2numberRev(_start,true);
    TQString query_string = TQString(s_q).tqarg(what.native()).tqarg(what.native()).tqarg((peg==svn::Revision::UNDEFINED?"":TQString(" AND revision<=%1").tqarg(peg.revnum())));
    if (peg==svn::Revision::UNDEFINED) {
        peg = latestCachedRev();
    }
    if (!itemExists(peg,what)) {
        throw svn::cache::DatabaseException(TQString("Entry '%1' does not exists at revision %2").tqarg(what.native()).tqarg(peg.toString()));
    }
    if (limit>0) {
        query_string+=TQString(" LIMIT %1").tqarg(limit);
    }
    TQSqlQuery _q(TQString(),m_Database);
    TQSqlQuery _q2(TQString(),m_Database);
    _q.prepare(query_string);
    if (!_q.exec()) {
        qDebug("Could not select values: %s",_q.lastError().text().TOUTF8().data());
        qDebug(_q.lastQuery().TOUTF8().data());
        throw svn::cache::DatabaseException(TQString("Could not select values: ")+_q.lastError().text(),_q.lastError().number());
    }
    while(_q.next()) {
        TQ_LLONG revision = _q.value(0).toLongLong();
        target[revision].revision=revision;
        target[revision].author=_q.value(1).toString();
        target[revision].date=_q.value(2).toLongLong();
        target[revision].message=_q.value(3).toString();
        query_string=s_e.tqarg(revision);
        _q2.prepare(query_string);
        if (!_q2.exec()) {
            qDebug("Could not select values: %s",_q2.lastError().text().TOUTF8().data());
        } else {
            while (_q2.next()) {
                target[revision].changedPaths.push_back (
                        LogChangePathEntry (_q2.value(0).toString(),
                                            _q2.value(1).toString()[0],
                                            _q2.value(2).toString(),
                                            _q2.value(3).toLongLong()
                                           )
                                                        );
            }
        }

    }
    return true;
}


/*!
    \fn svn::cache::ReposLog::itemExists(const svn::Revision&,const TQString&)
 */
bool svn::cache::ReposLog::itemExists(const svn::Revision&peg,const svn::Path&path)
{
    /// @todo this moment I have no idea how to check real  with all moves and deletes of parent folders without a hell of sql statements so we make it quite simple: it exists if we found it.


#if 0
    static TQString _s1("select revision from changeditems where changeditem='%1' and action='A' and revision<=%2 order by revision desc limit 1");
    TQSqlQuery _q(TQString(),m_Database);
    TQString query_string=TQString(_s1).tqarg(path.native()).tqarg(peg.revnum());
    if (!_q.exec(query_string)) {
        qDebug("Could not select values: %s",_q.lastError().text().TOUTF8().data());
        qDebug(_q.lastQuery().TOUTF8().data());
        throw svn::cache::DatabaseException(TQString("Could not select values: ")+_q.lastError().text(),_q.lastError().number());
    }
    qDebug(_q.lastQuery().TOUTF8().data());


    svn::Path _p = path;
    static TQString _s2("select revision from changeditem where changeditem in (%1) and action='D' and revision>%2 and revision<=%3 order by revision desc limit 1");
    TQStringList p_list;
    while (_p.length()>0) {
        p_list.append(TQString("'%1'").tqarg(_p.native()));
        _p.removeLast();
    }
    query_string=TQString(_s2).tqarg(p_list.join(",")).tqarg();
#endif
    return true;
}

bool svn::cache::ReposLog::isValid()const
{
    if (!m_Database) {
        m_Database = LogCache::self()->reposDb(m_ReposRoot);
        if (!m_Database) {
            return false;
        }
    }
    return true;
}