/**********************************************************************
 *
 * recordaction.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 "recordaction.h"
#include "recptprototools.h"
#include "dublincore.h"
#include "qualified_dublincore.h"
#include "rfc1807.h"
#include "oaitools.h"


recordaction::recordaction(text_tset &metadataset) : oaiaction("GetRecord") {
  metaformatptr fptr;

  if (metadataset.count("oai_dc") > 0) {
    fptr.set_class(new dublin_core());  
    this->formatMap[fptr.get_class()->formatName()] = fptr;
  }
  
  if (metadataset.count("gsdl_qdc") > 0) {
    fptr.set_class(new qualified_dublin_core());  
    this->formatMap[fptr.get_class()->formatName()] = fptr;
  }
  
  if (metadataset.count("rfc1807") > 0) {

    fptr.set_class(new rfc1807());
    this->formatMap[fptr.get_class()->formatName()] = fptr;
  }
}

recordaction::~recordaction() {
  metaformat_map::iterator here = this->formatMap.begin();
  metaformat_map::iterator end  = this->formatMap.end();

  while (here != end) {
    here->second.clear();
    ++here;
  }
}


bool recordaction::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 != "identifier" &&
	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);
    }

    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
  // ----------------------------------------------------------------------------

  // None!

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

  // The "metadataPrefix" argument is required
  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;
  }

  // The "identifier" argument is required
  text_t identifier = params["identifier"];

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

    // before checking the specified identifier exists, need to extract the greenstone OID from it
	text_t oai_OID_prefix = "oai:"+this->configuration->getRepositoryId()+":";
	if(identifier.replace(oai_OID_prefix, "") <= 0) {
		this->errorType = "idDoesNotExist";
		// Only throw an error if we're using v2.0. 
		if (this->configuration->getOAIVersion() >= 200) {
			return false;
		}
	}
  
  // Extract the collection name from the identifier specification
  text_t collection = "";
  oaiclassifier::toGSDL(collection, identifier);
  
  // Check a document with the specified identifier exists
  text_tset metadata;
  if (!get_oai_info(identifier, collection, "", metadata, false, protocol, this->gsdlResponse, *logout))
  {
    this->errorType = "idDoesNotExist";
    return false;
  }

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

  // None!

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

//
// Output the content of a GetRecord request; in this case, the static member 
// output_record below is used to fulfill most of the request
//
bool recordaction::output_content(ostream &output, recptproto *protocol, oaiargs &params)
{
  // validateAction will already have set up the correct response content
  text_t gsdlId = params["identifier"];
  text_t gsdlCollect;
  
	// although at this stage, we don't actually need to be testing that the identifier exists
	// (this method is not meant to throw an idDoesNotExist error), we do however need to be 
	// working with the correct identifier, so we need to remove any oai_OID_prefix from the 
	// gsdlId in order to extract the greenstone OID.
  	text_t oai_OID_prefix = "oai:"+this->configuration->getRepositoryId()+":";
	gsdlId.replace(oai_OID_prefix, "");
  
  // convert record identifier into GSDL format from OAI
  oaiclassifier::toGSDL(gsdlCollect, gsdlId);
  
  // go direct to output_record
  return output_record(output, gsdlCollect, gsdlId, params["metadataPrefix"]);
}

//
// A static member that does everything the output_content method above needs,
// but can be called when individual records are being output for another 
// action.
//
bool recordaction::output_record(ostream &output, recptproto *protocol, const text_t &collection, const text_t &OID, const text_t &metadataPrefix)
{
  text_tset        metadata;
  ofstream         logout("oai.log", ios::app);
  
  // get the document information
  if (!get_oai_info(OID, collection, "", metadata, false, protocol, this->gsdlResponse, logout)) {

    this->errorType = "idDoesNotExist";

    if(this->configuration->getOAIVersion() >= 200) {
      this->output_error(output, errorType);
      return false;
    }
  }
  else {    
    this->errorType = "";
  }

  return this->output_record(output, collection, OID, metadataPrefix);
}  

bool recordaction::output_record(ostream &output, const text_t &collection, const text_t &OID, 
				 const text_t &metadataPrefix)
{ int oaiVersion = this->configuration->getOAIVersion();

  // check to see if it's a classifier
  text_t childHead;
  text_t::const_iterator start = OID.begin();
  text_t::const_iterator here  = OID.begin();
  here += 2;
  childHead = substr(start, here);
  
  // if it isn't a document, kill it now
  if (childHead == "CL") {
    cerr << "Not a document" << endl;
    return false;
  }

  ResultDocInfo_t doc_info = this->gsdlResponse.docInfo[0];
  text_t lastModified = "";
  text_t deleted_status = "";

  // Fills lastModified with the date from the document in doc_info, in the format YYYY-MM-DD
  this->getLastModifiedDate(doc_info, lastModified);
  this->getMeta(doc_info, "oaiinf.status", deleted_status);

  // If the ID exists, output record for oai response (OAI v1.1)
  // OAI v2.0 will already have bailed if ID doesn't exist (yes?)
  if (this->errorType != "idDoesNotExist") {
    text_t oaiLabel = OID;
    oaiclassifier::toOAI(this->configuration->getRepositoryId(), collection, oaiLabel); // Concatenates HASH id to collection, which OAI needs
    
    // output a record
    output << "  <record>\n";
    
    // output header part of oai response
    this->output_record_header(output, oaiLabel, lastModified, deleted_status,
			       doc_info.metadata["memberof"].values, oaiVersion);
    
    // only output <metadata /> section if record is not marked as deleted
    if (this->errorType != "cannotDisseminateFormat" && deleted_status != "D") {
      if (this->formatMap[metadataPrefix].get_class()->output_metadata(output, collection, doc_info)) {
	//  output 'about' part of oai response - we probably won't ever use this
	//output << "    <about>\n";
	//output << "    </about>\n";
      } 
    }
    // close record
    output << "  </record>\n\n";
  }
  return true;
}
