#include "ntfsrunlistpropagator.hh"
#include "ntfsmftentry.hh"
#include "ntfsmftentryreader.hh"


using namespace Gecode;


NTFSRunlistPropagator::NTFSRunlistPropagator (Space& home, Int::IntView clusterStart, Int::IntView offsetStart, Int::IntView offsetDATA, ViewArray<Int::IntView> blocks, IntVar offsetInternal, IntVar fileLength, GetDataSource callback, NTFSMFTEntryXML& xmlout) : 
  Propagator (home),
  m_callback (callback),
  ds (m_callback (home)),
  m_clusterStart (clusterStart),
  m_offsetStart (offsetStart),
  m_offsetDATA (offsetDATA),
  m_blocks (blocks),
  m_offsetInternal (offsetInternal),
  m_fileLength (fileLength),
  m_xmlout (xmlout)
{
  m_clusterStart.subscribe (home, *this, Int::PC_INT_VAL);
  m_offsetStart.subscribe (home, *this, Int::PC_INT_VAL);
  m_offsetDATA.subscribe (home, *this, Int::PC_INT_VAL);
  m_blocks.subscribe (home, *this, Int::PC_INT_VAL);
}


NTFSRunlistPropagator::NTFSRunlistPropagator (Space& home, bool share, NTFSRunlistPropagator& p) : 
  Propagator (home, share, p),
  m_callback (p.m_callback),
  ds (m_callback (home)),  
  m_xmlout (p.m_xmlout)
{
  m_clusterStart.update (home, share, p.m_clusterStart);
  m_offsetStart.update (home, share, p.m_offsetStart);
  m_offsetDATA.update (home, share, p.m_offsetDATA);
  m_blocks.update (home, share, p.m_blocks);
  m_offsetInternal.update (home, share, p.m_offsetInternal);
  m_fileLength.update (home, share, p.m_fileLength);
}


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


size_t NTFSRunlistPropagator::dispose (Space& home) 
{
  m_clusterStart.cancel (home, *this, Int::PC_INT_VAL);
  m_offsetStart.cancel (home, *this, Int::PC_INT_VAL);
  m_offsetDATA.cancel (home, *this, Int::PC_INT_VAL);
  m_blocks.cancel (home, *this, Int::PC_INT_VAL);
  (void) Propagator::dispose (home);
  return sizeof (*this);
}


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


int throwscount[12];

std::string current_filename;


ExecStatus NTFSRunlistPropagator::propagate (Space& home, const ModEventDelta& med) 
{
  if (m_clusterStart.assigned () && m_offsetStart.assigned ()) {
    printf ("throwscount = %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d\n", 
            throwscount[1], throwscount[2], throwscount[3], throwscount[4], 
            throwscount[5], throwscount[6], throwscount[7], throwscount[8],
            throwscount[9], throwscount[10], throwscount[11]);
            

    int64_t clusterStart = m_clusterStart.val ();
    clusterStart *= BLOCK_SIZE;
    int64_t offsetStart = m_offsetStart.val ();
    int64_t offset = clusterStart + offsetStart;
    std::cout << std::endl;
    std::cout << "Running NTFSRunlistPropagator: " << clusterStart << " / " << offsetStart << " / " << offset << std::endl;

    try {
      NTFSMFTEntryReader reader;
      NTFSMFTEntry entry = reader.read_entry (offset, ds);

      if (entry.get_resVnonres () == 0) {
        std::cout << "Filename: " << entry.get_filename () << std::endl;
        std::cout << "Length: " << entry.get_length () << std::endl;
        std::cout << "Entry Location: " << entry.get_entryLocation () << std::endl;
        std::cout << "Offset Within Entry: " << entry.get_offsetInEntry () << std::endl;
        std::cout << std::dec;

        std::cout << "FAILED: ignoring resident files" << std::endl;
        return ES_FAILED; 
        
      } else if (entry.get_resVnonres () == 1) {
        
        current_filename = entry.get_filename ();

        std::cout << "Filename: " << entry.get_filename () << std::endl;
        std::cout << "Length: " << entry.get_length () << std::endl;
	std::cout << "$DATA offset: " << entry.get_offsetDATAattr () << std::endl;
        std::cout << "Clusters: " << std::endl;
        std::cout << std::dec;

        int64_t max = Int::Limits::max;

        int64_t length = entry.get_length ();
        if (length > max || length >= BLOCK_SIZE * m_blocks.size ()) {
          std::cout << "FAILED: file length outside range" << std::endl;
          return ES_FAILED; 
        }
        int truncated_length = (int) length;

	int64_t offsetDATA = entry.get_offsetDATAattr ();
	if (offsetDATA >= (BLOCK_SIZE*2)) {
	  std::cout << "FAILED: $DATA offset outside of range" << std::endl;
	  return ES_FAILED;
	}
	int truncated_offsetDATA = (int) offsetDATA;

        GECODE_ME_CHECK (m_fileLength.eq (home, truncated_length));
        GECODE_ME_CHECK (m_offsetInternal.eq (home, 0));
	GECODE_ME_CHECK (m_offsetDATA.eq (home, truncated_offsetDATA));

        std::vector<int64_t> clusters = entry.get_clusters ();
        unsigned int count = 0; 
        unsigned int num_blocks = m_blocks.size ();
        for (std::vector<int64_t>::const_iterator it = clusters.begin (); it != clusters.end (); ++it, count++) {
          if (count >= num_blocks) {
            std::cout << "FAILED: file will not fit in block array; count = " << count << "; num_blocks = " << num_blocks << "; clusters.size () = " << clusters.size () << std::endl;
            return ES_FAILED; 
          }
          int64_t cluster = *it;
          if (cluster > max) {
            std::cout << "FAILED: cluster outside range" << std::endl;
            return ES_FAILED; 
          }
          int truncated_cluster = (int) cluster;
          GECODE_ME_CHECK (m_blocks[count].eq (home, truncated_cluster));
        }
        // zero the remainder of the m_blocks array
        for (; count < num_blocks; count++) {
          GECODE_ME_CHECK (m_blocks[count].eq (home, 0));
        }

	/* outputs to xml file */
	m_xmlout.output_into_XML ( entry, offset );
        
        return home.ES_SUBSUMED (*this);
      } else {
	std::cout << "FAILED: resident flag not 0 or 1" << std::endl;
        return ES_FAILED; 
      } 

      GECODE_NEVER;

    } catch (int e) {
      throwscount[e]++;
      std::cout << std::dec;
      return ES_FAILED; 
    }
    
  } else {
    return ES_FIX;
  }
  GECODE_NEVER;
}


ExecStatus NTFSRunlistPropagator::post (Space& home, Int::IntView clusterStart, Int::IntView offsetStart, Int::IntView offsetDATA, ViewArray<Int::IntView> blocks, IntVar offsetInternal, IntVar fileLength, GetDataSource callback, NTFSMFTEntryXML& xmlout)
{
  (void) new (home) NTFSRunlistPropagator (home, clusterStart, offsetStart, offsetDATA, blocks, offsetInternal, fileLength, callback, xmlout);
  return ES_OK;
}


void ntfs_runlist (Space& home, IntVar clusterStart, IntVar offsetStart, IntVar offsetDATA, IntVarArray blocks, IntVar offsetInternal, IntVar fileLength, GetDataSource callback, NTFSMFTEntryXML& xmlout)
{
  Int::IntView clusterStartV (clusterStart);
  Int::IntView offsetStartV (offsetStart);
  Int::IntView offsetDATAV (offsetDATA);
  Int::IntView offsetInternalV (offsetInternal);
  Int::IntView fileLengthV (fileLength);
  ViewArray<Int::IntView> blocksV (home, IntVarArgs (blocks));
  if (NTFSRunlistPropagator::post (home, clusterStartV, offsetStartV, offsetDATAV, blocksV, offsetInternalV, fileLengthV, callback, xmlout) != ES_OK) {
    home.fail ();
  }
}

