/*
 * Copyright (C) 2007 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include <kjs/wk_config.h>
#include "DatabaseTracker.h"

#include "Database.h"
#include "FileSystem.h"
#include "NotImplemented.h"
#include "SQLiteStatement.h"

namespace WebCore {

DatabaseTracker& DatabaseTracker::tracker()
{
    static DatabaseTracker tracker;

    return tracker;
}

DatabaseTracker::DatabaseTracker()
{
}

void DatabaseTracker::setDatabasePath(const String& path)
{        
    m_databasePath = path;
    openTrackerDatabase();
}

const String& DatabaseTracker::databasePath()
{
    return m_databasePath;
}

void DatabaseTracker::openTrackerDatabase()
{
    ASSERT(!m_database.isOpen());
    
    makeAllDirectories(m_databasePath);
    String databasePath = pathByAppendingComponent(m_databasePath, "Databases.db");

    if (!m_database.open(databasePath)) {
        // FIXME: What do do here?
        return;
    }
    if (!m_database.tableExists("Origins")) {
        if (!m_database.executeCommand("CREATE TABLE Origins (origin TEXT UNIQUE ON CONFLICT REPLACE, creationPolicy INTEGER NOT NULL ON CONFLICT FAIL, sizePolicy INTEGER NOT NULL ON CONFLICT FAIL);")) {
            // FIXME: and here
        }
    }

    if (!m_database.tableExists("Databases")) {
        if (!m_database.executeCommand("CREATE TABLE Databases (guid INTEGER PRIMARY KEY AUTOINCREMENT, origin TEXT UNIQUE ON CONFLICT REPLACE, name TEXT UNIQUE ON CONFLICT REPLACE, path TEXT NOT NULL ON CONFLICT FAIL);")) {
            // FIXME: and here
        }
    }
}
    
String DatabaseTracker::fullPathForDatabase(const String& origin, const String& name)
{
    SQLiteStatement statement(m_database, "SELECT path FROM Databases WHERE origin=? AND name=?;");

    if (statement.prepare() != SQLResultOk)
        return "";

    statement.bindText16(1, origin, false);
    statement.bindText16(2, name, false);

    int result = statement.step();

    if (result == SQLResultRow)
        return pathByAppendingComponent(m_databasePath, statement.getColumnText16(0));
    if (result != SQLResultDone) {
        LOG_ERROR("Failed to retrieve filename from Database Tracker for origin %s, name %s", origin.ascii().data(), name.ascii().data());
        return "";
    }

    SQLiteStatement sequenceStatement(m_database, "SELECT seq FROM sqlite_sequence WHERE name='Databases';");

    // FIXME: More informative error handling here, even though these steps should never fail
    if (sequenceStatement.prepare() != SQLResultOk)
        return "";
    result = sequenceStatement.step();

    // This has a range of 2^63 and starts at 0 for every time a user resets Safari -
    // I can't imagine it'd over overflow
    int64_t seq = 0;
    if (result == SQLResultRow) {
        seq = sequenceStatement.getColumnInt64(0);
    } else if (result != SQLResultDone)
        return "";

    String filename;
    do {
        ++seq;
        filename = pathByAppendingComponent(m_databasePath, String::format("%016llx.db", seq));
    } while (fileExists(filename));

    sequenceStatement.finalize();

    if (!addDatabase(origin, name, String::format("%016llx.db", seq)))
        return "";

    return filename;
}

void DatabaseTracker::populateOrigins()
{
    if (m_origins)
        return;

    m_origins.set(new HashSet<String>);

    if (!m_database.isOpen())
        return;

    SQLiteStatement statement(m_database, "SELECT DISTINCT origin FROM Databases;");

    if (statement.prepare() != SQLResultOk)
        return;

    int result;
    while ((result = statement.step()) == SQLResultRow)
        m_origins->add(statement.getColumnText16(0));

    if (result != SQLResultDone)
        LOG_ERROR("Failed to read in all origins from the database");

    return;
}

const HashSet<String>& DatabaseTracker::origins()
{
    if (!m_origins)
        populateOrigins();

    return *(m_origins.get());
}

bool DatabaseTracker::databaseNamesForOrigin(const String& origin, Vector<String>& resultVector)
{
    if (!m_database.isOpen())
        return false;

    SQLiteStatement statement(m_database, "SELECT name FROM Databases where origin=?;");

    if (statement.prepare() != SQLResultOk)
        return false;

    statement.bindText16(1, origin, false);

    int result;
    while ((result = statement.step()) == SQLResultRow)
        resultVector.append(statement.getColumnText16(0));

    if (result != SQLResultDone) {
        LOG_ERROR("Failed to retrieve all database names for origin %s", origin.ascii().data());
        return false;
    }

    return true;
}

bool DatabaseTracker::addDatabase(const String& origin, const String& name, const String& path)
{
    if (!m_database.isOpen())
        return false;

    SQLiteStatement statement(m_database, "INSERT INTO Databases (origin, name, path) VALUES (?, ?, ?);");

    if (statement.prepare() != SQLResultOk)
        return false;

    statement.bindText16(1, origin, true);
    statement.bindText16(2, name, true);
    statement.bindText16(3, path, true);

    if (!statement.executeCommand()) {
        LOG_ERROR("Failed to add database %s to origin %s: %s\n", name.ascii().data(), origin.ascii().data(), statement.lastErrorMsg());
        return false;
    }

    populateOrigins();
    m_origins->add(origin);
    return true;
}

void DatabaseTracker::deleteAllDatabases()
{
    notImplemented();
}

void DatabaseTracker::deleteAllDatabasesForOrigin(const String& origin)
{
    notImplemented();
}

void DatabaseTracker::deleteDatabase(const String& origin, const String& name)
{
    notImplemented();
}

} // namespace WebCore
