/**********************************************************************
 *
 * oaiconfig.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 "oaiconfig.h"
#include <iostream>
#include <stdlib.h>
#include "fileutil.h"

/**
 *  The mapping works as follows in the oai.cfg or collect.cfg file.
 *
 *  A line is in the format oaimapping <collection field> <oai field>
 *
 *  The map here is used to look up the "Greenstone" name which is mapped from
 *  a given OAI field name, the reverse direction to that given in the 
 *  Greenstone collect.cfg file.  The oairecordaction class instance which
 *  produces output for an OAI record information request thus uses the map
 *  to work from the field in the collection it has on hand which OAI
 *  record name it should use instead.
 *
 *  An extension is to be used for this in which the OAI field name in the
 *  collect.cfg file can be made specific for a particular record format.
 *  This is done using the OAI field name in the format of 
 *    <OAI format>.<OAI field name>
 *  Thus, an rfc1807 Title field would be referred to as rfc1807.Title
 *
 *  A collection-level mapping can be provided in oai.cfg by prepending 
 *  collname to collection field:
 *  <collname:field> <oai field>

 *  In the absence of a particular format name, the mapping is taken to be
 *  universal.
 */

oaiconfig::oaiconfig() : configurable () {
  this->resumptionSize = -1; // Default = do not use resumption tokens
}

oaiconfig::oaiconfig(text_t &gsdlhome, text_t &gsdlcollect) 
{
  // read main configuration file (oai.cfg) to get oai collections
  text_t mainconfig = filename_cat(gsdlhome, "etc", "oai.cfg");
  this->collection = "";
  this->resumptionSize = -1;
  this->read_configfile(mainconfig);

  // then if we've not got a specified collection in the gsdlcollect
  // parameter, read in all the collection's individual configurations
  if (gsdlcollect == "") {
    text_tarray::iterator start = this->collectList.begin();
    text_tarray::iterator here  = this->collectList.end()-1;
    while (here != start) {		
      if (!this->configureCollection(gsdlhome, *here)) {
	this->collectList.erase(here);
      }
      --here;
    }
    // and do the first one
    if (!this->configureCollection(gsdlhome, *here)) {
      this->collectList.erase(here);
    }	
  }
  else {
    // what do we do if this fails?
    this->configureCollection(gsdlhome, gsdlcollect);
  }
}

oaiconfig::~oaiconfig() 
{
  oaicollectmap::iterator here = this->collectMap.begin();
  oaicollectmap::iterator end  = this->collectMap.end();
  while (here != end) {
    delete here->second;
    ++here;
  }	
}

int oaiconfig::resumeAfter()
{ return this->resumptionSize;
}

int oaiconfig::getOAIVersion()
{ 
  if (this->oaiVersion == "1.1") {
    return 110;
  }
  return 200; 
}

bool oaiconfig::isValidCollection(const text_t &collection)
{
  for (int c = 0; c < this->collectList.size(); c++)
    if (this->collectList[c] == collection) {
      return true;
    }
  return false;
}

bool oaiconfig::configureCollection(const text_t &gsdlhome, const text_t &gsdlcollect)
{
  text_t cnfgfile = filename_cat(gsdlhome, "collect", gsdlcollect, "etc", "collect.cfg");
  if (!file_exists(cnfgfile)) {
    return false;
  }  
  
  this->collection = gsdlcollect;
  this->read_configfile(cnfgfile);
  
  // to work out the earliestDatestamp for the repository, need to
  // to read in the build.cfg file of each OAI collection, in order to
  // find the oldest earliestDatestamp field of all the OAI collections
  text_t buildcnfgfile = filename_cat(gsdlhome, "collect", gsdlcollect, "index", "build.cfg");
  if (file_exists(buildcnfgfile)) {
	this->read_configfile(buildcnfgfile);
  }
  
  return true;
}
void oaiconfig::configure (const text_t &key, const text_tarray &cfgline)
{
  // we've got an oai mapping item, and at least two fields
  if (key == "oaimapping" && cfgline.size() > 1) {
    text_t::const_iterator colonAt;
    text_t index, name, configCollection;

    // Take a default collection as being whatever the collection being configured is...
    configCollection = this->collection;
    
    // get the name of the (collection) field to map; this may actually
    // be in a colon separated format of the type 
    // <collection name>:<field name>
    index = cfgline[0];
    if ((colonAt = findchar(index.begin(), index.end(), ':')) != index.end()) {
      configCollection = substr(index.begin(), colonAt);

      if (this->collection != "" && configCollection != this->collection) {
	cerr << "Attempt to configure OAI mappings for " << configCollection << " in " << this->collection << endl;
      }

      colonAt += 1;
      index = substr(colonAt, index.end());
    }
    
    // the second parameter is the metadata field to map the collection
    // field onto.  It may be provided with a metadata protocol (which
    // will be given first and separated by a period or full stop).  In 
    // the case of format.field name, the splitting is done here.
    if ((colonAt = findchar(cfgline[1].begin(), cfgline[1].end(), '.')) != cfgline[1].end()) {
      text_t stub = substr(cfgline[1].begin(), colonAt);
      colonAt += 1;
      name = substr(colonAt, cfgline[1].end());
      index.append(":");
      index.append(stub);
    }
    else {
      name = cfgline[1];
    }
    
    // now 'index' is in the form <collectionfield>:(formatname) 
    //     'name' is simply the fieldname within the format
    //     'configCollection' is the collection to be configured
    
    // now simply map the field name (index) onto the collection name (name)
    if (this->collectMap[configCollection] == NULL) {
      this->collectMap[configCollection] = new oaicollectconfig(configCollection);
    }
    this->collectMap[configCollection]->fieldMap[index] = name;
    
    //    cerr << "Mapping " << index << " to " << name << " in " << configCollection << endl;
    
    // TODO: check that the mapped field is actually in use
  }
  else if (key == "oaicollection" && cfgline.size() >= 1) {
    // Configure a collection to be used as part of the OAI archive.
    // This line should read:
    //
    // oaicollection <collectionname>
    //
    // Where <collectionname> is the name of the directory inside the
    // gsdl/collect folder which contains the collection.
    //
    // To configure several collections, merely repeat this line,
    // or alternatively use additional collection names after the
    // first one.
    //
    // This configuration should only appear in oai.cfg
    //
    if (this->collection != "") {
      cerr << "Attempt to configure an oai collection outside of oai.cfg" << endl;
      cerr << "Configuration attempted in " << this->collection << " collection." << endl;
      exit(1);
    }
    for (int c = 0; c < cfgline.size(); ++c) {
      this->collectList.push_back(cfgline[c]);
      addToAllCollectionsList(cfgline[c]);
    }
  }
  else if (key == "oaisupercollection" && cfgline.size() > 1) {

    // Configure a super collection set
    // The line should read 
    //
    // oaisupercollection <setName> [collections part of this set]
    //
    // Records in specified collections will be advertised as being part 
    // of the SetName set as well as their collectionName set
    //
    // This configuration should only appear in oai.cfg
    //
    if (this->collection != "") {
      cerr << "Attempt to configure an oai super collection outside of oai.cfg" << endl;
      cerr << "Configuration attempted in " << this->collection << " collection." << endl;
      exit(1);
    }
    
    text_t super_set = cfgline[0];
    this->superCollectList.push_back(super_set);
    this->superCollectMap[super_set] = new oaisupercollectconfig(super_set);
    for (int c = 1; c < cfgline.size(); ++c) {
      text_t sub_coll = cfgline[c];
      // Add the collection into the list for this supercoll
      this->superCollectMap[super_set]->collectionList.push_back(sub_coll);
      // Add the super coll into the list for the collection
      if (this->collectMap[sub_coll]== NULL) {
	this->collectMap[sub_coll] = new oaicollectconfig(sub_coll);
      }
      this->collectMap[sub_coll]->superCollectList.push_back(super_set);
      addToAllCollectionsList(sub_coll);
    }
  }
  else if (key == "oaimetadata" && cfgline.size() >= 1) {
    // List of metadata prefixes to suuport
    // This line should read:
    //
    // oaicollection <metadataname> <metadataname>...
    //
    //
    // This configuration should only appear in oai.cfg
    //
    if (this->collection != "") {
      cerr << "Attempt to configure oai metadata outside of oai.cfg" << endl;
      cerr << "Configuration attempted in " << this->collection << " collection." << endl;
      exit(1);
    }
    for (int c = 0; c < cfgline.size(); ++c) {
      // todo: check that the set name is valid
      this->metadataSet.insert(cfgline[c]);
    }
  }
  else if (key == "oaiinfo" && cfgline.size() >= 1) {
    // Get a piece of information for the oai repository information
    // request.  The line should read:
    //
    // oaiinfo <information field name> <value>
    //
    // This configuration should only be attempted in oai.cfg
    //
    if (this->collection != "") {
      cerr << "Attempt to set oai information outside of oai.cfg" << endl;
      cerr << "Configuration attempted in " << this->collection << " collection." << endl;
      exit(1);
    }
    
    // if no second parameter is given, then the first parameter
    if (cfgline.size() == 1) {
      this->infoMap[cfgline[0]] = cfgline[0];
    }
    else {
      this->infoMap[cfgline[0]] = cfgline[1];
    }
  }
  else if ( key == "oaisetname" || key == "oaisetdescription") { 
    text_t coll_name;
    text_t value = "";
    if (this->collection != "") {
      // we are in collect.cfg
      coll_name = this->collection;
      if (cfgline.size() == 1) {
	// just the collection value
	value = cfgline[0];
      }
      else if (cfgline.size() == 2) {
	// we have a subset name (eg for classifier)
	coll_name.append(":");
	coll_name.append(cfgline[0]);
	value = cfgline[1];
      }
    } else if (cfgline.size() == 2) {
      // oai.cfg, line should be collname, setName
      coll_name = cfgline[0];
      value = cfgline[1];
    }
    if (value != "") {
      // check first for super collection
      if (this->superCollectMap[coll_name] != NULL) {
	if (key == "oaisetname") {
	  this->superCollectMap[coll_name]->setName = value;
	} else if (key == "oaisetdescription") {
	  this->superCollectMap[coll_name]->setDescription = value;
	}
      } else {
	if (this->collectMap[coll_name] == NULL) {
	  this->collectMap[coll_name] = new oaicollectconfig(coll_name);
	}
	if (key == "oaisetname") {
	  this->collectMap[coll_name]->setName = value;
	} else if (key == "oaisetdescription") {
	  this->collectMap[coll_name]->setDescription = value;
	}
      }
    }
  }

  else if (key == "resumeafter" && cfgline.size() >= 1) {
    this->resumptionSize = cfgline[0].getint();
  }

  else if (key == "maintainer") {
    this->maintainer = cfgline[0];
  }
  else if (key == "repositoryName") {
    this->repositoryName = cfgline[0];
  }
  else if (key == "repositoryId") {
    this->repositoryId = cfgline[0];
  }
  else if (key == "repositoryIdVersion") {
    this->repositoryIdVersion = cfgline[0];
  }
  else if (key == "baseServerURL") {
    this->baseServerURL = cfgline[0];
  }
  else if (key == "oaiserverPath") {
    this->oaiserverPath = cfgline[0];
  }
  else if (key == "libraryPath") {
    this->libraryPath = cfgline[0];
  }
  else if (key == "docRootPath") {
    this->docRootPath = cfgline[0];
  }

  else if (key == "oaiversion") {
    this->oaiVersion = cfgline[0];
  }
}
  
void oaiconfig::addToAllCollectionsList(const text_t coll_name) {

  for (int c=0; c<this->allCollectList.size(); c++) {
    if (allCollectList[c] == coll_name) return;
  }
  this->allCollectList.push_back(coll_name);
  
}

text_t oaiconfig::generateBaseServerURL() {
  char *server_name = getenv("SERVER_NAME");
  char *server_port = getenv("SERVER_PORT");
  text_t url = "http://";
  url.append(server_name);
  url.append(":");
  url.append(server_port);
  return url;
}

text_t oaiconfig::getMapping(const text_t &collection, const text_t &collectfield)
{
  if (this->collectMap[collection] == NULL) {
    return "";
  }
  return this->collectMap[collection]->fieldMap[collectfield];
}

/**
 *  Get the mapping for a field in a given collection; if no mapping
 *  exists, the result will be a blank string.
 */
text_t oaiconfig::getMapping(const text_t &collection, const text_t &collectfield, const text_t &formatname)
{
  text_t fullName = collectfield;
  fullName.append(":");
  fullName.append(formatname);

  // try the collection-specific options first
  if (this->collectMap[collection] != NULL) {
    // first try the most specific item - this collection, and given that protocol
    if (this->collectMap[collection]->fieldMap.count(fullName) >= 1) {
      return this->collectMap[collection]->fieldMap[fullName];
    }
    // otherwise, fall back to this collection, and all protocols
    else if (this->collectMap[collection]->fieldMap.count(collectfield) >= 1) {
      return this->collectMap[collection]->fieldMap[collectfield];
    }
  }

  // if no mappings exist, return an empty item
  if (this->collectMap[""] == NULL) {
    return "";
  }

  // then try generic rules
  if (this->collectMap[""]->fieldMap.count(fullName) >= 1) {
    return this->collectMap[""]->fieldMap[fullName];
  }
  else {
    return this->collectMap[""]->fieldMap[collectfield];
  }
}

text_t oaiconfig::getBaseURL()
{
  if (this->baseServerURL.empty()) {
    this->baseServerURL = generateBaseServerURL();
  }
  return this->baseServerURL + this->oaiserverPath;
}
text_t oaiconfig::getBaseLibraryURL()
{
  if (this->baseServerURL.empty()) {
    this->baseServerURL = generateBaseServerURL();
  }
  return this->baseServerURL + this->libraryPath;
}
text_t oaiconfig::getBaseDocRoot()
{
  if (this->baseServerURL.empty()) {
    this->baseServerURL = generateBaseServerURL();
  }
  return this->baseServerURL + this->docRootPath;
}

text_t oaiconfig::getRelativeBaseDocRoot()
{
  return this->docRootPath;
}
text_t oaiconfig::getRepositoryName()
{
  return this->repositoryName;
}
text_t oaiconfig::getRepositoryId()
{
  return this->repositoryId;
}
text_t oaiconfig::getRepositoryIdVersion()
{
  return this->repositoryIdVersion;
}
text_t oaiconfig::getMaintainer()
{
  return this->maintainer;
}
text_t oaiconfig::getSetName(const text_t &setSpec)
{
  if (this->collectMap[setSpec] != NULL) {
    return this->collectMap[setSpec]->setName;
  }
  if (this->superCollectMap[setSpec] != NULL) {
    return this->superCollectMap[setSpec]->setName;
  }
  return "" ;
  
}

text_t oaiconfig::getSetDescription(const text_t &setSpec)
{
  if (this->collectMap[setSpec] != NULL) {
  return this->collectMap[setSpec]->setDescription;
  }
  if (this->superCollectMap[setSpec] != NULL) {
    return this->superCollectMap[setSpec]->setDescription;
  }
  
  return "" ;
  
}


text_tarray oaiconfig::getSuperCollectionsForThisCollection(const text_t &collection)
{

  text_tarray super_colls;
  if (this->superCollectList.size() == 0) {
    return super_colls;
  }

  for (int s = 0; s < this->superCollectList.size(); s++) {
    text_tarray colls = this->superCollectMap[this->superCollectList[s]]->collectionList;
    for (int c = 0; c < colls.size(); c++)
    {
      if (colls[c] == collection)
      {
	super_colls.push_back(this->superCollectList[s]);
	break;
      }
    }
  }
   
  return super_colls;
}
