#include "LogCache.hpp"

#include <tqdir.h>
#include <tqsql.h>
#include <tqsqldatabase.h>
#include <tqthreadstorage.h>
#include <tqmap.h>

#include "svnqt/path.hpp"
#include "svnqt/cache/DatabaseException.hpp"

#ifndef NO_SQLITE3
#include "sqlite3/qsql_sqlite3.h"
#define SQLTYPE "QSQLITE3"
#else
#define SQLTYPE "QSQLITE"
#endif

#define SQLMAIN "logmain-logcache"
#define SQLMAINTABLE "logdb"

namespace svn {
namespace cache {

LogCache* LogCache::mSelf = 0;

class ThreadDBStore
{
public:
    ThreadDBStore(){
        m_DB=0;
    }
    ~ThreadDBStore(){
        m_DB=0;
        TQSqlDatabase::removeDatabase(key);
        TQMap<TQString,TQString>::Iterator it;
        for (it=reposCacheNames.begin();it!=reposCacheNames.end();++it) {
            TQSqlDatabase::removeDatabase(it.data());
        }
    }

    TQDataBase m_DB;
    TQString key;
    TQMap<TQString,TQString> reposCacheNames;
};

class LogCacheData
{

protected:
    TQMutex m_singleDbMutex;

public:
    LogCacheData(){}
    ~LogCacheData(){
        if (m_mainDB.hasLocalData()) {
            m_mainDB.setLocalData(0L);
        }
    }

    bool checkReposDb(TQDataBase aDb)
    {
        if (!aDb) {
            return false;
        }
        if (!aDb->open()) {
            return false;
        }

        TQSqlQuery _q(TQString(), aDb);
        TQStringList list = aDb->tables();

        if (list.find("logentries")==list.end()) {
            aDb->transaction();
            _q.exec("CREATE TABLE \"logentries\" (\"revision\" INTEGER UNIQUE,\"date\" INTEGER,\"author\" TEXT, \"message\" TEXT)");
            aDb->commit();
        }
        if (list.find("changeditems")==list.end()) {
            aDb->transaction();
            _q.exec("CREATE TABLE \"changeditems\" (\"revision\" INTEGER,\"changeditem\" TEXT,\"action\" TEXT,\"copyfrom\" TEXT,\"copyfromrev\" INTEGER, PRIMARY KEY(revision,changeditem,action))");
            aDb->commit();
        }
        list = aDb->tables();
        if (list.find("logentries")==list.end() || list.find("changeditems")==list.end()) {
            return false;
        }
        return true;
    }

    TQString createReposDB(const svn::Path&reposroot) {
        TQMutexLocker locker( &m_singleDbMutex );

        TQDataBase _mdb = getMainDB();

        TQSqlQuery query1(TQString(),_mdb);
        TQString q("insert into "+TQString(SQLMAINTABLE)+" (reposroot) VALUES('"+reposroot+"')");
        _mdb->transaction();

        query1.exec(q);
        _mdb->commit();
        TQSqlQuery query(TQString(),_mdb);
        query.prepare(s_reposSelect);
        query.bindValue(0,reposroot.native());
        query.exec();
        TQString db;
        if (query.lastError().type()==TQSqlError::None && query.next()) {
            db = query.value(0).toString();
        }
        else {
            tqDebug("Error select_01: %s (%s)",query.lastError().text().TOUTF8().data(),
                   query.lastQuery().TOUTF8().data());
        }
        if (!db.isEmpty()) {
            TQString fulldb = m_BasePath+"/"+db+".db";
            TQDataBase _db = TQSqlDatabase::addDatabase(SQLTYPE,"tmpdb");
            _db->setDatabaseName(fulldb);
            if (!checkReposDb(_db)) {
            }
            TQSqlDatabase::removeDatabase("tmpdb");
        }
        return db;
    }

    TQDataBase getReposDB(const svn::Path&reposroot) {
        if (!getMainDB()) {
            return 0;
        }
        bool checkDone = false;
        // make sure path is correct eg. without traling slashes.
        TQString dbFile;
        TQSqlQuery c(TQString(),getMainDB());
        c.prepare(s_reposSelect);
        c.bindValue(0,reposroot.native());
        c.exec();

        //tqDebug("Check for path: "+reposroot.native());

        // only the first one
        if ( c.next() ) {
/*            tqDebug( c.value(0).toString() + ": " +
                    c.value(0).toString() );*/
            dbFile = c.value(0).toString();
        }
        if (dbFile.isEmpty()) {
            dbFile = createReposDB(reposroot);
            if (dbFile.isEmpty()) {
                return 0;
            }
            checkDone=true;
        }
        if (m_mainDB.localData()->reposCacheNames.find(dbFile)!=m_mainDB.localData()->reposCacheNames.end()) {
            return TQSqlDatabase::database(m_mainDB.localData()->reposCacheNames[dbFile]);
        }
        int i = 0;
        TQString _key = dbFile;
        while (TQSqlDatabase::contains(_key)) {
            _key = TQString("%1-%2").arg(dbFile).arg(i++);
        }
//        tqDebug("The repository key is now: %s",_key.TOUTF8().data());
        TQDataBase _db = TQSqlDatabase::addDatabase(SQLTYPE,_key);
        if (!_db) {
            return 0;
        }
        TQString fulldb = m_BasePath+"/"+dbFile+".db";
        _db->setDatabaseName(fulldb);
//        tqDebug("try database open %s",fulldb.TOUTF8().data());
        if (!checkReposDb(_db)) {
            tqDebug("no DB opened");
            _db = 0;
        } else {
            tqDebug("Insert into map");
            m_mainDB.localData()->reposCacheNames[dbFile]=_key;
        }
        return _db;
    }

    TQDataBase getMainDB()const
    {
        if (!m_mainDB.hasLocalData()) {
            unsigned i=0;
            TQString _key = SQLMAIN;
            while (TQSqlDatabase::contains(_key)) {
                _key.sprintf("%s-%i",SQLMAIN,i++);
            }
            tqDebug("The key is now: %s",_key.TOUTF8().data());

            TQDataBase db = TQSqlDatabase::addDatabase(SQLTYPE,_key);
            db->setDatabaseName(m_BasePath+"/maindb.db");
            if (!db->open()) {
                tqWarning("%s", TQString("Failed to open main database: " + db->lastError().text()).ascii());
            } else {
                m_mainDB.setLocalData(new ThreadDBStore);
                m_mainDB.localData()->key = _key;
                m_mainDB.localData()->m_DB = db;
            }
        }
        if (m_mainDB.hasLocalData()) {
            return m_mainDB.localData()->m_DB;
        } else {
            return 0;
        }
    }
    TQString m_BasePath;

    mutable TQThreadStorage<ThreadDBStore*> m_mainDB;

    static const TQString s_reposSelect;
};


TQString LogCache::s_CACHE_FOLDER="logcache";
const TQString LogCacheData::s_reposSelect=TQString("SELECT id from ")+TQString(SQLMAINTABLE)+TQString(" where reposroot=? ORDER by id DESC");

/*!
    \fn svn::cache::LogCache::LogCache()
 */
LogCache::LogCache()
{
    m_BasePath = TQDir::HOMEDIR()+"/.svnqt";
    setupCachePath();
}

LogCache::LogCache(const TQString&aBasePath)
{
    if (mSelf) {
        delete mSelf;
    }
    mSelf=this;
    if (aBasePath.isEmpty()) {
        m_BasePath=TQDir::HOMEDIR()+"/.svnqt";
    } else {
        m_BasePath=aBasePath;
    }
    setupCachePath();
}


LogCache::~LogCache()
{
}

/*!
    \fn svn::cache::LogCache::setupCachePath()
 */
void LogCache::setupCachePath()
{
    m_CacheData = new LogCacheData;
    m_CacheData->m_BasePath=m_BasePath;
    TQDir d;
    if (!d.exists(m_BasePath)) {
        d.mkdir(m_BasePath);
    }
    m_BasePath=m_BasePath+"/"+s_CACHE_FOLDER;
    if (!d.exists(m_BasePath)) {
        d.mkdir(m_BasePath);
    }
    m_CacheData->m_BasePath=m_BasePath;
    if (d.exists(m_BasePath)) {
        setupMainDb();
    }
}

void LogCache::setupMainDb()
{
#ifndef NO_SQLITE3
    if (!TQSqlDatabase::isDriverAvailable(SQLTYPE)) {
        TQSqlDatabase::registerSqlDriver(SQLTYPE,new TQSqlDriverCreator<TQSQLite3Driver>);
    }
#endif
    TQDataBase mainDB = m_CacheData->getMainDB();
    if (!mainDB || !mainDB->open()) {
        tqWarning("%s", TQString("Failed to open main database: " + (mainDB?mainDB->lastError().text():"No database object.")).ascii());
    } else {
        TQSqlQuery q(TQString(), mainDB);
        mainDB->transaction();
        if (!q.exec("CREATE TABLE IF NOT EXISTS \""+TQString(SQLMAINTABLE)+"\" (\"reposroot\" TEXT,\"id\" INTEGER PRIMARY KEY NOT NULL);")) {
            tqWarning("%s", TQString("Failed create main database: " + mainDB->lastError().text()).ascii());
        }
        mainDB->commit();
    }
}

}
}


/*!
    \fn svn::cache::LogCache::self()
 */
svn::cache::LogCache* svn::cache::LogCache::self()
{
    if (!mSelf) {
        mSelf=new LogCache();
    }
    return mSelf;
}


/*!
    \fn svn::cache::LogCache::reposDb()
 */
TQDataBase  svn::cache::LogCache::reposDb(const TQString&aRepository)
{
//    tqDebug("reposDB");
    return m_CacheData->getReposDB(aRepository);
}


/*!
    \fn svn::cache::LogCache::cachedRepositories()const
 */
TQStringList svn::cache::LogCache::cachedRepositories()const
{
    static TQString s_q(TQString("select \"reposroot\" from ")+TQString(SQLMAINTABLE)+TQString("order by reposroot"));
    TQDataBase mainDB = m_CacheData->getMainDB();
    TQStringList _res;
    if (!mainDB || !mainDB->open()) {
        tqWarning("Failed to open main database.");
        return _res;
    }
    TQSqlQuery cur(TQString(),mainDB);
    cur.prepare(s_q);
    if (!cur.exec()) {
        tqDebug("%s", TQString(cur.lastError().text().TOUTF8().data()).ascii());
        throw svn::cache::DatabaseException(TQString("Could not retrieve values: ")+cur.lastError().text());
        return _res;
    }
    while (cur.next()) {
        _res.append(cur.value(0).toString());
    }

    return _res;
}

bool svn::cache::LogCache::valid()const
{
    TQDataBase mainDB = m_CacheData->getMainDB();
    if (!mainDB || !mainDB->open()) {
        return false;
    }
    return true;
}
