<?php


namespace CMSMSExt\FS;


use CMSMSExt\Exceptions\FileSystemException;

class DirComparor
{
  private $_a;
  private $_b;
  private $_list_a;
  private $_list_b;
  private $_do_md5;
  private $_has_run = NULL;
  private $_base_dir;
  private $_ignored = [];
  private $_donotdelete;
  
  public function __construct(string $dir_a, string $dir_b, bool $do_md5 = FALSE)
  {
    if(!\is_dir($dir_a))
    {
      throw new FileSystemException(\xt_utils::get_xt(), \CMSMSExt\core\Error::INVALID_DIR_ERROR, $dir_a);
    }
    if(!\is_readable($dir_a))
    {
      throw new FileSystemException('Directory ' . $dir_a . ' is not readable');
    }
    if(!\is_dir($dir_b))
    {
      throw new FileSystemException('Invalid directory ' . $dir_b);
    }
    if(!\is_readable($dir_b))
    {
      throw new FileSystemException('Directory ' . $dir_b . ' is not readable');
    }
    
    $this->_a      = $dir_a;
    $this->_b      = $dir_b;
    $this->_do_md5 = $do_md5;
    $this->_list_a = [];
    $this->_list_b = [];
    $this->_donotdelete = [];
  }
  
//  public function do_not_delete($in)
//  {
//    if(!$in) { return; }
//
//    if(!\is_array($in)) { $in = [$in]; }
//
//    foreach($in as $one)
//    {
//      $one = \trim($one);
//
//      if($one) { $this->_donotdelete[] = $one; }
//    }
//  }
  
  public function ignore($in)
  {
    if(!$in) { return; }
    
    if(!\is_array($in)) { $in = [$in]; }
    
    foreach($in as $one)
    {
      $one = \trim($one);
      
      if($one) { $this->_ignored[] = $one; }
    }
  }
  
  private function _set_base(string $dir)
  {
    $this->_base_dir = $dir;
  }
  
  private function _get_base()
  {
    return $this->_base_dir;
  }
  
  private function _is_ignored(string $filename) : bool
  {
    foreach($this->_ignored as $pattern)
    {
      if($pattern === $filename || \fnmatch($pattern, $filename, \FNM_CASEFOLD)) { return TRUE; }
    }
    
    return FALSE;
  }
  
  private function _read_dir($dir = NULL)
  {
    if(!$dir) { $dir = $this->_base_dir; }
    
    if(!$dir)
    {
      throw new FileSystemException('No directory specified to _read_dir');
    }
    
    $out = [];
    $tmp = [];
    $dh  = \opendir($dir);
    
    if(!$dh)
    {
      throw new FileSystemException('Problem getting directory handle for ' . $dir);
    }
    
    while( FALSE !== ( $file = \readdir($dh) ) )
    {
      if('.' === $file || '..' === $file) { continue; }
      
      $fn = $dir . \DIRECTORY_SEPARATOR . $file;
      
      if( $this->_is_ignored($file) ) { continue; }
      
      $base = \substr( $fn, \strlen( $this->_get_base() ) );
      
      if( \is_dir($fn) )
      {
        $tmp[]        = $this->_read_dir($fn);
        //$out          = array_merge($out, $tmp);
        $rec          = [];
        $rec['size']  = @\filesize($fn);
        $rec['mtime'] = @\filemtime($fn);
        
        if($this->_do_md5) { $rec['md5'] = \md5_file($fn); }
        
        $out[$base] = $rec;
        continue;
      }
      
      if(!\is_readable($fn))
      {
        \audit(80085, __CLASS__, "$fn is not readable");
        continue;
      }
      
      $rec          = [];
      $rec['size']  = @\filesize($fn);
      $rec['mtime'] = @\filemtime($fn);
      
      if($this->_do_md5) { $rec['md5'] = \md5_file($fn); }
      
      $out[$base] = $rec;
    }
    
    $out = \array_merge($out, ...$tmp);
    
    return $out;
  }
  
  public function run()
  {
    if($this->_has_run) { return; }
    
    $this->_has_run = TRUE;
    
    $this->_set_base($this->_a);
    $this->_list_a = $this->_read_dir();
    $this->_set_base($this->_b);
    $this->_list_b = $this->_read_dir($this->_b);
  }
  
  public function get_new_files() : array
  {
    $this->run();
    
    // get all the files in b that are not in a
    $tmp_a = \array_keys($this->_list_a);
    $tmp_b = \array_keys($this->_list_b);
    
    return \array_diff($tmp_b, $tmp_a);
  }
  
  public function get_deleted_files() : array
  {
    $this->run();
    
    // get all the files in b that are not in a
    $tmp_a = \array_keys($this->_list_a);
    $tmp_b = \array_keys($this->_list_b);
    $out   = \array_diff($tmp_a, $tmp_b);
    
    if(\count($out) && \count($this->_donotdelete))
    {
      $new_out = [];
      
      foreach($out as $file)
      {
        $skipped = FALSE;
        foreach($this->_donotdelete as $nd)
        {
          if(startswith($file, $nd))
          {
            // skip this file at this stage.
            $skipped = TRUE;
            break;
          }
        }
        if(!$skipped)
        {
          $new_out[] = $file;
        }
        else
        {
          \audit(80085, __CLASS__, 'skipped ' . $file . ', it is in the notdeleted list');
        }
      }
      
      $out = $new_out;
    }
    
    return $out;
  }
  
  public function get_changed_files() : array
  {
    $this->run();
    
    $out = [];
    
    foreach($this->_list_a as $path => $rec_a)
    {
      if(!isset($this->_list_b[$path])) { continue; } // deleted/moved in b.
      
      $rec_b = $this->_list_b[$path];
      
      if(
        $rec_a['size'] != $rec_b['size'] || $rec_a['mtime'] != $rec_b['mtime'] ||
        (isset($rec_a['md5'], $rec_b['md5']) && $rec_a['md5'] != $rec_b['md5'])
      ) { $out[] = $path; }
    }
    
    return $out;
  }
  
}

?>