/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*

  Implementation for the RDF container utils.

 */


#include "nsCOMPtr.h"
#include "nsIServiceManager.h"
#include "nsIRDFContainer.h"
#include "nsIRDFContainerUtils.h"
#include "nsIRDFService.h"
#include "nsRDFCID.h"
#include "nsString.h"
#include "nsXPIDLString.h"
#include "plstr.h"
#include "prprf.h"
#include "rdf.h"
#include "rdfutil.h"

class RDFContainerUtilsImpl : public nsIRDFContainerUtils
{
public:
    // nsISupports interface
    NS_DECL_ISUPPORTS

    // nsIRDFContainerUtils interface
    NS_DECL_NSIRDFCONTAINERUTILS

private:
    friend nsresult NS_NewRDFContainerUtils(nsIRDFContainerUtils** aResult);

    RDFContainerUtilsImpl();
    virtual ~RDFContainerUtilsImpl();

    nsresult MakeContainer(nsIRDFDataSource* aDataSource,
                           nsIRDFResource* aResource,
                           nsIRDFResource* aType,
                           nsIRDFContainer** aResult);

    bool IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType);

    // pseudo constants
    static int32_t gRefCnt;
    static nsIRDFService* gRDFService;
    static nsIRDFResource* kRDF_instanceOf;
    static nsIRDFResource* kRDF_nextVal;
    static nsIRDFResource* kRDF_Bag;
    static nsIRDFResource* kRDF_Seq;
    static nsIRDFResource* kRDF_Alt;
    static nsIRDFLiteral* kOne;
    static const char kRDFNameSpaceURI[];
};

int32_t         RDFContainerUtilsImpl::gRefCnt = 0;
nsIRDFService*  RDFContainerUtilsImpl::gRDFService;
nsIRDFResource* RDFContainerUtilsImpl::kRDF_instanceOf;
nsIRDFResource* RDFContainerUtilsImpl::kRDF_nextVal;
nsIRDFResource* RDFContainerUtilsImpl::kRDF_Bag;
nsIRDFResource* RDFContainerUtilsImpl::kRDF_Seq;
nsIRDFResource* RDFContainerUtilsImpl::kRDF_Alt;
nsIRDFLiteral*  RDFContainerUtilsImpl::kOne;
const char      RDFContainerUtilsImpl::kRDFNameSpaceURI[] = RDF_NAMESPACE_URI;

////////////////////////////////////////////////////////////////////////
// nsISupports interface

NS_IMPL_ISUPPORTS(RDFContainerUtilsImpl, nsIRDFContainerUtils)

////////////////////////////////////////////////////////////////////////
// nsIRDFContainerUtils interface

NS_IMETHODIMP
RDFContainerUtilsImpl::IsOrdinalProperty(nsIRDFResource *aProperty, bool *_retval)
{
    NS_PRECONDITION(aProperty != nullptr, "null ptr");
    if (! aProperty)
        return NS_ERROR_NULL_POINTER;

    nsresult rv;

    const char	*propertyStr;
    rv = aProperty->GetValueConst( &propertyStr );
    if (NS_FAILED(rv)) return rv;

    if (PL_strncmp(propertyStr, kRDFNameSpaceURI, sizeof(kRDFNameSpaceURI) - 1) != 0) {
        *_retval = false;
        return NS_OK;
    }

    const char* s = propertyStr;
    s += sizeof(kRDFNameSpaceURI) - 1;
    if (*s != '_') {
        *_retval = false;
        return NS_OK;
    }

    ++s;
    while (*s) {
        if (*s < '0' || *s > '9') {
            *_retval = false;
            return NS_OK;
        }

        ++s;
    }

    *_retval = true;
    return NS_OK;
}


NS_IMETHODIMP
RDFContainerUtilsImpl::IndexToOrdinalResource(int32_t aIndex, nsIRDFResource **aOrdinal)
{
    NS_PRECONDITION(aIndex > 0, "illegal value");
    if (aIndex <= 0)
        return NS_ERROR_ILLEGAL_VALUE;

    nsAutoCString uri(kRDFNameSpaceURI);
    uri.Append('_');
    uri.AppendInt(aIndex);
    
    nsresult rv = gRDFService->GetResource(uri, aOrdinal);
    NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get ordinal resource");
    if (NS_FAILED(rv)) return rv;
    
    return NS_OK;
}


NS_IMETHODIMP
RDFContainerUtilsImpl::OrdinalResourceToIndex(nsIRDFResource *aOrdinal, int32_t *aIndex)
{
    NS_PRECONDITION(aOrdinal != nullptr, "null ptr");
    if (! aOrdinal)
        return NS_ERROR_NULL_POINTER;

    const char	*ordinalStr;
    if (NS_FAILED(aOrdinal->GetValueConst( &ordinalStr )))
        return NS_ERROR_FAILURE;

    const char* s = ordinalStr;
    if (PL_strncmp(s, kRDFNameSpaceURI, sizeof(kRDFNameSpaceURI) - 1) != 0) {
        NS_ERROR("not an ordinal");
        return NS_ERROR_UNEXPECTED;
    }

    s += sizeof(kRDFNameSpaceURI) - 1;
    if (*s != '_') {
        NS_ERROR("not an ordinal");
        return NS_ERROR_UNEXPECTED;
    }

    int32_t idx = 0;

    ++s;
    while (*s) {
        if (*s < '0' || *s > '9') {
            NS_ERROR("not an ordinal");
            return NS_ERROR_UNEXPECTED;
        }

        idx *= 10;
        idx += (*s - '0');

        ++s;
    }

    *aIndex = idx;
    return NS_OK;
}

NS_IMETHODIMP
RDFContainerUtilsImpl::IsContainer(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, bool *_retval)
{
    NS_PRECONDITION(aDataSource != nullptr, "null ptr");
    if (! aDataSource)
        return NS_ERROR_NULL_POINTER;

    NS_PRECONDITION(aResource != nullptr, "null ptr");
    if (! aResource)
        return NS_ERROR_NULL_POINTER;

    NS_PRECONDITION(_retval != nullptr, "null ptr");
    if (! _retval)
        return NS_ERROR_NULL_POINTER;

    if (IsA(aDataSource, aResource, kRDF_Seq) ||
        IsA(aDataSource, aResource, kRDF_Bag) ||
        IsA(aDataSource, aResource, kRDF_Alt)) {
        *_retval = true;
    }
    else {
        *_retval = false;
    }
    return NS_OK;
}


NS_IMETHODIMP
RDFContainerUtilsImpl::IsEmpty(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, bool* _retval)
{
    if (! aDataSource)
        return NS_ERROR_NULL_POINTER;

    nsresult rv;

    // By default, say that we're an empty container. Even if we're not
    // really even a container.
    *_retval = true;

    nsCOMPtr<nsIRDFNode> nextValNode;
    rv = aDataSource->GetTarget(aResource, kRDF_nextVal, true, getter_AddRefs(nextValNode));
    if (NS_FAILED(rv)) return rv;

    if (rv == NS_RDF_NO_VALUE)
        return NS_OK;

    nsCOMPtr<nsIRDFLiteral> nextValLiteral;
    rv = nextValNode->QueryInterface(NS_GET_IID(nsIRDFLiteral), getter_AddRefs(nextValLiteral));
    if (NS_FAILED(rv)) return rv;

    if (nextValLiteral.get() != kOne)
        *_retval = false;

    return NS_OK;
}


NS_IMETHODIMP
RDFContainerUtilsImpl::IsBag(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, bool *_retval)
{
    NS_PRECONDITION(aDataSource != nullptr, "null ptr");
    if (! aDataSource)
        return NS_ERROR_NULL_POINTER;

    NS_PRECONDITION(aResource != nullptr, "null ptr");
    if (! aResource)
        return NS_ERROR_NULL_POINTER;

    NS_PRECONDITION(_retval != nullptr, "null ptr");
    if (! _retval)
        return NS_ERROR_NULL_POINTER;

    *_retval = IsA(aDataSource, aResource, kRDF_Bag);
    return NS_OK;
}


NS_IMETHODIMP
RDFContainerUtilsImpl::IsSeq(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, bool *_retval)
{
    NS_PRECONDITION(aDataSource != nullptr, "null ptr");
    if (! aDataSource)
        return NS_ERROR_NULL_POINTER;

    NS_PRECONDITION(aResource != nullptr, "null ptr");
    if (! aResource)
        return NS_ERROR_NULL_POINTER;

    NS_PRECONDITION(_retval != nullptr, "null ptr");
    if (! _retval)
        return NS_ERROR_NULL_POINTER;

    *_retval = IsA(aDataSource, aResource, kRDF_Seq);
    return NS_OK;
}


NS_IMETHODIMP
RDFContainerUtilsImpl::IsAlt(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, bool *_retval)
{
    NS_PRECONDITION(aDataSource != nullptr, "null ptr");
    if (! aDataSource)
        return NS_ERROR_NULL_POINTER;

    NS_PRECONDITION(aResource != nullptr, "null ptr");
    if (! aResource)
        return NS_ERROR_NULL_POINTER;

    NS_PRECONDITION(_retval != nullptr, "null ptr");
    if (! _retval)
        return NS_ERROR_NULL_POINTER;

    *_retval = IsA(aDataSource, aResource, kRDF_Alt);
    return NS_OK;
}


NS_IMETHODIMP
RDFContainerUtilsImpl::MakeBag(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, nsIRDFContainer **_retval)
{
    return MakeContainer(aDataSource, aResource, kRDF_Bag, _retval);
}


NS_IMETHODIMP
RDFContainerUtilsImpl::MakeSeq(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, nsIRDFContainer **_retval)
{
    return MakeContainer(aDataSource, aResource, kRDF_Seq, _retval);
}


NS_IMETHODIMP
RDFContainerUtilsImpl::MakeAlt(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, nsIRDFContainer **_retval)
{
    return MakeContainer(aDataSource, aResource, kRDF_Alt, _retval);
}



////////////////////////////////////////////////////////////////////////


RDFContainerUtilsImpl::RDFContainerUtilsImpl()
{
    if (gRefCnt++ == 0) {
        nsresult rv;

        NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID);
        rv = CallGetService(kRDFServiceCID, &gRDFService);

        NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service");
        if (NS_SUCCEEDED(rv)) {
            gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"),
                                     &kRDF_instanceOf);
            gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"),
                                     &kRDF_nextVal);
            gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Bag"),
                                     &kRDF_Bag);
            gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Seq"),
                                     &kRDF_Seq);
            gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Alt"),
                                     &kRDF_Alt);
            gRDFService->GetLiteral(u"1", &kOne);
        }
    }
}


RDFContainerUtilsImpl::~RDFContainerUtilsImpl()
{
#ifdef DEBUG_REFS
    --gInstanceCount;
    fprintf(stdout, "%d - RDF: RDFContainerUtilsImpl\n", gInstanceCount);
#endif

    if (--gRefCnt == 0) {
        NS_IF_RELEASE(gRDFService);
        NS_IF_RELEASE(kRDF_instanceOf);
        NS_IF_RELEASE(kRDF_nextVal);
        NS_IF_RELEASE(kRDF_Bag);
        NS_IF_RELEASE(kRDF_Seq);
        NS_IF_RELEASE(kRDF_Alt);
        NS_IF_RELEASE(kOne);
    }
}



nsresult
NS_NewRDFContainerUtils(nsIRDFContainerUtils** aResult)
{
    NS_PRECONDITION(aResult != nullptr, "null ptr");
    if (! aResult)
        return NS_ERROR_NULL_POINTER;

    RDFContainerUtilsImpl* result =
        new RDFContainerUtilsImpl();

    if (! result)
        return NS_ERROR_OUT_OF_MEMORY;

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


nsresult
RDFContainerUtilsImpl::MakeContainer(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType, nsIRDFContainer** aResult)
{
    NS_PRECONDITION(aDataSource != nullptr, "null ptr");
    if (! aDataSource)	return NS_ERROR_NULL_POINTER;

    NS_PRECONDITION(aResource != nullptr, "null ptr");
    if (! aResource)	return NS_ERROR_NULL_POINTER;

    NS_PRECONDITION(aType != nullptr, "null ptr");
    if (! aType)	return NS_ERROR_NULL_POINTER;

    if (aResult)	*aResult = nullptr;

    nsresult rv;

    // Check to see if somebody has already turned it into a container; if so
    // don't try to do it again.
    bool isContainer;
    rv = IsContainer(aDataSource, aResource, &isContainer);
    if (NS_FAILED(rv)) return rv;

    if (!isContainer)
    {
	rv = aDataSource->Assert(aResource, kRDF_instanceOf, aType, true);
	if (NS_FAILED(rv)) return rv;

	rv = aDataSource->Assert(aResource, kRDF_nextVal, kOne, true);
	if (NS_FAILED(rv)) return rv;
    }

    if (aResult) {
        rv = NS_NewRDFContainer(aResult);
        if (NS_FAILED(rv)) return rv;

        rv = (*aResult)->Init(aDataSource, aResource);
        if (NS_FAILED(rv)) return rv;
    }

    return NS_OK;
}

bool
RDFContainerUtilsImpl::IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType)
{
    if (!aDataSource || !aResource || !aType) {
        NS_WARNING("Unexpected null argument");
        return false;
    }

    nsresult rv;

    bool result;
    rv = aDataSource->HasAssertion(aResource, kRDF_instanceOf, aType, true, &result);
    if (NS_FAILED(rv))
      return false;

    return result;
}

NS_IMETHODIMP
RDFContainerUtilsImpl::IndexOf(nsIRDFDataSource* aDataSource, nsIRDFResource* aContainer, nsIRDFNode* aElement, int32_t* aIndex)
{
    if (!aDataSource || !aContainer)
        return NS_ERROR_NULL_POINTER;

    // Assume we can't find it.
    *aIndex = -1;

    // If the resource is null, bail quietly
    if (! aElement)
      return NS_OK;

    // We'll assume that fan-out is much higher than fan-in, so grovel
    // through the inbound arcs, look for an ordinal resource, and
    // decode it.
    nsCOMPtr<nsISimpleEnumerator> arcsIn;
    aDataSource->ArcLabelsIn(aElement, getter_AddRefs(arcsIn));
    if (! arcsIn)
        return NS_OK;

    while (1) {
        bool hasMoreArcs = false;
        arcsIn->HasMoreElements(&hasMoreArcs);
        if (! hasMoreArcs)
            break;

        nsCOMPtr<nsISupports> isupports;
        arcsIn->GetNext(getter_AddRefs(isupports));
        if (! isupports)
            break;

        nsCOMPtr<nsIRDFResource> property =
            do_QueryInterface(isupports);

        if (! property)
            continue;

        bool isOrdinal;
        IsOrdinalProperty(property, &isOrdinal);
        if (! isOrdinal)
            continue;

        nsCOMPtr<nsISimpleEnumerator> sources;
        aDataSource->GetSources(property, aElement, true, getter_AddRefs(sources));
        if (! sources)
            continue;

        while (1) {
            bool hasMoreSources = false;
            sources->HasMoreElements(&hasMoreSources);
            if (! hasMoreSources)
                break;

            nsCOMPtr<nsISupports> isupports2;
            sources->GetNext(getter_AddRefs(isupports2));
            if (! isupports2)
                break;

            nsCOMPtr<nsIRDFResource> source =
                do_QueryInterface(isupports2);

            if (source == aContainer)
                // Found it.
                return OrdinalResourceToIndex(property, aIndex);
        }
    }

    return NS_OK;
}