#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <sstream>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>

#include <glibmm/fileutils.h>

#include <arc/ArcLocation.h>
#include <arc/Run.h>
#include <arc/message/PayloadSOAP.h>
#include <arc/FileUtils.h>

#include "grid-manager/files/ControlFileHandling.h"
#include "job.h"
#include "arex.h"

namespace ARex {


int ARexService::OpenInfoDocument() {
  int h = infodoc_.OpenDocument();
  if (h == -1) {
    // If information collector has no file assigned (not yet or collector is not running) open it directly
    h = open(config_.InformationFile().c_str(), O_RDONLY);
  }
  return h;
}

void ARexService::InformationCollector(void) {
  thread_count_.RegisterThread();
  for(;;) {
    // Run information provider
    std::string xml_str;
    int r = -1;
    {
      std::string cmd;
      cmd=Arc::ArcLocation::GetDataDir()+"/CEinfo.pl --splitjobs --config "+config_.ConfigFile();
      std::string stdin_str;
      std::string stderr_str;
      Arc::Run run(cmd);
      run.AssignStdin(stdin_str);
      run.AssignStdout(xml_str, 1024*1024); // can information document become bigger than 1MB?
      run.AssignStderr(stderr_str);
      logger_.msg(Arc::DEBUG,"Resource information provider: %s",cmd);
      if(!run.Start()) {
        // Failed to fork proces
        logger_.msg(Arc::DEBUG,"Resource information provider failed to start");
      } else {
        if(!run.Wait()) {
          logger_.msg(Arc::DEBUG,"Resource information provider failed to run");
        } else {
          r = run.Result();
          if (r!=0) {
            logger_.msg(Arc::WARNING,"Resource information provider failed with exit status: %i\n%s",r,stderr_str);
          } else {
            logger_.msg(Arc::DEBUG,"Resource information provider log:\n%s",stderr_str);
          };
        };
      };
    };
    if (r!=0) {
      logger_.msg(Arc::WARNING,"No new informational document assigned");
    } else {
      logger_.msg(Arc::VERBOSE,"Obtained XML: %s",xml_str.substr(0,100));
      // Following code is suboptimal. Most of it should go away
      // and functionality to be moved to information providers.
      if(!xml_str.empty()) {
        // Currently glue states are lost. Counter of all jobs is lost too.
        infodoc_.Assign(xml_str, config_.InformationFile());
        Arc::XMLNode root = infodoc_.Acquire();
        Arc::XMLNode all_jobs_count = root["Domains"]["AdminDomain"]["Services"]["ComputingService"]["AllJobs"];
        if((bool)all_jobs_count) {
          Arc::stringto((std::string)all_jobs_count,all_jobs_count_);
          all_jobs_count.Destroy(); // is not glue2 info
        };
        infodoc_.Release();
      } else {
        logger_.msg(Arc::ERROR,"Informational document is empty");
      };
    };
    if(thread_count_.WaitOrCancel(infoprovider_wakeup_period_*100)) break;
  };
  thread_count_.UnregisterThread();
}

std::string ARexService::getID() {
  return "ARC:AREX";
}

class PrefixedFilePayload: public Arc::PayloadRawInterface {
 private:
  std::string prefix_;
  std::string postfix_;
  int handle_;
  void* addr_;
  off_t length_;
 public:
  PrefixedFilePayload(const std::string& prefix,const std::string& postfix,int handle) {
    prefix_ = prefix;
    postfix_ = postfix;
    handle_ = handle;
    addr_ = MAP_FAILED;
    length_ = 0;
    if(handle != -1) {
      struct stat st;
      if(::fstat(handle,&st) == 0) {
        if(st.st_size > 0) {
          length_ = st.st_size;
          addr_ = ::mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,handle,0);
          if(addr_ == MAP_FAILED) length_=0;
        };
      };
    };
  };
  ~PrefixedFilePayload(void) {
    if(addr_ != MAP_FAILED) ::munmap(addr_,length_);
    if(handle_ != -1) ::close(handle_);
  };
  virtual char operator[](Size_t pos) const {
    char* p = ((PrefixedFilePayload*)this)->Content(pos);
    if(!p) return 0;
    return *p;
  };
  virtual char* Content(Size_t pos) {
    if(pos < prefix_.length()) return (char*)(prefix_.c_str() + pos);
    pos -= prefix_.length();
    if(pos < length_) return ((char*)(addr_) + pos);
    pos -= length_;
    if(pos < postfix_.length()) return (char*)(postfix_.c_str() + pos);
    return NULL;
  };
  virtual Size_t Size(void) const {
    return (prefix_.length() + length_ + postfix_.length());
  };
  virtual char* Insert(Size_t /* pos */ = 0,Size_t /* size */ = 0) {
    return NULL;
  };
  virtual char* Insert(const char* /* s */,Size_t /* pos */ = 0,Size_t /* size */ = -1) {
    return NULL;
  };
  virtual char* Buffer(unsigned int num = 0) {
    if(num == 0) return (char*)(prefix_.c_str());
    if(addr_ != MAP_FAILED) {
      if(num == 1) return (char*)addr_;
    } else {
      ++num;
    };
    if(num == 2) return (char*)(postfix_.c_str());
    return NULL;
  };
  virtual Size_t BufferSize(unsigned int num = 0) const {
    if(num == 0) return prefix_.length();
    if(addr_ != MAP_FAILED) {
      if(num == 1) return length_;
    } else {
      ++num;
    };
    if(num == 2) return postfix_.length();
    return 0;
  };
  virtual Size_t BufferPos(unsigned int num = 0) const {
    if(num == 0) return 0;
    if(addr_ != MAP_FAILED) {
      if(num == 1) return prefix_.length();
    } else {
      ++num;
    };
    if(num == 2) return (prefix_.length() + length_);
    return (prefix_.length() + length_ + postfix_.length());
  };
  virtual bool Truncate(Size_t /* size */) { return false; };
};

OptimizedInformationContainer::OptimizedInformationContainer(bool parse_xml) {
  handle_=-1;
  parse_xml_=parse_xml;
}

OptimizedInformationContainer::~OptimizedInformationContainer(void) {
  if(handle_ != -1) ::close(handle_);
  if(!filename_.empty()) ::unlink(filename_.c_str());
}

int OptimizedInformationContainer::OpenDocument(void) {
  int h = -1;
  olock_.lock();
  if(handle_ != -1) h = ::dup(handle_);
  olock_.unlock();
  return h;
}

void OptimizedInformationContainer::Assign(const std::string& xml, const std::string filename) {
  std::string tmpfilename;
  int h = -1;
  if(filename.empty()) {
    h = Glib::file_open_tmp(tmpfilename);
  } else {
    tmpfilename = filename;
    tmpfilename += ".tmpXXXXXX";
    h = Glib::mkstemp(tmpfilename);
  };
  if(h == -1) {
    Arc::Logger::getRootLogger().msg(Arc::ERROR,"OptimizedInformationContainer failed to create temporary file");
    return;
  };
  Arc::Logger::getRootLogger().msg(Arc::VERBOSE,"OptimizedInformationContainer created temporary file: %s",tmpfilename);
  for(std::string::size_type p = 0;p<xml.length();++p) {
    ssize_t l = ::write(h,xml.c_str()+p,xml.length()-p);
    if(l == -1) {
      ::unlink(tmpfilename.c_str());
      ::close(h);
      Arc::Logger::getRootLogger().msg(Arc::ERROR,"OptimizedInformationContainer failed to store XML document to temporary file");
      return;
    };
    p+=l;
  };
  Arc::XMLNode newxml(parse_xml_?xml:std::string());
  if(parse_xml_ && !newxml) {
    ::unlink(tmpfilename.c_str());
    ::close(h);
    Arc::Logger::getRootLogger().msg(Arc::ERROR,"OptimizedInformationContainer failed to parse XML");
    return;
  };
  // Here we have XML stored in file and optionally parsed
  // Attach to new file
  olock_.lock();
  if(filename.empty()) {
    if(!filename_.empty()) ::unlink(filename_.c_str());
    filename_ = tmpfilename;
  } else {
    if(::rename(tmpfilename.c_str(), filename.c_str()) != 0) {
      olock_.unlock();
      Arc::Logger::getRootLogger().msg(Arc::ERROR,"OptimizedInformationContainer failed to rename temporary file");
      return;
    };
    // Do not delete old file if same name requested - it is removed by rename()
    if(!filename_.empty()) if(filename_ != filename) ::unlink(filename_.c_str());
    filename_ = filename;
  };
  if(handle_ != -1) ::close(handle_);
  handle_ = h;
  if(parse_xml_) {
    // Assign parsed xml
    lock_.lock();
    doc_.Swap(newxml);
    lock_.unlock();
    Arc::InformationContainer::Assign(doc_,false);
  };
  olock_.unlock();
}

}

