package ConclaveConceptMapper;
use Dancer ':syntax';

use lib '/home/smash/playground/natura.svn.main/Conclave/Conclave-Mapper/lib';
use lib '/home/smash/playground/natura.svn.main/Conclave/Conclave-kPSS/lib';  # FIXME

use Conclave::Utils::OTK;
use Conclave::Mapper::Mapping;
use Conclave::Mapper::Query;
use Conclave::Mapper;
use Conclave::kPSS;
use JSON;
use File::Slurp qw/write_file read_file/;
use Data::Dumper;
use Cwd;

our $VERSION = '0.1';

our $HOME = '/home/smash/conclave-website-root';
our $OWLROOT = "$HOME/owl";

my $files;  # FIXME

hook before_template => sub {
    my $tokens = shift;
    my $path = request->base->path;
    my $pkgid = session 'pkgid';

    $tokens->{uri_base} = $path eq '/' ? $path : $path.'/';
    $tokens->{pkgid} = $pkgid if $pkgid;
};

get '/' => sub {
  my @packages = __package_list($OWLROOT);

  template 'index' => { packages =>[@packages] };
};

get '/select/:pkgid' => sub {
  my $pkgid = param 'pkgid';
  session 'pkgid' => $pkgid;

  redirect "/search/$pkgid";
};

get '/search/:pkgid' => sub {
  my $pkgid = param 'pkgid';

  my $o = Conclave::Utils::OTK->new($pkgid, 'problem');
  my @concepts = $o->__get_class_child('<http://conclave.di.uminho.pt/owl/tree-1.5.3/problem#Concept>');

  template 'search' => { curr=>'search', pkgid => $pkgid, concepts => [@concepts] };
};

post '/search' => sub {
  my $pkgid = param 'pkgid';
  my $concept = param 'concept';
  my $free = param 'free';

  my $json = read_file("$HOME/data/$pkgid/splits_oracle.json", {binmode=>':utf8'});
  my $ids = decode_json $json;
  $json = read_file("$HOME/data/$pkgid/pss.json", {binmode=>':utf8'});
  my $pss = decode_json $json;

  my $o = Conclave::Utils::OTK->new($pkgid, 'problem');
  my @instances = $o->get_instances($concept);
  @instances = map {$_ =~ s/.*?#//; $_ } @instances;

  if ($concept =~ m/<.*?#(.*?)>/) {
    $concept = $1;
  }
  unless (grep {$concept eq $_} @instances) {
    push @instances, $concept;
  }

  if ($free) {
    @instances = split /\s+/, $concept;
    $concept = '';
  }

  my ($match, $code);
  foreach my $i (keys %$ids) {
    my $pss = $pss->{"<http://conclave.di.uminho.pt/owl/$pkgid/program#$i>"}; # FIXME

    foreach (@instances) {
      if (exists $pss->{$_}) {
        my $filename = $1 if $i =~ m/^(.*?)::/;
        my $line = $1 if $i =~ m/::(\d+)$/;
        if ($filename and $line) {
          $code->{$i} = join('<br>', __get_file_line($pkgid, $filename, $line));
        }
        
        $match->{$i} = $ids->{$i};
      }
    }
  }

  my $new;
  foreach (keys $pss) {
    if ($_ =~ m/<.*?#(.*?)>/) {
      $new->{$1} = $pss->{$_};
    }
  }

  template 'search' => { concept=>$concept, match=>$match, pss=>$new, instances=>[@instances], code=>$code }, { layout=>undef };
};

get '/locate/:pkgid' => sub {
  my $pkgid = param 'pkgid';

  template 'locate' => { curr=>'locate', pkgid => $pkgid, query => '' };
};

post '/locate/:pkgid' => sub {
  my $pkgid = param 'pkgid';
  my $query = param 'query';

  my $dir = cwd;
  chdir '/home/smash/playground/natura.svn.main/Conclave/Conclave-Mapper';
  my $res = `perl -Ilib locate "$query" $pkgid`;
  chdir $dir;

  template 'locate' => { curr=>'locate', pkgid=>$pkgid, query=>$query, res=>$res };
};

my %queries = (
    'problem-Concept' => '[ onto=problem class=Concept ]',
    'problem-directory' => '[ onto=problem class=directory ]',
    'program-Function' => '[ onto=program class=Function ]',
    'program-Variable' => '[ onto=program class=Variable ]',
  );

get '/map/:pkgid/:qid1/:qid2/:f' => sub {
  my $pkgid = param 'pkgid';
  my $qid1 = param 'qid1';
  my $qid2 = param 'qid2';
  my $f = param 'f';

  my $q1 = $queries{$qid1};
  my $q2 = $queries{$qid2};

  my $mapper = Conclave::Mapper::Mapping->new($pkgid);
  my $map = $mapper->map($q1, $q2, $f);

  # FIXME move to map object
  my (%cols, %rows);
  foreach (@{ $map->cells }) {
    $cols{$_->col}++;
    $rows{$_->row}++;
  }

  # FIXME
  my $data;
  foreach (@{ $map->cells }) {
    $data->{$_->col}->{$_->row} = $_->score =~ m/^\d/ ? sprintf("%.2f", $_->score) : $_->score;
  }

  template 'mapping' => { curr=>'mapping', pkgid => $pkgid, map => $data, cols => [sort keys %cols], rows => [sort keys %rows], q1=>$q1, q2=>$q2, f=>$f };
};

post '/map/tree/:pkgid' => sub {
  my $pkgid = param 'pkgid';
  my $q1 = param 'q1';
  my $q2 = param 'q2';
  my $f = param 'f';

  my @l1;
  while ($q1 =~ m/(\w+)=(\w+)/g) {
    if ($1 eq 'program' or $1 eq 'problem') { unshift @l1, $2; }
    else { push @l1, $2; }
  }
  my @l2;
  while ($q2 =~ m/(\w+)=(\w+)/g) {
    if ($1 eq 'program' or $1 eq 'problem') { unshift @l2, $2; }
    else { push @l2, $2; }
  }

  redirect "/map/tree/$pkgid/".join('-',@l1)."/".join('-',@l2)."/$f";
};

get '/map/tree/:pkgid/:qid1/:qid2/:f' => sub {
  my $pkgid = param 'pkgid';
  my $qid1 = param 'qid1';
  my $q1_curr = $qid1;
  my $qid2 = param 'qid2';
  my $q2_curr = $qid2;
  my $f = param 'f';

  my $q1 = $queries{$qid1} || __guess_query($qid1);
  my $q2 = $queries{$qid2} || __guess_query($qid2);

  my $mapper = Conclave::Mapper::Mapping->new($pkgid);
  my $map = $mapper->map($q1, $q2, $f);

  # FIXME move to map object
  my (%cols, %rows);
  foreach (@{ $map->cells }) {
    $cols{$_->col}++;
    $rows{$_->row}++;
  }

  # FIXME
  my $data;
  foreach (@{ $map->cells }) {
    $data->{$_->col}->{$_->row} = $_->score =~ m/^\d/ ? sprintf("%.2f", $_->score) : $_->score;
  }

  # FIXME
  my $o = Conclave::Utils::OTK->new($pkgid, 'program');
  my $tree2 = $o->get_class_tree("<http://conclave.di.uminho.pt/owl/$pkgid/program#ProgramElement>");
  $o = Conclave::Utils::OTK->new($pkgid, 'problem');
  my $tree1 = $o->get_class_tree("<http://conclave.di.uminho.pt/owl/$pkgid/problem#Concept>");

  # FIXME
  my $classes;
  my $qq1 = Conclave::Mapper::Query->new($pkgid, $q1);
  my $qq2 = Conclave::Mapper::Query->new($pkgid, $q2);
  foreach (__get_list($qq1), __get_list($qq2)) {
    $classes->{"<$_>"}++;
  }

  my $rank = $map->rank;
  my $lines;
  foreach (@$rank) {
    my $id;
    $id = $_->{col} if ($_->{col} =~ m/program#/);
    $id = $_->{row} if ($_->{row} =~ m/program#/);
    next unless $id;
    if ($id =~ m/.*?#(.*?)::(.*?)::(\d+)$/) {
      my ($filename, $name, $line) = ($1, $2, $3);
      my @l = __get_file_line($pkgid, $filename, $line);
      @l = map {$_ =~ s/\b$name\b/<span class="label label-info">$name<\/span>/g; $_} @l;
      $lines->{$id} = [@l];
    }
  }

  template 'mapping_tree' => { curr=>'mapping', pkgid => $pkgid, map => $data, cols => [sort keys %cols], rows => [sort keys %rows], q1=>$q1, q2=>$q2, qq1=>$qq1, qq2=>$qq2, f=>$f, tree1=>$tree1, tree2=>$tree2, rank=>$rank, classes=>$classes, q1_curr=>$q1_curr, q2_curr=>$q2_curr, lines=>$lines };
};

get '/map/explain/:pkgid/:e1/:e2/:f' => sub {
  my $pkgid = param 'pkgid';
  my $e1 = param 'e1';
  my ($o1, $i1) = split /\-/, $e1;
  my $id1 = ($i1 =~ m/.*?::(.*?)::.*?/) ? $1 : $i1;
  my $e2 = param 'e2';
  my ($o2, $i2) = split /\-/, $e2;
  my $id2 = ($i2 =~ m/.*?::(.*?)::.*?/) ? $1 : $i2;
  my $f = param 'f';

  # get terms
  my $q1 = Conclave::Mapper::Query->new($pkgid, "[ onto=$o1 ]");
  my $t1 = get_terms($q1,"http://conclave.di.uminho.pt/owl/$pkgid/$o1#$i1");
  my $q2 = Conclave::Mapper::Query->new($pkgid, "[ onto=$o2 ]");
  my $t2 = get_terms($q2,"http://conclave.di.uminho.pt/owl/$pkgid/$o2#$i2");

  # calc kPSS
  my $kpss1;
  foreach my $t (split /,/, $t1->{terms}) {
    $kpss1->{$t} = kpss_flatten(kpss_create($t));
  }
  my $kpss2;
  foreach my $t (split /,/, $t2->{terms}) {
    $kpss2->{$t} = kpss_flatten(kpss_create($t));
  }

  # calc kPSS multi-term
  my @terms1 = split /,/, $t1->{terms};
  my $f1 = shift @terms1;
  my $k1 = kpss_create($f1);
  foreach (@terms1) {
    $k1 = kpss_union($k1, kpss_create($_));
  }
  my $pss1 = kpss_flatten($k1);
  my @terms2 = split /,/, $t2->{terms};
  my $f2 = shift @terms2;
  my $k2 = kpss_create($f2);
  foreach (@terms2) {
    $k2 = kpss_union($k2, kpss_create($_));
  }
  my $pss2 = kpss_flatten($k2);

  my $score = 0;
  if ($pss1 and $pss2) {
    my $o = Conclave::PSS->new;
    $score = $o->simil($pss1, $pss2);
  }

  template 'mapping_explain' => { curr => 'mapping', pkgid => $pkgid, e1 => $e1, e2 => $e2, f => $f, t1 => $t1, t2 => $t2, o1 => $o1, o2 => $o2, kpss1 => $kpss1, kpss2 => $kpss2, pss1 => $pss1, pss2 => $pss2, score => $score, id1 => $id1, id2 => $id2 };
};

sub __guess_query {
  my ($str) = @_;

  my @a;
  foreach (split /\-/, $str) {
    if ($_ =~ m/^(program|problem)$/) { push @a, "onto=$_"; }
    else { push @a, "class=$_"; 
    }
  }

  return "[ ".join(' ',@a)." ]";
}

sub __get_list {
  my ($query) = @_;
  my $o = Conclave::Utils::OTK->new($query->pkgid, $query->onto);
  # set classes
  my @init;
  push @init, 'ProgramElement' if $query->onto eq 'program';
  push @init, 'Concept' if $query->onto eq 'problem';
  if ($query->class) {
    @init = @{ $query->class };
  }
  my %classes;
  foreach my $c (@init) {
    $classes{$query->full_uri($c)}++;
    foreach ($o->get_all_subclasses($query->full_uri($c))) {
      next if $_ =~ m/Identifier/;  # FIXME
      $classes{$_}++;
    }
  }
  my @classes = keys %classes;

  return @classes;
}

sub __package_list {
  my $root = shift;

  my @packages;
  opendir(my $dh, $root) or die "can't opendir $root: $!";
  while(readdir $dh) {
    next if $_ =~ m/^\.{1,2}$/;
    next unless -d "$root/$_";

    push @packages, $_;
  }
  closedir $dh;

  return @packages;
}

sub __package_available_list {
  my ($root, $pkgid) = @_;

  my %available;
  opendir(my $dh, "$root/$pkgid") or die "can't opendir $root/$pkgid: $!";
  while(readdir $dh) {
    next if $_ =~ m/^\.{1,2}$/;

    $_ =~ s/\..*?$//;
    $available{$_}++;
  }
  closedir $dh;

  return keys %available;
}

sub __get_file_line {
  my ($pkgid, $filename, $line, $single_line) = @_;

  my @lines;
  my $key = "$pkgid-$filename";
  if ($files->{$key}) {
    @lines = @{ $files->{$key} };
  }
  else {
    my $cnt = read_file("$HOME/packages/$pkgid/$filename", {binmode=>':utf8'});
    @lines = split /\n/, $cnt;
    $files->{$key} = [@lines];
  }

  return $lines[$line-1] if ($single_line);
  my @res =  splice(@lines, $line-4, 6);
  my $c = $line-4+1;
  foreach (@res) {
    $_ = "<span class=\"text-info\">$c:</span> $_";
    $c++;
  }
  return @res;
}

true;
