#include "forensics-gecode.hh"
#include "error.hh"
#include "util.hh"
#include "filewrapper.hh"
#include "imagedatasource.hh"
#include "shiftdatasource.hh"
#include "interdatasource.hh"
#include "blockdatasource.hh"
#include "minsearch.hh"
#include "patternminsearcher.hh"
#include "execpropagator.hh"
#include "extract.hh"
#include "ntfsbitmap.hh"
#include "ntfsallocation.hh"
#include "ntfsrunlistpropagator.hh"
#include "ntfsmftentryxml.hh"

using namespace Gecode;

/* ************************************************************************************************** */

DataSource& getFileDS (Space& home);
DataSource& getInterDS (Space& home);
BlockDataSource& getBlocksDS (Space& home);

/* ************************************************************************************************** */

const int max_file_length_to_search_for = 1 * 1024 * 1024;

class MySpace : public Space
{
public:
  const int num_clusters;
  filewrapper& fw;
  ImageDataSource fileDS;
  ShiftDataSource shiftDS;
  InterDataSource interDS;
  BlockDataSource blocksDS;
  IntVar clusterFILE;
  IntVar offsetFILE;
  IntVar offsetDATA;
  IntVar offsetInternal;
  IntVar fileLength;
  NTFSMFTEntryXML& xmlout;

  MySpace (filewrapper& fw, NTFSMFTEntryXML& xmlout) : 
    Space (),
    num_clusters ((int) ((fw.size () / BLOCK_SIZE) - 63 - 1)),
    fw (fw),
    fileDS (*this, fw),
    shiftDS (*this, fileDS, 63),
    interDS (*this, shiftDS, IntSet (0, Int::Limits::max)),
    blocksDS (*this, interDS, (max_file_length_to_search_for / BLOCK_SIZE) + 1, 0, num_clusters), // +1 needed when offsetStart need not be zero.
    clusterFILE (*this, 0, num_clusters),
    offsetFILE (*this, 0, BLOCK_SIZE - 1),
    offsetDATA (*this, 0, BLOCK_SIZE*2 - 1), // is BLOCK_SIZE correct to use here??
    offsetInternal (*this, 0, num_clusters),
    fileLength (*this, 0, max_file_length_to_search_for),
    xmlout (xmlout)
  {
    unsigned char file_pattern_array[] = "FILE";
    std::list<unsigned char> file_pattern (&file_pattern_array[0], &file_pattern_array[4]);
    rel (*this, offsetFILE == 0);

    IntVar clusterFILEE (*this, 0, num_clusters);
    IntVar offsetFILEE (*this, 0, BLOCK_SIZE - 1);
    pattern_min_search (*this, clusterFILE, offsetFILE, clusterFILEE, offsetFILEE, file_pattern, getInterDS);

    ntfs_runlist (*this, clusterFILE, offsetFILE, offsetDATA, blocksDS.blocks (), offsetInternal, fileLength, getInterDS, xmlout);

    IntVarArgs l (2);
    l[0] = clusterFILE;
    l[1] = offsetFILE;
    
    branch (*this, l, INT_VAR_NONE, INT_VAL_SPLIT_MIN);
  }


  MySpace (bool share, MySpace& s) :
    Space (share, s),
    num_clusters (s.num_clusters),
    fw (s.fw),
    fileDS (share, *this, s.fileDS),
    shiftDS (share, *this, fileDS, s.shiftDS),
    interDS (share, *this, shiftDS, s.interDS),
    blocksDS (share, *this, interDS, s.blocksDS),
    xmlout (s.xmlout)
  {
    clusterFILE.update (*this, share, s.clusterFILE);
    offsetFILE.update (*this, share, s.offsetFILE);
    offsetDATA.update (*this, share, s.offsetDATA);
    offsetInternal.update (*this, share, s.offsetInternal);
    fileLength.update (*this, share, s.fileLength);
  }


  virtual Space* copy (bool share) 
  {
    MySpace* result = new MySpace (share, *this);
    return result;
  }


  void print (std::ostream& os) const
  {
    os << "fileDS = ";
    fileDS.print (os) ;
    os << std::endl;
    double percent = 0;
    if (clusterFILE.assigned()) {
      percent = 100*clusterFILE.val ();
    }
    percent /= num_clusters;
    os << "clusterFILE = " << clusterFILE << " out of " << num_clusters << " [ " << percent << "% ]"
       << ", offsetFILE = " << offsetFILE
       << ", offsetDATA = " 
       << offsetDATA
       << ", offsetInternal = " << offsetInternal
       << ", fileLength = " << fileLength
       << ", filename = " << current_filename;

    os << std::endl;
  }

  void print (void) const
  {
    print (std::cout);
  }

};

/* ************************************************************************************************** */

DataSource& getFileDS (Space& home)
{
  MySpace& myhome = static_cast<MySpace&> (home);
  DataSource& result = myhome.fileDS;
  return result;
}

/* ************************************************************************************************** */

DataSource& getInterDS (Space& home)
{
  MySpace& myhome = static_cast<MySpace&> (home);
  DataSource& result = myhome.interDS;
  return result;
}

/* ************************************************************************************************** */

BlockDataSource& getBlocksDS (Space& home)
{
  MySpace& myhome = static_cast<MySpace&> (home);
  BlockDataSource& result = myhome.blocksDS;
  return result;
}

/* ************************************************************************************************** */

int main1 (int argc, char** argv) 
{
  std::cout << "sizeof (off_t) = " << sizeof (off_t) << std::endl;

  try {

    if (argc != 2) error ("wrong number of arguments");

    filewrapper fw (argv[1]);

    NTFSMFTEntryXML xmlout;

    // create model
    MySpace* m = new MySpace (fw, xmlout);

    std::cout << std::endl << "Results of trivial propagation" << std::endl << std::endl;
    m->print ();
    std::cout << std::endl;

    std::cout << "Results of main propagation" << std::endl << std::endl;
    m->status ();
    m->print ();
    std::cout << std::endl;

    std::cout << "Create search engine" << std::endl;
    std::cout << std::endl << "===================================================================" << std::endl << std::endl;

    Search::Options options;
    options.threads = 1; // one thread
    // options.threads = -1; // all but one thread

    DFS<MySpace> e (m, options);
    int num_solutions = 0;
    while (MySpace* s = e.next ()) {

      print_statistics (std::cout, e.statistics ());
      s->print ();
      delete s;
      num_solutions++;

      std::cout << std::endl << "===================================================================" << std::endl << std::endl;
    }

    std::cout << std::endl;

    print_statistics (std::cout, e.statistics ());
    std::cout << "number of solutions = " << num_solutions << std::endl;

    delete m;

    std::cout << "cache hits = " << cache_hits << "; cache_misses = " << cache_misses << std::endl;
    std::cout << "pointless_finds = " << pointless_finds << std::endl;
    std::cout << "num_bytes_written_to_exec = " << num_bytes_written_to_exec << std::endl;

    //    xmlfile.close();

  } catch (Exception e) {
    std::cerr << "Gecode exception: " << e.what () << std::endl;
    return 1;
  }

  return 0;
}

/* ************************************************************************************************** */

int main2 (int argc, char** argv) 
{
  std::cout << "sizeof (off_t) = " << sizeof (off_t) << std::endl;

  try {

    if (argc != 2) error ("wrong number of arguments");

    filewrapper fw (argv[1]);

    NTFSMFTEntryXML xmlout;

    // create model
    MySpace* m = new MySpace (fw, xmlout);

    std::cout << std::endl << "Results of trivial propagation" << std::endl << std::endl;
    m->print ();
    std::cout << std::endl;

    std::cout << "Results of main propagation" << std::endl << std::endl;
    m->status ();
    m->print ();
    std::cout << std::endl;

    std::cout << "Create search engine" << std::endl;
    std::cout << std::endl << "===================================================================" << std::endl << std::endl;

    Gist::Print<MySpace> p ("Print solution");
    Gist::Options o;
    o.inspect.click (&p);
    Gist::dfs (m, o);
    delete m;

  } catch (Exception e) {
    std::cerr << "Gecode exception: " << e.what () << std::endl;
    return 1;
  }

  return 0;
}

/* ************************************************************************************************** */

int main (int argc, char** argv) 
{
  // std::ios::sync_with_stdio (false);
  return main1 (argc, argv);
}

/* ************************************************************************************************** */
