#include "execpropagator.hh"
#include "error.hh"
#include "signal.h"


using namespace Gecode;


static bool sig_pipe_caught = false;

static void sig_pipe (int signo)
{
  // std::cout << "SIGPIPE caught" << std::endl;
  sig_pipe_caught = true;
}


ExecPropagator::ExecPropagator (Space& home, Int::IntView start, Int::IntView length, std::string& executable, GetBlockDataSource callback) :
  Propagator (home),
  m_callback (callback),
  ds (m_callback (home)),
  m_blocks (home, IntVarArgs (ds.blocks ())),
  m_unavailable (ds.unavailable ()),
  m_start (start),
  m_length (length),
  m_executable (executable)
{
  m_blocks.subscribe (home, *this, Int::PC_INT_VAL); 
  m_unavailable.subscribe (home, *this, Int::PC_INT_DOM);
  m_start.subscribe (home, *this, Int::PC_INT_VAL);
  m_length.subscribe (home, *this, Int::PC_INT_VAL);
  
  if (signal (SIGPIPE, sig_pipe) == SIG_ERR) {
    perror ("signal");
    error ("signal");
  }
}


ExecPropagator::ExecPropagator (Space& home, bool share, ExecPropagator& p) :
  Propagator (home, share, p),
  m_callback (p.m_callback),
  ds (m_callback (home)),
  m_executable (p.m_executable)
{
  m_blocks.update (home, share, p.m_blocks);
  m_unavailable.update (home, share, p.m_unavailable);
  m_start.update (home, share, p.m_start);
  m_length.update (home, share, p.m_length);
}


Propagator* ExecPropagator::copy (Space& home, bool share)
{
  return new (home) ExecPropagator (home, share, *this);
}


size_t ExecPropagator::dispose (Space& home)
{
  m_blocks.cancel (home, *this, Int::PC_INT_VAL);
  m_unavailable.cancel (home, *this, Int::PC_INT_DOM);
  m_start.cancel (home, *this, Int::PC_INT_VAL);
  m_length.cancel (home, *this, Int::PC_INT_VAL);
  (void) Propagator::dispose (home);
  return sizeof (*this);
}


PropCost ExecPropagator::cost (const Space& home, const ModEventDelta& med) const
{
  return PropCost::binary (PropCost::HI);
}


int64_t num_bytes_written_to_exec = 0;



ExecStatus ExecPropagator::propagate (Space& home, const ModEventDelta& med)
{
  if (!m_start.assigned () || !m_length.assigned ()) {
    return ES_FIX;
  } else if (!ds.blocks ().assigned ()) {
    return ES_FIX;
  } else {
    
    const int64_t start = m_start.val ();
    const int64_t length = m_length.val ();

    for (int64_t i = 0; i < length + BLOCK_SIZE; i += BLOCK_SIZE) {
      int block_num = (int) ((start + i) / BLOCK_SIZE);
      if (m_unavailable.in (block_num)) {
        return ES_FIX;
      }
    }

    FILE* out;

    sig_pipe_caught = false;
    bool hit_eof = false;

    if ( (out = popen (m_executable.c_str (), "w")) == NULL ) {
      perror ("popen");
      error ("popen");
    }

    const bool use_fwrite_implementation = false;

    if (use_fwrite_implementation) {
      unsigned char buffer[BLOCK_SIZE];
      int64_t i = start; 
      const int64_t end = start + length;
      while (!sig_pipe_caught && i < end) {
        ds.read (i / BLOCK_SIZE, buffer);
        int pos = (int) (i % BLOCK_SIZE);
        int num_to_write = BLOCK_SIZE - pos;
        int num_written;
        while ( (num_written = fwrite (&(buffer[pos]), 1, num_to_write, out)) != num_to_write) {
          pos += num_written;
          num_to_write -= num_written;
          if (num_written == 0) {
            std::cout << "fwrite zero" << std::endl;
          } else if (num_written < 0) {
            std::cout << "fwrite error; num_written = " << num_written << std::endl;
            hit_eof = true;
            break;
          } else if (ferror (out)) {
            clearerr (out);
            std::cout << "fwrite error; ferror" << std::endl;
            hit_eof = true;
            break;
          } else {
            num_bytes_written_to_exec += num_written;
          }
        }
        i += num_written;
      }
    } else {
      for (int64_t i = 0; !sig_pipe_caught && i < length; i++) {
        int64_t o = start + i;
        unsigned char ch = ds.read (o);

        int result = fputc (ch, out);
        num_bytes_written_to_exec++;
        if (result == EOF) {
          hit_eof = true;
          break;
        }
      }
    }

    int status;

    if ( (status = pclose (out)) == -1 ) {
      perror ("pclose");
      error ("pclose");
    }

    if (hit_eof) {
      return ES_FAILED;
    } else if (sig_pipe_caught) {
      return ES_FAILED;
    } else if (WIFEXITED (status)) {
      if (status == 0) { 
        return home.ES_SUBSUMED (*this);
      } else {
        return ES_FAILED;
      }
    } else {
      return ES_FAILED;
    }
  }
}


ExecStatus ExecPropagator::post (Space& home, Int::IntView start, Int::IntView length, std::string& executable, GetBlockDataSource callback)
{
  (void) new (home) ExecPropagator (home, start, length, executable, callback);
  return ES_OK;
}


void exec_propagator (Space& home, IntVar start, IntVar length, std::string& executable, GetBlockDataSource callback)
{
  Int::IntView startV (start);
  Int::IntView lengthV (length);
  if (ExecPropagator::post (home, startV, lengthV, executable, callback) != ES_OK) {
    home.fail ();
  }
}


