/**********************************************************************
 *
 * listidsaction.cpp --
 *
 * Copyright (C) 2004-2010  The New Zealand Digital Library Project
 *
 * A component of the Greenstone digital library software
 * from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *********************************************************************/

#include "listidsaction.h"
#include "recptprototools.h"
#include "oaitools.h"

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

bool listidsaction::validateAction(recptproto *protocol, oaiargs &params)
{
  // ----------------------------------------------------------------------------
  //  1. Check for invalid arguments
  // ----------------------------------------------------------------------------
  bool invalid_argument_supplied = false;
  text_tmap::const_iterator param_iterator = params.begin();
  while (param_iterator != params.end())
  {
    // Check for arguments that aren't valid for this action
    if (param_iterator->first != "verb" &&
	param_iterator->first != "from" &&
	param_iterator->first != "until" &&
	param_iterator->first != "set" &&
	param_iterator->first != "resumptionToken" &&
	param_iterator->first != "metadataPrefix")
    {
      // We've found an invalid argument
      invalid_argument_supplied = true;

      // Delete the invalid argument from the list so it doesn't end up in the <request> tag that is returned
      params.erase(param_iterator->first);
    }

    // The metadataPrefix argument is not allowed in OAI v1.1
    else if (param_iterator->first == "metadataPrefix" && this->configuration->getOAIVersion() <= 110)
    {
      // We've found an invalid argument
      invalid_argument_supplied = true;

      // Delete the invalid argument from the list so it doesn't end up in the <request> tag that is returned
      params.erase(param_iterator->first);
    }

    param_iterator++;
  }

  // If we found an invalid argument it's an error, so don't go any further 
  if (invalid_argument_supplied)
  {
    this->errorType = "badArgument";
    return false;
  }

  // ----------------------------------------------------------------------------
  //  2. Handle any exclusive arguments
  // ----------------------------------------------------------------------------

  //  The resumptionToken argument is exclusive
  if (params["resumptionToken"] != "")
  {
    // This argument is exclusive, so no other arguments are allowed (except "verb" of course)
    if (params.getSize() != 2)
    {
      this->errorType = "badArgument";
      return false;
    }

    // Check the resumption token is valid
    ResumptionToken token(params["resumptionToken"]);
    if (token.isValid())
    {
      // Everything is fine, and we don't continue further because this is an exclusive argument
      this->errorType = "";
      return true;
    }
    else
    {
      // There was an error with the resumption token
      this->errorType = "badResumptionToken";
      return false;
    }
  }

  // ----------------------------------------------------------------------------
  //  3. Handle any required arguments
  // ----------------------------------------------------------------------------

  // OAI v2.0 requires metadataPrefix
  if (this->configuration->getOAIVersion() > 110)
  {
    text_t metadataPrefix = params["metadataPrefix"];

    // Check that the metadataPrefix argument exists
    if (metadataPrefix == "")
    {
      this->errorType = "badArgument";
      return false;
    }

    // Check that the metadataPrefix is a format we support
    if (this->formatNotSupported(metadataPrefix))
    {
      this->errorType = "cannotDisseminateFormat";
      return false;
    }
  }

  // ----------------------------------------------------------------------------
  // 4. Check any remaining arguments
  // ----------------------------------------------------------------------------

  // Check "from" and "until" arguments
  if (params["from"] != "" || params["until"] != "")
  {
    text_t from  = params["from"];
    text_t until = params["until"];

    // Check the from date is in the correct format: YYYY-MM-DD
    if (from != "")
    {
      // Must be in the form YYYY-MM-DD
      if (from.size() != 10 || from[4] != '-' || from[7] != '-')
      {
	this->errorType = "badArgument";
	params.erase("from");
      }
    }
    // Check the until date is in the correct format: YYYY-MM-DD
    if (until != "")
    {
      // Must be in the form YYYY-MM-DD
      if (until.size() != 10 || until[4] != '-' || until[7] != '-')
      {
	this->errorType = "badArgument";
	params.erase("until");
      }
    }

    if (this->errorType == "badArgument")
    {
      return false;
    }

    // If both arguments are supplied the from date must be less than or equal to the until date
    if (from != "" && until != "" && !(from <= until))
    {
      this->errorType = "badArgument";
      return false;
    }
  }

  // Check "set" argument
  if (params["set"] != "")
  {
    // Example set specification: "demo:CL2"
    text_t set = params["set"];

    // Is the set a super collection??
    text_tarray &super_collections = this->configuration->getSuperCollectionsList();
    bool super_collection_found = false;
    for (int c = 0; c < super_collections.size(); c++)
    {
      if (super_collections[c] == set)
      {
	super_collection_found = true;
	break;
      }
    }
    if (super_collection_found) {
      this->errorType = "";
      return true;
    }
    
    // Extract the collection name from the set specification
    text_t collection = "";
    oaiclassifier::toGSDL(collection, set);

    // Check that the collection is accessible
    ColInfoResponse_t cinfo;
    comerror_t err;
    protocol->get_collectinfo(collection, cinfo, err, cerr);
    if (err != noError)
    {
      this->errorType = "badArgument";
      return false;
    }

    // Check the collection is one that is in the list in the oai.cfg file
    if (!this->configuration->isValidCollection(collection)) {
      this->errorType = "badArgument";
      return false;
    }      

    // Check the child set if it was given
    if (set != "" && !this->check_classifier(protocol, collection, set))
    {
      this->errorType = "badArgument";
      return false;
    }
  }

  // If we've reached here everything must be fine
  this->errorType = "";
  return true;
}

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

bool listidsaction::output_document(ostream& output, recptproto *protocol, const text_t &collection, 
				    const text_t &OID, const text_t &metadataPrefix /* ignored */)
{
  FilterResponse_t response;
  ResultDocInfo_t  doc_info;
  text_tset        metadata;
  ofstream         logout("oai.log", ios::app);
    // logout closed automatically by object destructor at this method's end:
    // https://stackoverflow.com/questions/748014/do-i-need-to-manually-close-an-ifstream
  text_t           lastModified = "";
  text_t           deleted_status = "";
  int              oaiVersion = this->configuration->getOAIVersion();
  text_t repos_id = this->configuration->getRepositoryId();
  get_oai_info(OID, collection, "", metadata, false, protocol, response, logout);
  doc_info = response.docInfo[0];
  this->getLastModifiedDate(doc_info, lastModified);
  this->getMeta(doc_info, "oaiinf.status", deleted_status);
  
  // output the record for this document
  text_t oaiLabel = OID;
  oaiclassifier::toOAI(repos_id, collection, oaiLabel);

  if(oaiVersion <= 110)
    output << "  <identifier>" << oaiLabel << "</identifier>\n";
  else
    this->output_record_header(output, oaiLabel, lastModified, deleted_status,
			       doc_info.metadata["memberof"].values, oaiVersion);

  return true;
}
