/* 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 "nsServiceManagerUtils.h"
#include "nsComponentManagerUtils.h"

#include "nsIRDFService.h"
#include "nsIRDFContainerUtils.h"

#include "flockIRDFContainer.h"
#include "flockRDFSourceUtils.h"

#include "nsStringAPI.h"

#define NS_RDFSERVICE_CONTRACTID "@mozilla.org/rdf/rdf-service;1"
#define FL_ENSURE(x)                                         \
    PR_BEGIN_MACRO                                           \
    nsresult rv = x;                                         \
    if (NS_FAILED(rv)) {                                     \
       NS_WARNING("FL_ENSURE(" #x ") failed");               \
       return rv;                                            \
    }                                                        \
    PR_END_MACRO


NS_IMPL_ISUPPORTS1(flockRDFSourceUtils, flockIRDFSourceUtils)

flockRDFSourceUtils::flockRDFSourceUtils() {
  mRDFService = do_GetService(NS_RDFSERVICE_CONTRACTID);
}

flockRDFSourceUtils::~flockRDFSourceUtils() {
}

NS_IMETHODIMP
flockRDFSourceUtils::Init(nsIRDFDataSource *aDataSource, nsIRDFResource *aSource) {
  mDS = aDataSource;
  mSource = aSource;
  return NS_OK;
}

static nsISupports* GetNext(nsISimpleEnumerator *aEnum) {
  nsCOMPtr<nsISupports> retval = nsnull;
  aEnum->GetNext(getter_AddRefs(retval));
  return retval;
}

NS_IMETHODIMP
flockRDFSourceUtils::GetParent(nsIRDFResource **_retval)
{
  nsCOMPtr<nsIRDFContainerUtils> rdfc =
    do_GetService("@mozilla.org/rdf/container-utils;1");

  nsCOMPtr<nsISimpleEnumerator> properties;
  FL_ENSURE(mDS->ArcLabelsIn(mSource, getter_AddRefs(properties)));

  while (PR_TRUE) {
    nsCOMPtr<nsIRDFResource> prop = do_QueryInterface(GetNext(properties));
    if (!prop) {
      _retval = nsnull;
      break;
    }
    PRBool isOrdinal = PR_FALSE;
    rdfc->IsOrdinalProperty(prop, &isOrdinal);
    if (isOrdinal) {
      FL_ENSURE(mDS->GetSource(prop, mSource, PR_TRUE, _retval));
      break;
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
flockRDFSourceUtils::DeleteAllTargets(nsIRDFResource *aProperty) {
  nsCOMPtr<nsISimpleEnumerator> targets;
  FL_ENSURE(mDS->GetTargets(mSource, aProperty,
                                PR_TRUE, getter_AddRefs(targets)));
  while (PR_TRUE) {
    nsCOMPtr<nsIRDFNode> target = do_QueryInterface(GetNext(targets));
    if (!target)
      break;
    FL_ENSURE(mDS->Unassert(mSource, aProperty, target));
  }
  return NS_OK;
}

NS_IMETHODIMP
flockRDFSourceUtils::EmptyContainer() {
  nsCOMPtr<flockIRDFContainer> container =
    do_CreateInstance("@mozilla.org/rdf/container;1");
  nsresult rv = container->Init(mDS, mSource);
  if (!NS_FAILED(rv)) {
    nsCOMPtr<nsISimpleEnumerator> children;
    FL_ENSURE(container->GetElementsBackwards(getter_AddRefs(children)));
    while (PR_TRUE) {
      nsCOMPtr<nsIRDFNode> child = do_QueryInterface(GetNext(children));
      if (!child)
        break;
      FL_ENSURE(container->RemoveElement(child, PR_FALSE));
    }
  }
  return NS_OK;
}

NS_IMETHODIMP
flockRDFSourceUtils::Destroy() {
  EmptyContainer();

  // remove the type first, so other coop users can't get at the object anymore
  SetTarget(NS_LITERAL_CSTRING("http://flock.com/rdf#CoopType"), nsnull);

  // remove from parent(s)
  nsCOMPtr<nsISimpleEnumerator> properties;
  FL_ENSURE(mDS->ArcLabelsIn( mSource, getter_AddRefs(properties)));
  while (PR_TRUE) {
    nsCOMPtr<nsIRDFResource> prop = do_QueryInterface(GetNext(properties));
    if (!prop)
      break;
    nsCOMPtr<nsISimpleEnumerator> parents;
    FL_ENSURE(mDS->GetSources( prop, mSource, PR_TRUE, getter_AddRefs(parents)));
    while (PR_TRUE) {
      nsCOMPtr<nsIRDFResource> parent = do_QueryInterface(GetNext(parents));
      if (!parent)
        break;
      FL_ENSURE(mDS->Unassert(parent, prop, mSource));
    }
  }

  // remove all properties
  FL_ENSURE(mDS->ArcLabelsOut( mSource, getter_AddRefs(properties)));
  while (PR_TRUE) {
    nsCOMPtr<nsIRDFResource> prop = do_QueryInterface(GetNext(properties));
    if (!prop)
      break;
    FL_ENSURE(DeleteAllTargets(prop));
  }
  return NS_OK;
}

NS_IMETHODIMP
flockRDFSourceUtils::GetDateTarget(const nsACString & aProperty, PRTime *_retval)
{
  nsCOMPtr<nsIRDFNode> target;
  FL_ENSURE(GetTarget(aProperty, getter_AddRefs(target)));
  return DateValue(target, _retval);
}

NS_IMETHODIMP
flockRDFSourceUtils::GetTarget(const nsACString & aProperty, nsIRDFNode **_retval)
{
  nsCOMPtr<nsIRDFResource> property;
  FL_ENSURE(mRDFService->GetResource(aProperty, getter_AddRefs(property)));

  return mDS->GetTarget(mSource, property, PR_TRUE, _retval);
}

NS_IMETHODIMP
flockRDFSourceUtils::DateValue(nsIRDFNode *aNode, PRTime *_retval)
{
  if (aNode) {
    nsCOMPtr<nsIRDFDate> date(do_QueryInterface(aNode));
    NS_ENSURE_TRUE(date != nsnull, NS_ERROR_INVALID_ARG);
    return date->GetValue(_retval);
  } else {
    *_retval = 0;
    return NS_OK;
  }
}

NS_IMETHODIMP
flockRDFSourceUtils::SetDateTarget(const nsACString &aProperty, PRTime aValue)
{
  nsCOMPtr<nsIRDFDate> date;
  FL_ENSURE(mRDFService->GetDateLiteral(aValue, getter_AddRefs(date)));
  return SetTarget(aProperty, date);
}

NS_IMETHODIMP
flockRDFSourceUtils::GetLitTarget(const nsACString &aProperty, nsAString &_retval)
{
  nsCOMPtr<nsIRDFNode> target;
  FL_ENSURE(GetTarget(aProperty, getter_AddRefs(target)));
  return LitValue(target, _retval);
}

NS_IMETHODIMP
flockRDFSourceUtils::LitValue(nsIRDFNode *aNode, nsAString &_retval)
{
  nsresult rv = NS_OK;
  if (aNode) {
    nsCOMPtr<nsIRDFLiteral> lit(do_QueryInterface(aNode));
    NS_ENSURE_TRUE(lit != nsnull, NS_ERROR_INVALID_ARG);
    const PRUnichar *s;
    FL_ENSURE(lit->GetValueConst(&s));
    _retval = s;
  }
  return rv;
}

NS_IMETHODIMP
flockRDFSourceUtils::SetLitTarget(const nsACString &aProperty,
                             const nsAString &aValue)
{
  nsCOMPtr<nsIRDFLiteral> lit;
  FL_ENSURE(mRDFService->GetLiteral(PromiseFlatString(aValue).get(),
                                           getter_AddRefs(lit)));
  return SetTarget(aProperty, lit);
}

NS_IMETHODIMP
flockRDFSourceUtils::SetTarget(const nsACString &aProperty, nsIRDFNode *aTarget)
{
  nsCOMPtr<nsIRDFResource> property;
  FL_ENSURE(mRDFService->GetResource(aProperty, getter_AddRefs(property)));

  nsCOMPtr<nsIRDFNode> old_target;
  FL_ENSURE(mDS->GetTarget(mSource, property, PR_TRUE, 
                                  getter_AddRefs(old_target)));

  // does this property already exist?
  if (old_target) {
    if (!aTarget) {
      return mDS->Unassert(mSource, property, old_target);
    } else {
      PRBool equals;
      FL_ENSURE(aTarget->EqualsNode(old_target, &equals));
      if (equals) {
        return NS_OK;
      }
      return mDS->Change(mSource, property, old_target, aTarget);
    }
  }
  if (aTarget) {
    return mDS->Assert(mSource, property, aTarget, PR_TRUE);
  }
  return NS_OK;
}

// _retval is the url of the child that was kicked off. It can be the url of the 
// one being inserted in the case of a position change.
NS_IMETHODIMP
flockRDFSourceUtils::InsertSortedOn(const nsACString &aSortCriteria,
                               nsIRDFResource *aChild2Insert, 
                               PRInt32 aMaxCount, nsACString & _retval)
{
  _retval = "";
  nsCOMPtr<flockIRDFSourceUtils> child = new flockRDFSourceUtils();
  child->Init(mDS, aChild2Insert);
  PRTime insertDate, cursorDate;
  nsAutoString insertLit, cursorLit;
  nsCOMPtr<nsIRDFNode> sortCriteria;
  FL_ENSURE(child->GetTarget(aSortCriteria, getter_AddRefs(sortCriteria)));
  nsCOMPtr<nsIRDFDate> dateCriteria(do_QueryInterface(sortCriteria));
  if(dateCriteria != nsnull) {
    FL_ENSURE(dateCriteria->GetValue(&insertDate));
  } else {
    FL_ENSURE(LitValue(sortCriteria, insertLit));
  }

  nsCOMPtr<flockIRDFContainer> container =
    do_CreateInstance("@mozilla.org/rdf/container;1");
  FL_ENSURE(container->Init(mDS, mSource));
  // First, remove the child to insert in case it has to be re-sorted
  FL_ENSURE(container->RemoveElement(aChild2Insert, PR_TRUE));
  // a sorted insert...
  PRInt32 count = 0;
  PRBool childWasAdded = PR_FALSE;
  nsCOMPtr<nsISimpleEnumerator> children;
  FL_ENSURE(container->GetElementsBackwards(getter_AddRefs(children)));
  nsCOMPtr<nsIRDFResource> previous = nsnull;
  while (PR_TRUE) {
    nsCOMPtr<nsIRDFResource> cursor = do_QueryInterface(GetNext(children));
    if (!cursor)
      break;
    if (!childWasAdded) {
      child->Init(mDS, cursor);
      PRBool foundPlace = PR_FALSE;
      if(dateCriteria != nsnull) {
        child->GetDateTarget(aSortCriteria, &cursorDate);
        foundPlace = cursorDate <= insertDate;
      } else {
        child->GetLitTarget(aSortCriteria, cursorLit);
        foundPlace = cursorLit <= insertLit;
      }
      if (foundPlace) {
        if (!previous)
          FL_ENSURE(container->AppendElement(aChild2Insert));
        else {
          PRInt32 pos;
          FL_ENSURE(container->IndexOf(previous , &pos));
          FL_ENSURE(container->InsertElementAt(aChild2Insert, pos, PR_TRUE));
        }
        if (!aMaxCount) {
          return NS_OK; // done. no need to continue counting
        }
        count++;
        childWasAdded = PR_TRUE;
      }
      previous = cursor;
    }
    if (aMaxCount) {
      count++;
      if (count > aMaxCount) {
        if (!childWasAdded) {
          // The child was too old
          child->Init(mDS, aChild2Insert);
          return child->Destroy();
        }
        // Kick out and return the oldest element
        FL_ENSURE(container->GetElements(getter_AddRefs(children)));
        nsCOMPtr<nsIRDFResource> oldest =
                                        do_QueryInterface(GetNext(children));
        oldest->GetValueUTF8(_retval);
        child->Init(mDS, oldest);
        return child->Destroy();
      }
    }
  }
  if (!childWasAdded) {
    NS_ASSERTION(!aMaxCount || count <= aMaxCount, "Algorithm error");
    // Never inserted
    // no items were in the collection before OR it is the oldest item.  
    if (aMaxCount && count == aMaxCount) {
      // The child was too old
      child->Init(mDS, aChild2Insert);
      return child->Destroy();
    }
    // Insert because there's room
    FL_ENSURE(container->InsertElementAt(aChild2Insert, 1, PR_TRUE));
  }
  return aChild2Insert->GetValueUTF8(_retval);
}
