package DX::Lib::FS;

use DX::Lib::FS::Observation::PathStatus;
use DX::Lib::FS::Action::CreateDirectory;
use DX::Lib::FS::Action::CreateFile;
use DX::Lib::FS::Action::SetPathMode;
use DX::Lib::FS::Observation::FileContent;
use DX::Lib::FS::Action::RewriteFile;
use File::Spec;
use DX::SetOver;
use Moo;

our @RULES = (
  [ path_status => [ qw(PS) ],
    [ member_of => 'PS', \'path_status' ] ],
  [ path => [ qw(PS P) ],
    [ prop => 'PS', \'path', 'P' ] ],
  [ info_prop => [ qw(PS N V) ],
    [ exists => [ qw(PSI) ],
      [ prop => 'PS', \'info', 'PSI' ],
      [ prop => 'PSI', 'N', 'V' ] ] ],
  [ mode => [ qw(PS M) ],
    [ info_prop => 'PS', \'mode', 'M' ] ],
  [ exists_path => [ qw(PS) ],
    [ info_prop => 'PS', \'is_directory', \1 ] ],
  [ exists_path => [ qw(PS) ],
    [ info_prop => 'PS', \'is_file', \1 ] ],
  [ is_directory => [ qw(PS) ],
    [ info_prop => 'PS', \'is_directory', \1 ] ],
  [ is_file => [ qw(PS) ],
    [ info_prop => 'PS', \'is_file', \1 ] ],
  [ path_status_at => [ 'PS', 'P' ],
    [ path_status => 'PS' ],
    [ path => qw(PS P) ],
    [ 'cut' ] ],
  [ path_status_at => [ 'PS', 'P' ],
    [ observe => [ 'P' ], sub {
        DX::Lib::FS::Observation::PathStatus->new(
          path => $_[0]
        )
      } ],
    [ path_status => 'PS' ],
    [ path => qw(PS P) ] ],
  [ directory_at => [ qw(PS P) ],
    [ path_status_at => qw(PS P) ],
    [ is_directory => 'PS' ] ],
  [ file_at => [ qw(PS P) ],
    [ path_status_at => qw(PS P) ],
    [ is_file => 'PS' ] ],
  [ is_directory => [ qw(PS) ],
    [ not => [ exists_path => 'PS' ] ],
    [ act => [ 'PS' ], sub {
        DX::Lib::FS::Action::CreateDirectory->new(
          path => $_[0]->path
        )
      } ] ],
  [ is_file => [ qw(PS) ],
    [ not => [ exists_path => 'PS' ] ],
    [ act => [ 'PS' ], sub {
        DX::Lib::FS::Action::CreateFile->new(
          path => $_[0]->path
        )
      } ] ],
  [ directory_in => [ qw(DirStatus DirName SubDirStatus) ],
    [ is_directory => qw(DirStatus) ],
    [ exists => [ qw(DirPath) ],
      [ path => qw(DirStatus DirPath) ],
      [ exists => [ qw(SubDirPath) ],
        [ catfile => qw(DirPath DirName SubDirPath) ],
        [ directory_at => qw(SubDirStatus SubDirPath) ] ] ] ],
  [ file_in => [ qw(DirStatus FileName FileStatus) ],
    [ is_directory => qw(DirStatus) ],
    [ exists => [ qw(DirPath) ],
      [ path => qw(DirStatus DirPath) ],
      [ exists => [ qw(FilePath) ],
        [ catfile => qw(DirPath FileName FilePath) ],
        [ file_at => qw(FileStatus FilePath) ] ] ] ],
  [ mode => [ qw(PS M) ],
    [ exists => [ qw(A) ],
      [ has_action => qw(PS A) ],
      [ does => 'A', \'DX::Lib::FS::Action::CreateDirectory' ],
      [ react => [ qw(PS M) ], sub {
          $_[0]->but(mode => $_[1]);
        } ] ],
      [ 'cut' ] ],
  [ mode => [ qw(PS M) ],
    [ exists => [ qw(A) ],
      [ has_action => qw(PS A) ],
      [ does => 'A', \'DX::Lib::FS::Action::CreateFile' ],
      [ react => [ qw(PS M) ], sub {
          $_[0]->but(mode => $_[1]);
        } ] ],
      [ 'cut' ] ],
  [ mode => [ qw(PS M) ],
    [ exists_path => qw(PS) ],
    [ not => [ info_prop => 'PS', \'mode', 'M' ] ],
    [ act => [ qw(PS M) ], sub {
       DX::Lib::FS::Action::SetPathMode->new(
         path_status => $_[0], mode => $_[1]
       )
    } ] ],
  [ file_content => [ qw(FC) ],
    [ member_of => 'FC', \'file_content' ] ],
  [ file_content_at => [ qw(FC P) ],
    [ file_content => 'FC' ],
    [ path => qw(FC P) ],
    [ 'cut' ] ],
  [ file_content_at => [ qw(FC P) ],
    [ observe => [ 'P' ], sub {
        DX::Lib::FS::Observation::FileContent->new(
          path => $_[0]
        )
      } ],
    [ file_content => 'FC' ],
    [ path => qw(FC P) ] ],
  [ file_data => [ qw(FC D) ], [ prop => 'FC', \'data', 'D' ] ],
  [ file_content_line => [ qw(FC L) ],
    [ exists => [ 'Lines' ],
      [ prop => 'FC' => \'lines' => 'Lines' ],
      [ member_of => 'L' => 'Lines' ] ] ],
  [ contains_line => [ qw(PS L) ],
    [ is_file => 'PS' ],
    [ exists => [ qw(FC P) ],
      [ path => qw(PS P) ],
      [ file_content_at => qw(FC P) ],
      [ file_content_line => qw(FC L) ] ] ],
  [ file_content_line => [ qw(FC L) ],
    [ not =>
      [ exists => [ 'Lines' ],
        [ prop => 'FC' => \'lines' => 'Lines' ],
        [ member_of => 'L' => 'Lines' ] ] ],
    [ exists => [ 'A' ],
      [ has_action => qw(FC A) ],
      [ does => 'A' => \'DX::Lib::FS::Action::RewriteFile' ] ],
    [ react => [ qw(FC L) ], sub { $_[0]->but_add($_[1]) } ],
    [ 'cut' ] ],
  [ file_content_line => [ qw(FC L) ],
    [ not =>
      [ exists => [ 'Lines' ],
        [ prop => 'FC' => \'lines' => 'Lines' ],
        [ member_of => 'L' => 'Lines' ] ] ],
    [ act => [ qw(FC L) ], sub {
        DX::Lib::FS::Action::RewriteFile->new(
          from => $_[0],
          add_lines => [ $_[1] ]
        );
      } ],
    [ 'cut' ] ],
  [ not_file_content_line => [ qw(FC L) ],
    [ not =>
      [ exists => [ 'Lines' ],
        [ prop => 'FC' => \'lines' => 'Lines' ],
        [ member_of => 'L' => 'Lines' ] ] ] ],
  [ not_contains_line => [ qw(PS L) ],
    [ is_file => 'PS' ],
    [ exists => [ qw(FC P) ],
      [ path => qw(PS P) ],
      [ file_content_at => qw(FC P) ],
      [ not_file_content_line => qw(FC L) ] ] ],
  [ not_file_content_line => [ qw(FC L) ],
    [ exists => [ 'Lines' ],
      [ prop => 'FC' => \'lines' => 'Lines' ],
      [ member_of => 'L' => 'Lines' ] ],
    [ exists => [ 'A' ],
      [ has_action => qw(FC A) ],
      [ does => 'A' => \'DX::Lib::FS::Action::RewriteFile' ] ],
    [ react => [ qw(FC L) ], sub { $_[0]->but_remove($_[1]) } ],
    [ 'cut' ] ],
  [ not_file_content_line => [ qw(FC L) ],
    [ exists => [ 'Lines' ],
      [ prop => 'FC' => \'lines' => 'Lines' ],
      [ member_of => 'L' => 'Lines' ] ],
    [ act => [ qw(FC L) ], sub {
        DX::Lib::FS::Action::RewriteFile->new(
          from => $_[0],
          remove_lines => { $_[1] => 1 }
        );
      } ],
    [ 'cut' ] ],
);

sub load_into {
  my ($self, $solver) = @_;
  $solver->facts->{path_status} = DX::SetOver->new(over => 'path');
  $solver->facts->{file_content} = DX::SetOver->new(over => 'path');
  $solver->add_predicate(
    catdir => [ qw(DirPath DirName SubDirPath) ],
      [ qw(+ + -) ] => sub {
        +(SubDirPath => [
           value => File::Spec->catdir($_{DirPath}, $_{DirName})
        ])
      },
      [ qw(- - +) ] => sub {
        my @split = File::Spec->splitdir($_{SubDirPath});
        my $last = pop @split;
        my $rest = File::Spec->catdir(@split);
        +(DirPath => [ value => $rest ], DirName => [ value => $last ])
      }
  );
  $solver->add_predicate(
    catfile => [ qw(DirPath FileName FilePath) ],
      [ qw(+ + -) ] => sub {
        my ($vol, $dir) = File::Spec->splitpath($_{DirPath}, 1);
        my $file_path = File::Spec->catpath($vol, $dir, $_{FileName});
        +(FilePath => [ value => $file_path ])
      },
      [ qw(- - +) ] => sub {
        my ($vol, $dir, $file) = File::Spec->splitpath($_{FilePath});
        my $dir_path = File::Spec->catpath($vol, $dir);
        +(DirPath => [ value => $dir_path ], FileName => [ value => $file ])
      }
  );
  $solver->add_rule(
    does => [ qw(Thing RoleName) ],
      [ constrain => [ qw(Thing RoleName) ], sub { $_[0]->DOES($_[1]) } ]
  );
  $solver->add_rule(@$_) for @RULES;
}

1;
