
#include <unistd.h>
#include <sys/dir.h>

#include "gsdlsitecfg.h"
#include "recptprototools.h"

#import <Foundation/NSString.h>
#import <Foundation/NSArray.h>

#import <UIKit/UIKit.h>
#import <UIKit/UIAlertSheet.h>

#import "GSResultItem.h"
#import "GreenstoneAPI.h"

GreenstoneAPI::GreenstoneAPI(UIApplication* toplevelApp) 
  : _toplevelApp(toplevelApp), _lang("en"),
    _cservers(nil), _nproto(nil), _gsdlhome()
{
  _cservers = new collectset(_gsdlhome); // sets gsdlhome

  // set up the null protocol
  _nproto = new nullproto();
  _nproto->set_collectset(_cservers);

  comerror_t comerr;
  _nproto->init(comerr,cerr);
  if (comerr!=noError) {
    text_t error_message = get_comerror_string (comerr);
    alert("Error",error_message);
  }

}


GreenstoneAPI::~GreenstoneAPI()
{
  delete _cservers;
  delete _nproto;

}

void GreenstoneAPI::alert(NSString* nslevel, NSString* nsmessage)
{
  // Allert sheet displayed at centre of screen.

  NSArray *buttons = [NSArray arrayWithObjects:@"OK", nil];
  UIAlertSheet *alertSheet = [UIAlertSheet alloc];
  [alertSheet initWithTitle:nslevel buttons:buttons 
    defaultButtonIndex:1 delegate:_toplevelApp context:nil];

  [alertSheet setBodyText:nsmessage];
  [alertSheet popupAlertAnimated:YES];
}


void GreenstoneAPI::alert(char* level, char* message)
{
  // Alert sheet displayed at centre of screen.

  NSString* nslevel = [[NSString alloc] initWithCString: level];
  NSString* nsmessage = [[NSString alloc] initWithCString: message];

  alert(nslevel,nsmessage);

  [nslevel release];
  [nsmessage release];
}

void GreenstoneAPI::alert(text_t level, text_t message)
{
  char* level_cstr = level.getcstr();
  char* message_cstr = message.getcstr();

  alert(level_cstr,message_cstr);

  delete [] level_cstr;
  delete [] message_cstr;
}

bool GreenstoneAPI::sanityCheck()
{  
  text_t gsdl_home, unused_collecthome;
  text_t unused_httpdomain,unused_httpprefix;
  if (!site_cfg_read(gsdl_home,unused_collecthome,
		     unused_httpdomain,unused_httpprefix)) {
    // couldn't find gsdlsite.cfg file    

    char cwd[128];
    getcwd(cwd,128);

    text_t sitecfg_filename = cwd;
    sitecfg_filename += "gsdlsite.cfg"; 
    
    alert("Error","Unable to find " + sitecfg_filename );
    return false;
  }

  text_t collect_dir = filename_cat(gsdl_home,"collect");
  char* collect_dir_cstr = collect_dir.getcstr();

  DIR* directorypointer = opendir(collect_dir_cstr);	
  delete [] collect_dir_cstr;

  if(directorypointer == NULL)	//If the directory could not be opened
    {  
      text_t mess = "No collections directory.  Please make one at: " + collect_dir;
      alert("Error",mess);
      return false;
    }
 
  return true;
}


NSMutableArray* GreenstoneAPI::collectionList()
{
  text_tarray collection_list;
  comerror_t comerr;
  
  _nproto->get_collection_list (collection_list, comerr, cerr);

  if (comerr != noError) {
    alert("Error",get_comerror_string(comerr));
    return nil;
  }

  int num_cols = collection_list.size();

  NSMutableArray* nscollist = [[NSMutableArray alloc] initWithCapacity:num_cols];

  text_tarray::iterator collection_iterator = collection_list.begin();
  while (collection_iterator != collection_list.end())
  {
    text_t& colname = *collection_iterator;
    char* colname_cstr = colname.getcstr();
    NSString* nscolname = [[NSString alloc ] initWithCString: colname_cstr];
    delete [] colname_cstr;

    [nscollist addObject:nscolname];

    collection_iterator++;
  }

  return nscollist;
}

ColInfoResponse_t& GreenstoneAPI::collectInfo (const text_t &collection)
{
  ColInfoResponse_t& collectinfo = *new ColInfoResponse_t();
  comerror_t comerr;
  _nproto->get_collectinfo(collection, collectinfo,comerr, cerr);

  if (comerr != noError) {
    alert("Error",get_comerror_string(comerr));
  }
  
  return collectinfo;
}

ColInfoResponse_t& GreenstoneAPI::collectInfo (NSString* nscollection)
{
  char const* nscollection_cstr  = [nscollection UTF8String]; 

  ColInfoResponse_t& colinfo = collectInfo(nscollection_cstr);

  // according to documentation, nscollectcion_cstr autorealeased
  // => don't need to free them
  return colinfo;
}

/* ****
text_t GreenstoneAPI::lookupLanguagePref()
{
  NSString* nslang = [(GreenstoneApplication*)_toplevelApp languagePref];
  char* lang_cstr = [nslang UTF8String];
  text_t lang = lang_cstr;
  [nslang release];

  return lang;
}
*/

NSString* GreenstoneAPI::collectionmetaValue(ColInfoResponse_t& colinfo,
					     text_t key)
{
  text_t val = colinfo.get_collectionmeta(key,_lang);
  char* val_cstr = val.getcstr();

  NSString* nsval = [[NSString alloc] initWithCString:val_cstr];
  delete [] val_cstr;


  return nsval;
}


NSString* GreenstoneAPI::collectionName(ColInfoResponse_t& colinfo)
{
  return collectionmetaValue(colinfo,"collectionname");
}


NSString* TextTToNSString(text_t& text)
{
  // Make this available to other parts of program!!!
  if (text.empty()) { return nil; }

  char* text_cstr = text.getcstr();
  NSString* nstext = [[NSString alloc ] initWithCString: text_cstr];
  delete [] text_cstr;

  return nstext;
}

bool GreenstoneAPI::recurse_contents(const text_t& collection, ResultDocInfo_t& section,
				     text_tset& metadata, text_t& output_text)
{  
  // Output this section 
  	
  // 1. Get the title

  text_t title = section.metadata["Title"].values[0];
  output_text.append("<h3>"+title+"</h3>");

  // cerr << "**** Title = " << title << endl;

  // 2. Get the text
  DocumentRequest_t docrequest;
  DocumentResponse_t docresponse;
  comerror_t comerr;
  docrequest.OID = section.OID;

  _nproto->get_document(collection, docrequest, docresponse, comerr, cerr);

  if (comerr != noError) {
    alert("Error",get_comerror_string(comerr));
    return false;
  }

  output_text.append(docresponse.doc);
	
  // cerr << "*** appending text " << docresponse.doc << endl;

  int haschildren = section.metadata["haschildren"].values[0].getint();
  	
  // recurse through children
  if (haschildren == 1)
    {
      bool getParents = false;
      FilterResponse_t tmp;

      get_children (section.OID, collection, _lang, metadata, getParents, _nproto, tmp, cerr);
      ResultDocInfo_tarray::iterator thisdoc = tmp.docInfo.begin();
      ResultDocInfo_tarray::iterator lastdoc = tmp.docInfo.end();
      
      while (thisdoc != lastdoc) 
	{
	  bool recurse_ok = recurse_contents(collection,*thisdoc, metadata, output_text);
	  if (!recurse_ok) {
	    return false;
	  }
	  ++thisdoc;
	}
    }
	
  return true;
}


NSString* GreenstoneAPI::getResolvedDocument(NSString* nscollection, NSString* nsgid)
{
  char const* nscollection_cstr  = [nscollection UTF8String]; 
  char const* nsgid_cstr         = [nsgid UTF8String]; 

  text_t collection(nscollection_cstr);
  text_t gid(nsgid_cstr);

  //metadata elements needed by recurse_contents
  text_tset metadata;
  metadata.insert ("Title");
  metadata.insert ("haschildren");

  bool getParents = false;

  FilterResponse_t inforesponse;
  comerror_t comerr;

  NSString* nsresolvedtext = nil;

  if (get_info(gid, collection, _lang, metadata, getParents, _nproto, inforesponse, cerr))
    {
      text_t output_text;
      text_t make_utf8 = "    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n";

      output_text.append("<html>\n");
      output_text.append("  <head>\n"+make_utf8+"  </head>\n");

      if (recurse_contents(collection,inforesponse.docInfo[0], metadata, output_text)) {

	// cerr << "**** text = " << output_text << endl;

	  // retrieve [archivedir] for this document id
	  NSString* nsarchivedir = getMetadataFirst(nscollection,nsgid,@"archivedir");
	  const char* nsarchivedir_cstr = [nsarchivedir UTF8String]; 
	  text_t archivedir = nsarchivedir_cstr;
	  [nsarchivedir release];
	  
	  text_t resolved_text = resolveUrls(output_text,collection,archivedir);
	  
	  char* resolved_text_cstr = resolved_text.getcstr();
	  nsresolvedtext = [[NSString alloc] initWithCString:resolved_text_cstr];
	  
	  delete [] resolved_text_cstr;
	  }
      else {
	alert("Error","Failed to get info for " + gid);
      }
      output_text.append("</html>\n");
    }
  else {
    alert("Error","Failed to get info for " + gid);
  }


  return nsresolvedtext;
}


NSMutableArray* GreenstoneAPI::getMetadata(NSString* nscollection, NSString* nsgid, NSString* nsmetaname)
{
  char const* nscollection_cstr  = [nscollection UTF8String]; 
  char const* nsgid_cstr         = [nsgid UTF8String]; 
  char const* nsmetaname_cstr    = [nsmetaname UTF8String]; 

  NSMutableArray* nsmdlist = nil;

  text_t collection(nscollection_cstr);
  text_t gid(nsgid_cstr);
  text_t metaname(nsmetaname_cstr);

  text_tset metadata;
  metadata.insert (metaname);
  bool getParents = false;
  FilterResponse_t response;

  if (get_info (gid, collection, _lang, metadata, getParents, _nproto, response, cerr)) {
    int num_browse_by = response.docInfo.size();
    if (num_browse_by>0) {
      MetadataInfo_tmap& metadata = response.docInfo[0].metadata;
      MetadataInfo_t& metaname_info = metadata[nsmetaname_cstr];
      int num_browse_by = metaname_info.values.size();
      if (num_browse_by>0) {
	// protocol will return 1 even if there is no metadata
	// values will be the empty string

	nsmdlist = [[NSMutableArray alloc] initWithCapacity:num_browse_by];
	
	text_tarray& meta_val = metaname_info.values;
	text_tarray::iterator md_iterator = meta_val.begin();
	while (md_iterator != meta_val.end()) {
	  text_t& mdval = *md_iterator;

	  NSString* nsmdval = TextTToNSString(mdval);	  
	  if (nsmdval != nil) {
	    [nsmdlist addObject:nsmdval];
	  }

	  md_iterator++;	      
	}

	if ([nsmdlist count] == 0) {
	  // no valid metadata => free list and set to nil
	  [nsmdlist release];
	  nsmdlist = nil;
	}
      }
      else {
	alert("Error","OID" + gid + "does not have metadata for" + metaname);
      }
    }
    else {
      alert("Error","OID" + gid + "does not have metadata for" + metaname);
    }
  }
  else {
    alert("Error","Collection has no browsing capabilities" + gid);
  }

  // according to documentation, nscollectcion_cstr, nsgid_cstr and nsmetaname_cstr autorealeased
  // => don't need to free them

  return nsmdlist;
}


NSString* GreenstoneAPI::getMetadataFirst(NSString* nscollection, NSString* nsgid, NSString* nsmetaname)
{
  NSMutableArray* all_metavalues = getMetadata(nscollection,nsgid,nsmetaname);
 
  NSString* first_metavalue_copy = nil;

  if (all_metavalues != nil) {
    NSString* first_metavalue = [all_metavalues objectAtIndex:0];

    first_metavalue_copy = [[NSString alloc] initWithString:first_metavalue];

    [all_metavalues release];
  }

  return first_metavalue_copy;
}

NSString* GreenstoneAPI::getMetadataFirst(const text_t& collection, text_t& gid, text_t& metaname)
{
  char* collection_cstr = collection.getcstr();
  char* gid_cstr = gid.getcstr();
  char* metaname_cstr = metaname.getcstr();

  NSString* nscollection = [[NSString alloc] initWithCString:collection_cstr];
  NSString* nsgid = [[NSString alloc] initWithCString:gid_cstr];
  NSString* nsmetaname = [[NSString alloc] initWithCString:metaname_cstr];

  NSString* result = getMetadataFirst(nscollection,nsgid,nsmetaname);

  delete [] collection_cstr;
  delete [] gid_cstr;
  delete [] metaname_cstr;

  return result;

}

NSMutableArray* GreenstoneAPI::browseClassifierList(NSString* nscollection, NSString* nsgid)
{
  // **** upgrade this to use getMetadata, and then split on ';'

  char const* nscollection_cstr  = [nscollection UTF8String]; 
  char const* nsgid_cstr  = [nsgid UTF8String]; 

  text_t gid = nsgid_cstr;

  NSMutableArray* nscllist = nil;

  text_tset metadata;
  metadata.insert ("contains");
  FilterResponse_t response;
  comerror_t comerr;
  
  if (get_info (nsgid_cstr, nscollection_cstr, _lang, metadata, false, _nproto, response, cerr)) {
    int num_browse_by = response.docInfo.size();
    if (num_browse_by >0) {
      MetadataInfo_tmap& metadata = response.docInfo[0].metadata;
      MetadataInfo_t& contains = metadata["contains"];
      int num_browse_by = contains.values.size();
      if (num_browse_by>0) {

	// nscllist = [[NSMutableArray alloc] initWithCapacity:num_browse_by]; ****
	nscllist = [[NSMutableArray alloc] initWithCapacity:50];
	
	text_tarray& contains_val = contains.values;
	text_tarray::iterator cl_iterator = contains_val.begin();
	while (cl_iterator != contains_val.end()) {
	  text_t& clname = *cl_iterator;

	  // split up on ';'
	  text_tarray split_clname;
	  splitchar (clname.begin(), clname.end(), ';', split_clname);

	  text_tarray::iterator split_cl_iterator = split_clname.begin();
	  while (split_cl_iterator != split_clname.end()) {
	    text_t& cl = *split_cl_iterator;
	    NSString* nscl = nil;
	    if (cl[0] == '\"') {
	      text_t cl_trim = substr(cl.begin()+1,cl.end());
	      nscl = TextTToNSString(cl_trim);
	    }
	    else {
	      nscl = TextTToNSString(cl);
	    }
	    
	    if (nscl != nil) {
	      [nscllist addObject:nscl];
	    }
	    else {
	      cerr << "Warning: no semicolon split classifier 'contains' metadata value found for " + gid << endl;
	    }
	    
	    split_cl_iterator++;
	  }
	  cl_iterator++;	      
	}

	if ([nscllist count]==0) {
	  // No valid metadata found
	  [nscllist release];
	  nscllist = nil;
	}
      }
      else {
	alert("Error","Collection has to browsing capabilities");
      }
    }
    else {
      alert("Error","Collection has to browsing capabilities");
    }
  }
  else {
    alert("Error",get_comerror_string(comerr));
  }

  // according to documentation, nscollectcion_cstr and nsgid_cstr autorealeased
  // => don't need to free them

  return nscllist;
}


NSMutableArray* GreenstoneAPI::browseToplevelList(NSString* nscollection)
{
  return browseClassifierList(nscollection,@"browse");
}




void GreenstoneAPI::set_queryfilter_options (const text_t& collection,
					     FilterRequest_t &request, 
					     const text_t &querystring) 
{
  ColInfoResponse_t& collectinfo = collectInfo(collection);

  // request.filterResultOptions and request.fields (if required) should
  // be set from the calling code

  request.filterName = "QueryFilter";

  OptionValue_t option;

  option.name = "Term";
  option.value = querystring;
  request.filterOptions.push_back (option);

  option.name = "QueryType";
  option.value = "ranked";
  request.filterOptions.push_back (option);

  option.name = "MatchMode";
  option.value = "some";
  request.filterOptions.push_back (option);

  option.name = "Casefold";
  option.value = "true";
  request.filterOptions.push_back (option);

  option.name = "Stem";
  option.value = "true";
  request.filterOptions.push_back (option);

  option.name = "AccentFold";
  option.value = "true";
  request.filterOptions.push_back (option);

  text_t buildtype = collectinfo.buildType;

  if (buildtype == "lucene") {
    // Should prevent Lucene collection from doing any searching!! // ****
    alert("Warning","Searching lucene indexed collection on iPod not currently supported");
  }
  else if (buildtype == "mgpp") {
    option.name = "Level";
    option.value = "Doc";
    request.filterOptions.push_back (option);

    option.name = "Index";
    option.value = "idx"; // ****
    request.filterOptions.push_back (option);
  }
  else {
    cerr << "**** storing index as dte" << endl;
    option.name = "Index";
    option.value = "dte"; // ****
    request.filterOptions.push_back (option);
  }
  
  option.name = "Maxdocs";
  option.value = "50";
  request.filterOptions.push_back (option);

  option.name = "StartResults";
  option.value = "1";
  request.filterOptions.push_back (option);

  option.name = "EndResults";
  option.value = "50";
  request.filterOptions.push_back (option);
  

}


text_t GreenstoneAPI::resolveUrls(const text_t& unresolved, const text_t& collection, const text_t& archivedir)
{
  text_t resolved = unresolved;

  text_t httpprefix = _gsdlhome;
  text_t httpdocimg = httpprefix +"/collect/" + collection + "/index/assoc/" + archivedir;

  resolved.replace("[archivedir]",archivedir);
  resolved.replace("[assocfilepath]",archivedir);
  resolved.replace("[collection]",collection);
  resolved.replace("_httpprefix_",httpprefix);
  resolved.replace("_httpdocimg_",httpdocimg);

  return resolved;
}


text_t GreenstoneAPI::srclinkToUrl(const text_t& srclink, const text_t& collection, const text_t& archivedir)
{
  text_t srcurl;

  if (!srclink.empty()) {
    // extract URL
    text_tarray split_srclink;
    splitchar (srclink.begin(), srclink.end(), '\"', split_srclink);
    if (split_srclink.size()>=2) {
      srcurl = resolveUrls(split_srclink[1],collection,archivedir);
    }
  }

  return srcurl;
}


NSString* GreenstoneAPI::getSrcUrlMetadata(NSString* nscollection, NSString* nsgid)
{
  NSString* nssrclink = getMetadataFirst(nscollection,nsgid,@"srclink");
  if (nssrclink == nil) {
    return nil;
  }
  NSString* nsarchivedir = getMetadataFirst(nscollection,nsgid,@"archivedir");
  //NSString* nsassocfilepath = getMetadataFirst(nscollection,nsgid,@"assocfilepath"); // ****** used?

  char const* nscollection_cstr  = [nscollection UTF8String]; 
  char const* nssrclink_cstr  = [nssrclink UTF8String]; 
  char const* nsarchivedir_cstr  = [nsarchivedir UTF8String]; 

  text_t collection = nscollection_cstr;
  text_t srclink = nssrclink_cstr;
  text_t archivedir = nsarchivedir_cstr;

  text_t srcurl = srclinkToUrl(srclink,collection,archivedir);

  NSString* nsimage = getMetadataFirst(nscollection,nsgid,@"Image"); 
  char const* image_cstr  = [nsimage UTF8String]; 
  srcurl.replace("[Image]",image_cstr);

  NSString* nssrcurl = TextTToNSString(srcurl);
  
  return nssrcurl;
}




NSMutableArray* GreenstoneAPI::filterResponseToTitlesList(const text_t& collection, FilterResponse_t& sections)
{
  ResultDocInfo_tarray::iterator thissection = sections.docInfo.begin();
  ResultDocInfo_tarray::iterator endsection = sections.docInfo.end();

  int num_results = sections.docInfo.size();
  NSMutableArray* nsresultlist = nil;

  if (num_results>0) {
    nsresultlist = [[NSMutableArray alloc] initWithCapacity:num_results];

    while (thissection != endsection) {
      
      ResultDocInfo_t &docinfo = *thissection;
      text_t& oid = docinfo.OID;
      text_t title;
      text_t srcurl;

      MetadataInfo_tmap& metadata = docinfo.metadata;
      MetadataInfo_t& dctitle_info = metadata["dc.Title"];
      MetadataInfo_t& title_info = metadata["Title"];
      if ((dctitle_info.values.size()>0) && (!dctitle_info.values[0].empty())) {
	title = dctitle_info.values[0];
      }
      else if ((title_info.values.size()>0) && (!title_info.values[0].empty())) {
	title = title_info.values[0];
      }
      else {
	cerr << "Warning: could not find Title metadata for item" << endl;
      }

      MetadataInfo_t& archivedir_info = metadata["archivedir"];
      text_t archivedir = archivedir_info.values[0];
      
      MetadataInfo_t& srclink_info = metadata["srclink"];
      if (srclink_info.values.size()>0) {
	text_t& srclink = srclink_info.values[0];

	srcurl = srclinkToUrl(srclink,collection,archivedir);

	
	// ** think about embedding in routine?
	text_t imagemdname = "Image";
	NSString* nsimage = getMetadataFirst(collection,oid,imagemdname); 
	//char const* image_cstr  = [nsimage UTF8String]; 
	//text_t image = image_cstr;
	//srcurl.replace("[Image]",image);
	

      }
     
      NSString* nstitle = TextTToNSString(title);
      NSString* nsoid = TextTToNSString(oid);
      NSString* nssrcurl = TextTToNSString(srcurl); 

      GSResultItem* result_item = [[GSResultItem alloc] initWithOID:nsoid withTitle:nstitle withSrcUrl:nssrcurl];
      
      [nsresultlist addObject:result_item];

      ++thissection;
    }
  }

  return nsresultlist;
}

NSMutableArray* GreenstoneAPI::search_single_collection (const text_t& collection, const text_t& query)
{
  comerror_t comerr;
  FilterRequest_t request;
  FilterResponse_t response;

  NSMutableArray* nsresultlist = nil;
 
  // request.fields = metadata set
  request.fields.insert ("dc.Title"); 
  request.fields.insert ("Title"); 
  request.fields.insert ("srclink"); 
  request.fields.insert ("archivedir"); 
   
  // do the query
  request.filterResultOptions = FROID | FRmetadata | FRtermFreq;
  text_t formattedstring = query;

  // note! formattedstring is in unicode! mg and mgpp must convert!
  set_queryfilter_options (collection, request, formattedstring);

  _nproto->filter (collection, request, response, comerr, cerr);

  if (comerr != noError) {
    alert("Error",get_comerror_string(comerr));
    return nil;
  }
  else {
    nsresultlist = filterResponseToTitlesList(collection,response);
  }
  
  return nsresultlist;
}



NSMutableArray* GreenstoneAPI::query(NSString* nscollection, NSString* nsquery)
{
  char const* nscollection_cstr  = [nscollection UTF8String]; 
  char const* nsquery_cstr  = [nsquery UTF8String]; 

  text_t collection = nscollection_cstr;
  text_t query = nsquery_cstr;

  NSMutableArray* nsresultlist = search_single_collection(collection,query);

  return nsresultlist;
}

NSString* GreenstoneAPI::getDocument(NSString* nscollection, NSString* nsgid)
{
  char const* nscollection_cstr  = [nscollection UTF8String]; 
  char const* nsgid_cstr  = [nsgid UTF8String]; 

  text_t gid = nsgid_cstr;
  text_t collection = nscollection_cstr;

  DocumentRequest_t docrequest;
  DocumentResponse_t docresponse;
  comerror_t comerr;

  // get the text
  docrequest.OID = gid;
  _nproto->get_document(collection, docrequest, docresponse, comerr, cerr);
  
  if (comerr != noError) {
    alert("Error",get_comerror_string(comerr));
    return nil;
  }


  text_t& text = docresponse.doc;
  char* text_cstr = text.getcstr();
  NSString* nstext = [[NSString alloc] initWithCString:text_cstr];

  delete [] text_cstr;

  return nstext;
}






