package App::KeyMangler;

use Moo;

extends 'DX::Tcl';

sub BUILD {
  my ($self) = @_;
  our $Tcl_Data ||= do { local $/; my $data = <DATA>; close(DATA); $data };
  $self->tcl->Eval($Tcl_Data);
}

sub run {
  my ($self, @args) = @_;
  if (@args) {
    my $res = $self->tcl->call(km => @args);
    print $res;
  } else {
    $self->repl
  }
}

1;

__DATA__

rule dot_ssh_on { A D } {
  home_dir_on A H
  directory_in H '.ssh' D
  mode D '0700'
}

rule authorized_keys_on { A F } {
  dot_ssh_on A D
  file_in D 'authorized_keys' F
  mode F '0600'
}

rule key_installed_on { A K } {
  authorized_keys_on A F
  contains_line F K
}

rule key_not_installed_on { A K } {
  authorized_keys_on A F
  not_contains_line F K
}

rule my_config_dir D {
  home_dir_on '' H
  directory_in H '.keymangler' D
}

rule my_config_file { C F } {
  my_config_dir D
  file_in D C F
}

rule config_contains_line { C L } {
  my_config_file C F
  contains_line F L
}

rule config_not_contains_line { C L } {
  my_config_file C F
  not_contains_line F L
}

rule known_account A { config_contains_line 'accounts' A }
rule known_key K { config_contains_line 'keys' K }
rule known_dead D { config_contains_line 'keys.dead' D }

rule not_known_account A { config_not_contains_line 'accounts' A }
rule not_known_key K { config_not_contains_line 'keys' K }
rule not_known_dead D { config_not_contains_line 'keys.dead' D }

rule all_known_installed_on A {
  foreach K { known_key K } { key_installed_on A K }
}

rule all_dead_not_installed_on A {
  foreach K { known_dead K } { key_not_installed_on A K }
}

rule account_synchronized A {
  all_known_installed_on A
  all_dead_not_installed_on A
}

rule all_synchronized {} {
  foreach A { known_account A } { account_synchronized A }
}

rule unknown_installed_on { A K } {
  key_installed_on A K
  not { known_key K }
  not { known_dead K }
}

rule known_installed_on { A K } {
  key_installed_on A K
  known_key K
}

rule dead_installed_on { A K } {
  key_installed_on A K
  known_dead K
}

oo::class create ConfigSet {
  constructor mytype {
    namespace import ::DX::*
    variable type $mytype
  }
  method learn V {
    variable type
    ensure "known_$type {'$V'}"
  }
  method forget V {
    variable type
    ensure "not_known_$type {'$V'}"
  }
  method list {} {
    variable type
    query* "known_$type V" {puts $V}
  }
}

oo::class create KeyMangler {
  constructor {} {
    namespace import ::DX::*
    ConfigSet create key key
    ConfigSet create dead dead
    ConfigSet create account account
    variable mode ensure
  }

  method key args { key {*}$args }
  method dead args { dead {*}$args }
  method account args { account {*}$args }

  method status {} {
    query* {
      known_account A
      findall Known K { known_installed_on A K }
      findall Unknown U { unknown_installed_on A U }
      findall Dead D { dead_installed_on A D }
    } {
      puts "Host: $A"
      foreach k $Known { puts "  Known: [lindex $k 2]" }
      foreach u $Unknown { puts "  Unknown: [lindex $u 2]" }
      foreach d $Dead { puts "  Dead: [lindex $d 2]" }
      puts ""
    }
  }

  method -n args {
    variable mode solve
    my {*}$args
  }

  export -n

  method sync {{account -}} {
    variable mode
    if {"$account" eq "-"} {
      $mode { all_synchronized }
    } else {
      $mode "account_synchronized {'$account'}"
    }
  }
}

KeyMangler create km
