/* vim: ts=2 sw=2 expandtab
 * BEGIN FLOCK GPL
 *
 * Copyright Flock Inc. 2005-2009
 * http://flock.com
 *
 * This file may be used under the terms of the
 * GNU General Public License Version 2 or later (the "GPL"),
 * http://www.gnu.org/licenses/gpl.html
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * END FLOCK GPL
 */

#include "nsIFileStreams.h"
#include "nsIFile.h"
#include "nsIRDFPurgeableDataSource.h"
#include "nsIInputStream.h"
#include "nsIRDFContainerUtils.h"
#include "nsIRDFNode.h"
#include "nsIRDFRemoteDataSource.h"
#include "nsIRDFService.h"
#include "nsIRDFXMLParser.h"
#include "nsIStreamListener.h"
#include "nsIFileURL.h"
#include "nsNetUtil.h"
#include "nsRDFCID.h"
#include "nsCOMArray.h"
#include "rdf.h"
#include "mozIStorageService.h"
#include "mozIStorageConnection.h"
#include "mozIStorageStatement.h"
#include "mozStorageHelper.h"
#include "mozStorageCID.h"
#include "rdfIDataSource.h"
#include "flockIRDFSQLDataSource.h"
#include "nsITimelineService.h"

//----------------------------------------------------------------------

#define RDF_SQL_SCHEMA_VERSION 1

static NS_DEFINE_CID(kRDFInMemoryDataSourceCID, NS_RDFINMEMORYDATASOURCE_CID);
static NS_DEFINE_CID(kRDFServiceCID,            NS_RDFSERVICE_CID);
static NS_DEFINE_CID(kRDFContainerUtilsCID,     NS_RDFCONTAINERUTILS_CID);

#ifdef PR_LOGGING
static PRLogModuleInfo* gLog;
#endif


//----------------------------------------------------------------------
//
// flockRDFSQLDataSourceImpl
//

class flockRDFSQLDataSourceImpl : public nsIRDFDataSource,
                                  public nsIRDFRemoteDataSource,
                                  public rdfIDataSource,
                                  public flockIRDFSQLDataSource
{
protected:
    enum ObjectType {
        eObjectType_Resource, //0
        eObjectType_Literal,  //1
        eObjectType_Int,      //2
        eObjectType_Date,     //3
        eObjectType_Blob      //4
    };

    nsCOMPtr<mozIStorageConnection> mDBConn;
    nsCOMPtr<mozIStorageStatement> mGetAllTriplesBySubject;
    nsCOMPtr<mozIStorageStatement> mGetResourceId;
    nsCOMPtr<mozIStorageStatement> mAssertResource;
    nsCOMPtr<mozIStorageStatement> mAssertTriple;
    nsCOMPtr<mozIStorageStatement> mUnAssertResource;
    nsCOMPtr<mozIStorageStatement> mUnAssertTriple;
    nsCOMPtr<mozIStorageStatement> mChange;
    nsCOMPtr<mozIStorageStatement> mMove;
    nsIRDFDataSource*   mInner;         // OWNER
    nsCOMPtr<nsIURI>    mURL;
    nsCOMPtr<nsIStreamListener> mListener; // to load and convert the old XML/RDF file
    PRInt32     mUpdateBatchNest;

    // pseudo-constants
    static PRInt32 gRefCnt;
    static nsIRDFService* gRDFService;
    static nsIRDFContainerUtils* gRDFContainerUtils;
    static nsIRDFResource* kRDF_nextVal;

    nsresult Init();
    flockRDFSQLDataSourceImpl(void);
    virtual ~flockRDFSQLDataSourceImpl(void);

    friend nsresult
    NS_NewRDFSQLDataSource(nsIRDFDataSource** aResult);

    inline PRBool IsConvertingXml() {
        return mListener != nsnull;
    }

public:
    // nsISupports
    NS_DECL_ISUPPORTS

    // nsIRDFDataSource
    NS_IMETHOD GetURI(char* *uri);

    NS_IMETHOD GetSource(nsIRDFResource* property,
                         nsIRDFNode* target,
                         PRBool tv,
                         nsIRDFResource** source) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->GetSource(property, target, tv, source);
    }

    NS_IMETHOD GetSources(nsIRDFResource* property,
                          nsIRDFNode* target,
                          PRBool tv,
                          nsISimpleEnumerator** sources) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->GetSources(property, target, tv, sources);
    }

    NS_IMETHOD GetTarget(nsIRDFResource* source,
                         nsIRDFResource* property,
                         PRBool tv,
                         nsIRDFNode** target) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->GetTarget(source, property, tv, target);
    }

    NS_IMETHOD GetTargets(nsIRDFResource* source,
                          nsIRDFResource* property,
                          PRBool tv,
                          nsISimpleEnumerator** targets) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->GetTargets(source, property, tv, targets);
    }

    NS_IMETHOD Assert(nsIRDFResource* aSource,
                      nsIRDFResource* aProperty,
                      nsIRDFNode* aTarget,
                      PRBool tv);

    NS_IMETHOD Unassert(nsIRDFResource* source,
                        nsIRDFResource* property,
                        nsIRDFNode* target);

    NS_IMETHOD Change(nsIRDFResource* aSource,
                      nsIRDFResource* aProperty,
                      nsIRDFNode* aOldTarget,
                      nsIRDFNode* aNewTarget);

    NS_IMETHOD Move(nsIRDFResource* aOldSource,
                    nsIRDFResource* aNewSource,
                    nsIRDFResource* aProperty,
                    nsIRDFNode* aTarget);

    NS_IMETHOD HasAssertion(nsIRDFResource* source,
                            nsIRDFResource* property,
                            nsIRDFNode* target,
                            PRBool tv,
                            PRBool* hasAssertion) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->HasAssertion(source, property, target, tv, hasAssertion);
    }

    NS_IMETHOD AddObserver(nsIRDFObserver* aObserver) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->AddObserver(aObserver);
    }

    NS_IMETHOD RemoveObserver(nsIRDFObserver* aObserver) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->RemoveObserver(aObserver);
    }

    NS_IMETHOD HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, PRBool *_retval) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->HasArcIn(aNode, aArc, _retval);
    }

    NS_IMETHOD HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, PRBool *_retval) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->HasArcOut(aSource, aArc, _retval);
    }

    NS_IMETHOD ArcLabelsIn(nsIRDFNode* node,
                           nsISimpleEnumerator** labels) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->ArcLabelsIn(node, labels);
    }

    NS_IMETHOD ArcLabelsOut(nsIRDFResource* source,
                            nsISimpleEnumerator** labels) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->ArcLabelsOut(source, labels);
    }

    NS_IMETHOD GetAllResources(nsISimpleEnumerator** aResult) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->GetAllResources(aResult);
    }

    NS_IMETHOD GetAllCmds(nsIRDFResource* source,
                          nsISimpleEnumerator/*<nsIRDFResource>*/** commands) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->GetAllCmds(source, commands);
    }

    NS_IMETHOD IsCommandEnabled(nsISupportsArray/*<nsIRDFResource>*/* aSources,
                                nsIRDFResource*   aCommand,
                                nsISupportsArray/*<nsIRDFResource>*/* aArguments,
                                PRBool* aResult) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->IsCommandEnabled(aSources, aCommand, aArguments, aResult);
    }

    NS_IMETHOD DoCommand(nsISupportsArray/*<nsIRDFResource>*/* aSources,
                         nsIRDFResource*   aCommand,
                         nsISupportsArray/*<nsIRDFResource>*/* aArguments) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        return mInner->DoCommand(aSources, aCommand, aArguments);
    }

    NS_IMETHOD BeginUpdateBatch() {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        NS_ASSERTION(mDBConn, "SQL database not initialized");
        if (mUpdateBatchNest++ == 0) {
            // Uses the current transaction if one exists
            mDBConn->BeginTransaction();
        }
        return mInner->BeginUpdateBatch();
    }

    NS_IMETHOD EndUpdateBatch() {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        NS_ASSERTION(mDBConn, "SQL database not initialized");
        if (--mUpdateBatchNest == 0) {
            mDBConn->CommitTransaction();
        }
        return mInner->EndUpdateBatch();
    }

    NS_IMETHOD VisitAllSubjects(rdfITripleVisitor *aVisitor) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        nsresult rv;
        nsCOMPtr<rdfIDataSource> rdfds = do_QueryInterface(mInner, &rv);
        if (NS_FAILED(rv)) return rv;
        return rdfds->VisitAllSubjects(aVisitor);
    }

    NS_IMETHOD VisitAllTriples(rdfITripleVisitor *aVisitor) {
        if (!mInner) {
            return NS_ERROR_NOT_AVAILABLE;
        }
        nsresult rv;
        nsCOMPtr<rdfIDataSource> rdfds = do_QueryInterface(mInner, &rv);
        if (NS_FAILED(rv)) return rv;
        return rdfds->VisitAllTriples(aVisitor);
    }

    // nsIRDFRemoteDataSource interface
    NS_DECL_NSIRDFREMOTEDATASOURCE

    // flockIRDFSQLDataSource interface
    NS_DECL_FLOCKIRDFSQLDATASOURCE

    // Implementation methods

protected:
    nsresult
    BlockingParse(nsIURI* aURI);

    nsresult
    GetTarget(nsIRDFNode** target);

    nsresult
    SetNextValue(nsIRDFResource *aSubject, PRInt32 maxIndex);

    nsresult
    LoadAllTriples();

    nsresult
    BindResource(nsIRDFResource* res,
                 nsCOMPtr<mozIStorageStatement>& stmt,
                 PRUint32 aParamIndex);

    nsresult
    BindObjectParameter(nsIRDFNode* aTarget,
                        nsCOMPtr<mozIStorageStatement>& stmt,
                        PRUint32 aParamObjectIndex,
                        PRUint32 aParamObjectTypeIndex);

    nsresult
    GetResourceId(nsIRDFResource* res, PRInt64* resourceId, nsACString& uri);

    nsresult
    AddResource(nsIRDFResource* res, PRInt64* resourceId);
};

PRInt32         flockRDFSQLDataSourceImpl::gRefCnt = 0;
nsIRDFService*  flockRDFSQLDataSourceImpl::gRDFService;
nsIRDFContainerUtils* flockRDFSQLDataSourceImpl::gRDFContainerUtils;
nsIRDFResource* flockRDFSQLDataSourceImpl::kRDF_nextVal;

//----------------------------------------------------------------------

nsresult
NS_NewRDFSQLDataSource(nsIRDFDataSource** aResult)
{
    NS_PRECONDITION(aResult != nsnull, "null ptr");
    if (! aResult)
        return NS_ERROR_NULL_POINTER;

    flockRDFSQLDataSourceImpl* datasource = new flockRDFSQLDataSourceImpl();
    if (! datasource)
        return NS_ERROR_OUT_OF_MEMORY;

    nsresult rv;
    rv = datasource->Init();

    if (NS_FAILED(rv)) {
        delete datasource;
        return rv;
    }

    NS_ADDREF(datasource);
    *aResult = datasource;
    return NS_OK;
}


flockRDFSQLDataSourceImpl::flockRDFSQLDataSourceImpl(void)
    : mDBConn(nsnull),
      mInner(nsnull),
      mURL(nsnull),
      mUpdateBatchNest(0)
{
#ifdef PR_LOGGING
    if (! gLog)
        gLog = PR_NewLogModule("nsRDFSQLDataSource");
#endif
}


nsresult
flockRDFSQLDataSourceImpl::Init()
{
    nsresult rv;
    rv = CallCreateInstance(kRDFInMemoryDataSourceCID, &mInner);
    if (NS_FAILED(rv)) return rv;

    if (gRefCnt++ == 0) {
        rv = CallGetService(kRDFServiceCID, &gRDFService);

        NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service");
        if (NS_FAILED(rv)) return rv;

        rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils);
        if (NS_FAILED(rv)) {
            NS_ERROR("unable to get RDF container utils service");
            return rv;
        }
        gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"),
                                 &kRDF_nextVal);
    }

    return NS_OK;
}


flockRDFSQLDataSourceImpl::~flockRDFSQLDataSourceImpl(void)
{
    (void)Shutdown();

    if (--gRefCnt == 0) {
        NS_IF_RELEASE(gRDFService);
        NS_IF_RELEASE(gRDFContainerUtils);
        NS_IF_RELEASE(kRDF_nextVal);
    }
}


NS_IMPL_ISUPPORTS4(flockRDFSQLDataSourceImpl,
                   nsIRDFDataSource,
                   nsIRDFRemoteDataSource,
                   rdfIDataSource,
                   flockIRDFSQLDataSource)



// Return a 0 resourceId if the resource does not exist in the resource table
// yet.
nsresult
flockRDFSQLDataSourceImpl::GetResourceId(nsIRDFResource* res,
                                         PRInt64* resourceId,
                                         nsACString& uri)
{
    nsresult rv;
    res->GetValueUTF8(uri);
    mGetResourceId->BindUTF8StringParameter(0, uri);
    PRBool exists = PR_FALSE;
    rv = mGetResourceId->ExecuteStep(&exists);
    NS_ENSURE_SUCCESS(rv, rv);

    if (exists){
        rv = mGetResourceId->GetInt64(0, resourceId);
        NS_ENSURE_SUCCESS(rv, rv);
    }
    else *resourceId = 0;
    mGetResourceId->Reset();
    return rv;
}

// Checks that the resource does not exist in the resource table before
// creating it.
nsresult
flockRDFSQLDataSourceImpl::AddResource(nsIRDFResource* res, PRInt64* resourceId)
{
    NS_ASSERTION(mDBConn, "SQL database not initialized");
    nsCAutoString uri;
    nsresult rv = GetResourceId(res, resourceId, uri);
    if (!*resourceId) {
        NS_ENSURE_SUCCESS(rv, rv);
        mAssertResource->BindUTF8StringParameter(0, uri);
        rv = mAssertResource->Execute();
        NS_ENSURE_SUCCESS(rv, rv);
        rv = mDBConn->GetLastInsertRowID(resourceId);
    }
    return rv;
}

nsresult
flockRDFSQLDataSourceImpl::BindResource(nsIRDFResource* res,
                                        nsCOMPtr<mozIStorageStatement>& stmt,
                                        PRUint32 aParamIndex)
{
    NS_ASSERTION(mDBConn, "SQL database not initialized");
    nsCAutoString uri;
    PRInt64 resourceId;
    nsresult rv = GetResourceId(res, &resourceId, uri);
    NS_ENSURE_SUCCESS(rv, rv);
    if (!resourceId)
        return NS_ERROR_FAILURE;
    return stmt->BindInt64Parameter(aParamIndex, resourceId);
}

nsresult
flockRDFSQLDataSourceImpl::BindObjectParameter(nsIRDFNode* aTarget,
                                               nsCOMPtr<mozIStorageStatement>& stmt,
                                               PRUint32 aParamObjectIndex,
                                               PRUint32 aParamObjectTypeIndex)
{
    NS_ASSERTION(mDBConn, "SQL database not initialized");
    ObjectType objectType = eObjectType_Blob;
    nsCOMPtr<nsIRDFDate> date = do_QueryInterface(aTarget);
    if (date != nsnull) {
        objectType = eObjectType_Date;
        PRTime dateValue;
        date->GetValue(&dateValue);
        stmt->BindInt64Parameter(aParamObjectIndex, dateValue);
    }
    else {
        nsCOMPtr<nsIRDFInt> number = do_QueryInterface(aTarget);
        if (number != nsnull){
            objectType = eObjectType_Int;
            PRInt32 intValue;
            number->GetValue(&intValue);
            stmt->BindInt32Parameter(aParamObjectIndex, intValue);
        }
        else {
            nsCOMPtr<nsIRDFLiteral> literal = do_QueryInterface(aTarget);
            if (literal != nsnull) {
                objectType = eObjectType_Literal;
                const PRUnichar *value;
                literal->GetValueConst(&value);
                NS_ConvertUTF16toUTF8 s(value);
                stmt->BindUTF8StringParameter(aParamObjectIndex, s);
            }
            else {
                nsCOMPtr<nsIRDFResource> resource = do_QueryInterface(aTarget);
                if (resource != nsnull) {
                    // We could store objects that are resources in the resource
                    // table (but it makes the dabase harder to view).
                    objectType = eObjectType_Resource;
                    nsCAutoString uri;
                    resource->GetValueUTF8(uri);
                    stmt->BindUTF8StringParameter(aParamObjectIndex, uri);
                }
            }
        }
    }

    return stmt->BindInt32Parameter(aParamObjectTypeIndex, objectType);
}


//----------------------------------------------------------------------
//
// nsIRDFRemoteDataSource methods
//

NS_IMETHODIMP
flockRDFSQLDataSourceImpl::GetLoaded(PRBool* _result)
{
    *_result = (mDBConn != nsnull);
    return NS_OK;
}

NS_IMETHODIMP
flockRDFSQLDataSourceImpl::Init(const char* uri)
{
    NS_PRECONDITION(mInner != nsnull, "not initialized");
    if (! mInner)
        return NS_ERROR_OUT_OF_MEMORY;

    nsresult rv = NS_NewURI(getter_AddRefs(mURL), uri);
    if (NS_FAILED(rv)) return rv;

    rv = gRDFService->RegisterDataSource(this, PR_FALSE);
    if (NS_FAILED(rv)) return rv;

    return NS_OK;
}

NS_IMETHODIMP
flockRDFSQLDataSourceImpl::Refresh(PRBool aBlocking)
{
    // Init DB service and obtain a connection
    nsresult rv;
    nsCOMPtr<mozIStorageService> storageService = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    // Init DB
    nsCOMPtr<nsIFile> dbFile;
    nsCOMPtr<nsIFileURL> url(do_QueryInterface(mURL));
    rv = url->GetFile(getter_AddRefs(dbFile));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = storageService->OpenDatabase(dbFile, getter_AddRefs(mDBConn));
    NS_ENSURE_SUCCESS(rv, rv);

    PRBool tableExists = PR_FALSE;
    mDBConn->TableExists(NS_LITERAL_CSTRING("triple"), &tableExists);
    if (!tableExists) {
        mozStorageTransaction transaction(mDBConn, PR_FALSE);
        rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE resource (id INTEGER PRIMARY KEY AUTOINCREMENT, uri TEXT NOT NULL);"));
        NS_ENSURE_SUCCESS(rv, rv);
        rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE TABLE triple (subject INTEGER NOT NULL, predicate INTEGER NOT NULL, object BLOB, object_type INTEGER);"));
        NS_ENSURE_SUCCESS(rv, rv);
        rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE UNIQUE INDEX index_resource_uri ON resource (uri);"));
        NS_ENSURE_SUCCESS(rv, rv);
        //rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE UNIQUE INDEX index_triple ON triple (subject, predicate, object);")); // uses too much disk space (+50%)
        rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("CREATE INDEX index_triple ON triple (subject, predicate);")); // cannot be unique
        NS_ENSURE_SUCCESS(rv, rv);
        rv = mDBConn->SetSchemaVersion(RDF_SQL_SCHEMA_VERSION);
        NS_ENSURE_SUCCESS(rv, rv);
        transaction.Commit();
    }

    rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
                                   "PRAGMA synchronous = FULL"));
    NS_ENSURE_SUCCESS(rv, rv);
    rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("SELECT s.uri, p.uri, object, object_type FROM resource s INNER JOIN triple ON s.id = triple.subject INNER JOIN resource p ON p.id = triple.predicate ORDER BY s.id"),
                                getter_AddRefs(mGetAllTriplesBySubject));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("SELECT id FROM resource WHERE uri=?1"),
                                getter_AddRefs(mGetResourceId));
    NS_ENSURE_SUCCESS(rv, rv);
    rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("INSERT INTO resource (uri) VALUES (?1)"),
                                getter_AddRefs(mAssertResource));
    NS_ENSURE_SUCCESS(rv, rv);
    rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("INSERT INTO triple (subject, predicate, object, object_type) VALUES (?1, ?2, ?3, ?4)"),
                                getter_AddRefs(mAssertTriple));
    NS_ENSURE_SUCCESS(rv, rv);
    rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM resource WHERE uri=?1"),
                                getter_AddRefs(mUnAssertResource));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("DELETE FROM triple WHERE subject=?1 AND predicate=?2 AND object=?3 AND object_type=?4"),
                                getter_AddRefs(mUnAssertTriple));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("UPDATE triple SET object=?1, object_type=?2 WHERE subject=?3 AND predicate=?4 AND object=?5 AND object_type=?6"),
                                getter_AddRefs(mChange));
    NS_ENSURE_SUCCESS(rv, rv);

    rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("UPDATE triple SET subject=?1 WHERE subject=?2 AND predicate=?3 AND object=?4 AND object_type=?5"),
                                getter_AddRefs(mMove));
    NS_ENSURE_SUCCESS(rv, rv);

    if (! mURL)
        return NS_ERROR_FAILURE;

#ifdef PR_LOGGING
    nsCAutoString spec;
    if (mURL) {
        mURL->GetSpec(spec);
    }
    PR_LOG(gLog, PR_LOG_NOTICE,
           ("rdfsql[%p] Init(%s)", this, spec.get()));
#endif

    return LoadAllTriples();
}


nsresult
flockRDFSQLDataSourceImpl::LoadAllTriples()
{
    nsresult rv;

    NS_NAMED_LITERAL_CSTRING(kRDF_prefix, RDF_NAMESPACE_URI "_");

    nsIRDFResource *currentSubject = nsnull;
    PRInt32 maxIndex = 0;

    PRBool exists = PR_FALSE;
    while (1) {
        rv = mGetAllTriplesBySubject->ExecuteStep(&exists);
        NS_ENSURE_SUCCESS(rv, rv);
        if (!exists) break;
        nsCAutoString subjectUri, propertyUri;
        rv = mGetAllTriplesBySubject->GetUTF8String(0, subjectUri);
        NS_ENSURE_SUCCESS(rv, rv);
        rv = mGetAllTriplesBySubject->GetUTF8String(1, propertyUri);
        NS_ENSURE_SUCCESS(rv, rv);
        nsIRDFResource *aSubject, *aProperty;
        rv = gRDFService->GetResource(subjectUri, &aSubject);
        rv = gRDFService->GetResource(propertyUri, &aProperty);
        if (currentSubject != aSubject) {
            // Because the triples are sorted by subject, we can be sure that's
            // the end of this one.
            SetNextValue(currentSubject, maxIndex);
            currentSubject = aSubject;
            maxIndex = 0;
        }
        nsIRDFNode* target;
        rv = GetTarget(&target);
        NS_ENSURE_SUCCESS(rv, rv);
        if (StringBeginsWith(propertyUri, kRDF_prefix)) {
          // Do not call AppendElement to handle the case where a container has
          // holes.  Calculate the nextVal.
          PRInt32 aIndex = 0;
          gRDFContainerUtils->OrdinalResourceToIndex(aProperty, &aIndex);
          if (maxIndex < aIndex) {
            maxIndex = aIndex;
          }
        }
        rv = mInner->Assert(aSubject, aProperty, target, PR_TRUE);
        NS_ENSURE_SUCCESS(rv, rv);
    }
    mGetAllTriplesBySubject->Reset();
    // Don't forget the last item that could be a container.
    SetNextValue(currentSubject, maxIndex);
    return rv;
}

// This is called by LoadAllTriples();
// It requires that mGetAllTriplesBySubject is correctly initialized
nsresult
flockRDFSQLDataSourceImpl::GetTarget(nsIRDFNode** target)
{
    PRInt32 objectType;
    nsresult rv = mGetAllTriplesBySubject->GetInt32(3, &objectType);
    NS_ENSURE_SUCCESS(rv, rv);
    switch (objectType) {
        case eObjectType_Literal:
        {
            const PRUnichar *str;
            PRUint32 aLength;
            rv = mGetAllTriplesBySubject->GetSharedString(2, &aLength, &str);
            NS_ENSURE_SUCCESS(rv, rv);
            rv = gRDFService->GetLiteral(str, (nsIRDFLiteral**)target);
        }
        break;
        case eObjectType_Resource:
        {
            nsCAutoString uri;
            rv = mGetAllTriplesBySubject->GetUTF8String(2, uri);
            NS_ENSURE_SUCCESS(rv, rv);
            rv = gRDFService->GetResource(uri, (nsIRDFResource**)target);
        }
        break;
        case eObjectType_Date:
        {
            PRTime aTime;
            rv = mGetAllTriplesBySubject->GetInt64(2, &aTime);
            NS_ENSURE_SUCCESS(rv, rv);
            rv = gRDFService->GetDateLiteral(aTime, (nsIRDFDate**)target);
        }
        break;
        case eObjectType_Int:
        {
            PRInt32 number;
            rv = mGetAllTriplesBySubject->GetInt32(2, &number);
            NS_ENSURE_SUCCESS(rv, rv);
            rv = gRDFService->GetIntLiteral(number, (nsIRDFInt**)target);
        }
        break;
    }
    return rv;
}

nsresult
flockRDFSQLDataSourceImpl::SetNextValue(nsIRDFResource *aSubject,
                                        PRInt32 maxIndex)
{
  if (aSubject != nsnull) {
    PRBool isContainer;
    nsresult rv = gRDFContainerUtils->IsContainer(mInner, aSubject, &isContainer);
    if (NS_FAILED(rv)) return rv;

    if (isContainer) {
      nsAutoString s;
      s.AppendInt(maxIndex+1, 10);

      nsCOMPtr<nsIRDFLiteral> nextVal;
      if (NS_FAILED(rv = gRDFService->GetLiteral(s.get(), getter_AddRefs(nextVal)))) {
        NS_ERROR("unable to get nextVal literal");
        return rv;
      }

      rv = mInner->Assert(aSubject, kRDF_nextVal, nextVal, PR_TRUE);
      if (rv != NS_RDF_ASSERTION_ACCEPTED) {
        NS_ERROR("unable to update nextVal");
        return NS_ERROR_FAILURE;
      }
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
flockRDFSQLDataSourceImpl::Flush()
{
  // This gets called from destructor whether mDBConn is valid or not.
  if (mDBConn) {
    mDBConn->CommitTransaction();
  }
  return NS_OK;
}

NS_IMETHODIMP
flockRDFSQLDataSourceImpl::FlushTo(const char *aURI)
{
  return (NS_ERROR_NOT_IMPLEMENTED);
}


//----------------------------------------------------------------------
//
// nsIRDFDataSource methods
//

NS_IMETHODIMP
flockRDFSQLDataSourceImpl::GetURI(char* *aURI)
{
    *aURI = nsnull;
    if (!mURL) {
        return NS_OK;
    }

    nsCAutoString spec;
    mURL->GetSpec(spec);
    *aURI = ToNewCString(spec);
    if (!*aURI) {
        return NS_ERROR_OUT_OF_MEMORY;
    }

    return NS_OK;
}


NS_IMETHODIMP
flockRDFSQLDataSourceImpl::Assert(nsIRDFResource* aSource,
                                  nsIRDFResource* aProperty,
                                  nsIRDFNode* aTarget,
                                  PRBool aTruthValue)
{
    if (!mInner) {
        return NS_ERROR_NOT_AVAILABLE;
    }

    if (!mDBConn) {
        return NS_ERROR_NOT_INITIALIZED;
    }

    nsresult rv;
    // Do not store nextVal in the db because it's redundant.
    // Also RDFContainerImpl::GetNextValue calls Assert/UnAssert to much (for
    // every insert).
    if (aProperty != kRDF_nextVal) {
        PRBool aResult = PR_FALSE;
        rv = mInner->HasAssertion(aSource, aProperty, aTarget, aTruthValue, &aResult);
        if (!aResult) {
            // Uses the current transaction if one exists.
            mDBConn->BeginTransaction();

            PRInt64 resourceId;
            rv = AddResource(aSource, &resourceId);
            mAssertTriple->BindInt64Parameter (0, resourceId);
            rv = AddResource(aProperty, &resourceId);
            mAssertTriple->BindInt64Parameter (1, resourceId);

            rv = BindObjectParameter(aTarget, mAssertTriple, 2, 3);

            rv = mAssertTriple->Execute();
            NS_ENSURE_SUCCESS(rv, rv);
    /*        if(rv){
                PRInt32 error;
                mDBConn->GetLastError(&error);
                if (error != 19) {
                    NS_ENSURE_SUCCESS(rv, rv);
                }
            }*/
        }
    }

    if (IsConvertingXml()) {
        PRBool hasAssertion = PR_FALSE;

        nsCOMPtr<nsIRDFPurgeableDataSource> gcable = do_QueryInterface(mInner);
        if (gcable) {
            rv = gcable->Mark(aSource, aProperty, aTarget, aTruthValue, &hasAssertion);
            if (NS_FAILED(rv)) return rv;
        }

        rv = NS_RDF_ASSERTION_ACCEPTED;

        if (! hasAssertion) {
            rv = mInner->Assert(aSource, aProperty, aTarget, aTruthValue);

            if (NS_SUCCEEDED(rv) && gcable) {
                // Now mark the new assertion, so it doesn't get
                // removed when we sweep. Ignore rv, because we want
                // to return what mInner->Assert() gave us.
                PRBool didMark;
                (void) gcable->Mark(aSource, aProperty, aTarget, aTruthValue, &didMark);
            }

            if (NS_FAILED(rv)) return rv;
        }
        return rv;
    }
    else {
        return mInner->Assert(aSource, aProperty, aTarget, aTruthValue);
    }
}


NS_IMETHODIMP
flockRDFSQLDataSourceImpl::Unassert(nsIRDFResource* aSource,
                                    nsIRDFResource* aProperty,
                                    nsIRDFNode* aTarget)
{
    if (!mInner) {
        return NS_ERROR_NOT_AVAILABLE;
    }

    if (!mDBConn) {
        return NS_ERROR_NOT_INITIALIZED;
    }

    nsresult rv = mInner->Unassert(aSource, aProperty, aTarget);
    NS_ENSURE_SUCCESS(rv, rv);

/*    rv = BindResource(aSource, mGetTriples, 0);
    rv = BindResource(aProperty, mGetTriples, 1);
    PRBool exists = PR_FALSE;
    rv = mGetTriples->ExecuteStep(&exists);
    mGetTriples->Reset();
    NS_ENSURE_SUCCESS(rv, rv);
    if(!exists){
        mDBConn->CommitTransaction();
        NS_ENSURE_SUCCESS(rv, rv);
    }*/

    // Do not store nextVal in the db because it's redundant.
    // Also RDFContainerImpl::GetNextValue calls Assert/UnAssert to much (for
    // every insert).
    if (aProperty != kRDF_nextVal) {
        // Uses the current transaction if one exists.
        mDBConn->BeginTransaction();

        // Bind params of the WHERE clause.
        rv = BindResource(aSource, mUnAssertTriple, 0);
        rv = BindResource(aProperty, mUnAssertTriple, 1);
        rv = BindObjectParameter(aTarget, mUnAssertTriple, 2, 3);

        // Delete triple
        // note: even uncommitted asserts can be deleted here (SQLite
        // transactions are SERIALIZABLE).
        rv = mUnAssertTriple->Execute();
        NS_ENSURE_SUCCESS(rv, rv);

        // See if the subject resource is still needed
        //nsCAutoString uri;
        //aSource->GetValueUTF8(uri);
        //if (uri != NS_LITERAL_CSTRING("urn:flock:topfeeds")) {
        nsISimpleEnumerator* labels;
        rv = mInner->ArcLabelsOut(aSource,&labels);
        NS_ENSURE_SUCCESS(rv, rv);
        PRBool hasMore;
        rv = labels->HasMoreElements(&hasMore);
        if (!hasMore) {
            nsCAutoString uri;
            aSource->GetValueUTF8(uri);
            mUnAssertResource->BindUTF8StringParameter(0, uri);
            rv = mUnAssertResource->Execute();
            NS_ENSURE_SUCCESS(rv, rv);
        }
    }

    return rv;
}

NS_IMETHODIMP
flockRDFSQLDataSourceImpl::Change(nsIRDFResource* aSource,
                                  nsIRDFResource* aProperty,
                                  nsIRDFNode* aOldTarget,
                                  nsIRDFNode* aNewTarget)
{
    if (!mInner) {
        return NS_ERROR_NOT_AVAILABLE;
    }

    if (!mDBConn) {
        return NS_ERROR_NOT_INITIALIZED;
    }

    nsresult rv;

    // Uses the current transaction if one exists
    mDBConn->BeginTransaction();

    // SET object=?1, object_type=?2
    rv = BindObjectParameter(aNewTarget, mChange, 0, 1);

    // Bind params of the WHERE clause.
    rv = BindResource(aSource, mChange, 2);
    rv = BindResource(aProperty, mChange, 3);
    rv = BindObjectParameter(aOldTarget, mChange, 4, 5);

    // Update triple
    rv = mChange->Execute();
    NS_ENSURE_SUCCESS(rv, rv);

    return mInner->Change(aSource, aProperty, aOldTarget, aNewTarget);
}

NS_IMETHODIMP
flockRDFSQLDataSourceImpl::Move(nsIRDFResource* aOldSource,
                                nsIRDFResource* aNewSource,
                                nsIRDFResource* aProperty,
                                nsIRDFNode* aTarget)
{
    if (!mInner) {
        return NS_ERROR_NOT_AVAILABLE;
    }

    if (!mDBConn) {
        return NS_ERROR_NOT_INITIALIZED;
    }

    nsresult rv;

    // Uses the current transaction if one exists.
    mDBConn->BeginTransaction();

    // SET subject=?1
    rv = BindResource(aNewSource, mMove, 0);

    // Bind params of the WHERE clause.
    rv = BindResource(aOldSource, mMove, 1);
    rv = BindResource(aProperty, mMove, 2);
    rv = BindObjectParameter(aTarget, mMove, 3, 4);

    // Update triple
    rv = mMove->Execute();
    NS_ENSURE_SUCCESS(rv, rv);

    return mInner->Move(aOldSource, aNewSource, aProperty, aTarget);
}


//----------------------------------------------------------------------
//
// flockIRDFSQLDataSource methods
//

NS_IMETHODIMP
flockRDFSQLDataSourceImpl::Shutdown()
{
    nsresult rv;

    // Unregister first so that nobody else tries to get us.
    rv = gRDFService->UnregisterDataSource(this);

    // Now flush contents
    rv = Flush();

    NS_IF_RELEASE(mInner);
    mInner = nsnull;

    // Release all references to mDBConn.
    mGetAllTriplesBySubject = nsnull;
    mGetResourceId = nsnull;
    mAssertResource = nsnull;
    mAssertTriple = nsnull;
    mUnAssertResource = nsnull;
    mUnAssertTriple = nsnull;
    mChange = nsnull;
    mMove = nsnull;

    // This will close the database.
    mDBConn = nsnull;

    return rv;
}

NS_IMETHODIMP
flockRDFSQLDataSourceImpl::ImportRDFXML(nsIURI *aURI)
{
    nsresult rv;

    nsCOMPtr<nsIRDFXMLParser> parser =
        do_CreateInstance("@mozilla.org/rdf/xml-parser;1", &rv);
    NS_ENSURE_SUCCESS(rv, rv);

    rv = parser->ParseAsync(this, aURI, getter_AddRefs(mListener));
    NS_ENSURE_SUCCESS(rv, rv);

    mozStorageTransaction transaction(mDBConn, PR_FALSE);
    NS_TIMELINE_START_TIMER("rdf blocking parse");
    rv = BlockingParse(aURI);
    NS_TIMELINE_STOP_TIMER("rdf blocking parse");
    NS_TIMELINE_MARK_TIMER("rdf blocking parse");
    transaction.Commit();

    // Release the parser
    mListener = nsnull;

    return rv;
}

// Swiped from nsRDFXMLDataSource.cpp for RDF XML import
// it would be nice if we could reuse the logic to read a XML/RDF file
// from RDFXMLDataSourceImpl.

class ProxyStream2 : public nsIInputStream
{
private:
    const char* mBuffer;
    PRUint32    mSize;
    PRUint32    mIndex;

public:
    ProxyStream2(void) : mBuffer(nsnull)
    {
    }

    virtual ~ProxyStream2(void) {
    }

    // nsISupports
    NS_DECL_ISUPPORTS

    // nsIBaseStream
    NS_IMETHOD Close(void) {
        return NS_OK;
    }

    // nsIInputStream
    NS_IMETHOD Available(PRUint32 *aLength) {
        *aLength = mSize - mIndex;
        return NS_OK;
    }

    NS_IMETHOD Read(char* aBuf, PRUint32 aCount, PRUint32 *aReadCount) {
        PRUint32 readCount = PR_MIN(aCount, (mSize-mIndex));

        memcpy(aBuf, mBuffer+mIndex, readCount);
        mIndex += readCount;

        *aReadCount = readCount;

        return NS_OK;
    }

    NS_IMETHOD ReadSegments(nsWriteSegmentFun writer, void * closure, PRUint32 count, PRUint32 *_retval) {
        PRUint32 readCount = PR_MIN(count, (mSize-mIndex));

        *_retval = 0;
        nsresult rv = writer (this, closure, mBuffer+mIndex, mIndex, readCount, _retval);
        if (NS_SUCCEEDED(rv))
            mIndex += *_retval;

        // do not propogate errors returned from writer!
        return NS_OK;
    }

    NS_IMETHOD IsNonBlocking(PRBool *aNonBlocking) {
        *aNonBlocking = PR_TRUE;
        return NS_OK;
    }

    // Implementation
    void SetBuffer(const char* aBuffer, PRUint32 aSize) {
        mBuffer = aBuffer;
        mSize = aSize;
        mIndex = 0;
    }
};

NS_IMPL_ISUPPORTS1(ProxyStream2, nsIInputStream)

nsresult
flockRDFSQLDataSourceImpl::BlockingParse(nsIURI* aURI)
{
    nsresult rv;

    // XXX I really hate the way that we're spoon-feeding this stuff
    // to the parser: it seems like this is something that netlib
    // should be able to do by itself.

    nsCOMPtr<nsIChannel> channel;
    nsCOMPtr<nsIRequest> request;

    // Null LoadGroup ?
    rv = NS_NewChannel(getter_AddRefs(channel), aURI, nsnull);
    if (NS_FAILED(rv)) return rv;
    nsIInputStream* in;
    PRUint32 sourceOffset = 0;
    rv = channel->Open(&in);

    // Report success if the file doesn't exist, but propagate other errors.
    if (rv == NS_ERROR_FILE_NOT_FOUND) return NS_OK;
    if (NS_FAILED(rv)) return rv;

    NS_ASSERTION(in != nsnull, "no input stream");
    if (! in) return NS_ERROR_FAILURE;

    rv = NS_ERROR_OUT_OF_MEMORY;
    ProxyStream2* proxy = new ProxyStream2();
    if (! proxy)
        goto done;

    request = do_QueryInterface(channel);

    mListener->OnStartRequest(request, nsnull);
    while (PR_TRUE) {
        char buf[4096];
        PRUint32 readCount;

        if (NS_FAILED(rv = in->Read(buf, sizeof(buf), &readCount)))
            break; // error

        if (readCount == 0)
            break; // eof

        proxy->SetBuffer(buf, readCount);

        rv = mListener->OnDataAvailable(request, nsnull, proxy, sourceOffset, readCount);
        sourceOffset += readCount;
        if (NS_FAILED(rv))
            break;
    }

    mListener->OnStopRequest(channel, nsnull, rv);

    // don't leak proxy!
    proxy->Close();
    delete proxy;
    proxy = nsnull;

done:
    NS_RELEASE(in);
    return rv;
}
