# Copyright (c) 2023 Yuki Kimoto
# MIT License

class File::Path {
  version "0.015";
  
  use Hash;
  use StringList;
  use Sys::IO;
  use File::Basename;
  use File::Spec;
  
  static method mkpath : int ($path : string, $options : object[] = undef) {
    
    unless ($path) {
      die "The \$path must be defined";
    }
    
    unless (length $path >= 0) {
      die "The length of \$path must be greater than or equal to 0";
    }
    
    my $options_h = Hash->new($options);
    
    my $mode = $options_h->get_or_default("mode", -1)->(int);
    
    if ($mode < 0) {
      $mode = 0777;
    }
    
    my $created = 0;
    &_mkpath_recursive($path, \$created, $mode);
    
    return $created;
  }
  
  private static method _mkpath_recursive : void ($path : string, $created_ref : int*, $mode : int) {
    unless (Sys->d($path)) {
      
      my $parent = File::Basename->dirname($path);
      
      unless (Sys->d($parent) || $path eq $parent ) {
        &_mkpath_recursive($parent, $created_ref, $mode);
      }
      
      Sys::IO->mkdir($path, $mode);
      $$created_ref++;
    }
  }
  
  static method make_path : int ($path : string, $options : object[] = undef) {
    return &mkpath($path, $options);
  }

  static method rmtree : int ($path : string, $options : object[] = undef) {

    unless ($path) {
      die "The \$path must be defined";
    }
    
    unless (length $path >= 0) {
      die "The length of \$path must be greater than or equal to 0";
    }
    
    my $options_h = Hash->new($options);
    my $keep_root = $options_h->get_or_default("keep_root", 0)->(int);
    
    my $removed_count  = 0;
    &_rmtree_recursive($path, $path, \$removed_count, $keep_root);
    
    return $removed_count;
  }

  private static method _rmtree_recursive : void ($root : string, $path : string, $removed_count_ref : int*, $keep_root : int) {
    my $curdir = File::Spec->curdir;
    my $updir  = File::Spec->updir;
    
    if (Sys->d($path)) {
      my $dh = Sys::IO->opendir($path);
      my $file_base_names_list = StringList->new;
      
      while (my $dir_ent = Sys::IO->readdir($dh)) {
        my $file_base_name = $dir_ent->d_name;
        if ($file_base_name eq $updir) {
          next;
        }
        
        if ($file_base_name eq $curdir) {
          next;
        }
        
        $file_base_names_list->push($file_base_name);
      }
      
      if ($file_base_names_list->length > 0) {
        my $file_base_names = $file_base_names_list->to_array;
        for my $file_base_name (@$file_base_names) {
          my $child_dir = "$path/$file_base_name";
          &_rmtree_recursive($root, $child_dir, $removed_count_ref, $keep_root);
        }
      }
      
      my $not_remove = 0;
      if ($keep_root && $path eq $root) {
        $not_remove = 1;
      }
      
      unless ($not_remove) {
        Sys::IO->rmdir($path);
        $$removed_count_ref++;
      }
    }
    else {
      Sys::IO->unlink($path);
      $$removed_count_ref++;
    }
  }
  
  static method remove_tree : int ($path : string, $options : object[] = undef) {
    return &rmtree($path, $options);
  }
}