#!/usr/bin/perl

# ticket file format:
# project|id|code|subject|antecedents|notes

my $version = "99999 K ";

my %types = (
  C => "CHANGE",      # { Change or replace existing module or area }
  D => "DEFECT",      # { A known defect or problem area }
  F => "FEATURE",     # { Create new or replace a feature }
  I => "INVESTIGATE", # { Investigate and Report on a proposed [C|F] }
  R => "REFERENCE",   # { Continued notes or description to a ticket }
  S => "SAGA",        # { A major effort or long running collection work }
  T => "TASK",        # { A smaller sized amount of work. Attached to [C|D|F] }
  X => "DOCUMENT"     # { A task to document a component, module or system }
);

sub read_ticket_file {
  my ($file) = @_;
  return `cat $file`;
}

sub get_tickets {
  my (@file) = @_;
  my %tickets = ();
  foreach(@file) {
    if($_ =~ /^(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)/) {
      $tickets{$1}{$2} = {
        code    => $3,
        subject => $4,
        ante    => $5,
        notes   => $6
      };
    }
  }
  return %tickets;
}

sub get_ticket_ids {
  my ($project, %tickets) = @_;
  my @ids = ();
  foreach my $id (sort {$a <=> $b} keys %{$tickets{$project}}) {
    push @ids, $id;
  }
  return @ids;
}

sub get_ticket_ids_with_antecedents {
  my ($project, @tickets) = @_;
  my @ids = ();
  foreach my $id (sort {$a <=> $b} keys %{$tickets{$project}}) {
    push @ids, $id if $tickets{$project}{$id}{ante} ne "";
  }
  return @ids;
}

sub get_disconnected_nodes {
  my ($project, @tickets) = @_;
  my @ids = ();
  foreach my $id (sort {$a <=> $b} keys %{$tickets{$project}}) {
    push @ids, $id if $tickets{$project}{$id}{ante} eq "";
  }
  return @ids;
}

sub get_disconn_refs {
  my ($project, $dis_r, $tickets_r) = @_;
  my @dis = @{$dis_r};
  my %tickets = %{$tickets_r};

  foreach my $id (sort {$a <=> $b} keys %{$tickets{$project}}) {
    @alist = split /,/, $tickets{$id}{ante};
    foreach my $a (@alist) {
      for($j = 0; $j < @dis; $j++) {
        if($a eq $dis[$j]) {
          splice @dis, $j, 1;
        }
      }
    }
  }
  return @dis;
}

sub get_last_ticket_id  { my (@ids) = @_; return pop @ids; }

sub get_ticket_code_by_id {
  my ($project, $id, %tickets) = @_;
  return $tickets{$project}{$id}{code} if exists $tickets{$project}{$id};
}

sub check_ticket_code {
  my ($code) = @_;
  return "T" if exists $types{$code};
  return "F";
}

sub check_project_exists {
  my ($project) = @_;
  return "T" if exists $tickets{$project};
  return "F";
}

sub check_project_ticket_id_exists {
  my ($project, $id) = @_;
  return "T" if exists $tickets{$project}{$id};
  return "F";
}

sub add_ticket {
  my ($project, $ticket_file, %tickets) = @_;

  if(!exists $tickets{$project}) {
    $last_id = 1;
  } else {
    $last_id = get_last_ticket_id(get_ticket_ids($project, %tickets)) + 1;
  }

  print "Subject: "; my $subject = <STDIN>; chomp($subject);
  print "Ticket Code: "; my $code = <STDIN>; chomp($code);
  print "Antecedents (comma delimited): "; my $ante = <STDIN>; chomp($ante);
  print "Add Ticket Notes: "; my $notes = <STDIN>; chomp($notes);

  $tickets{$project};
  $tickets{$project}{$last_id}{code}    = $code;
  $tickets{$project}{$last_id}{subject} = $subject;
  $tickets{$project}{$last_id}{ante}    = $ante;
  $tickets{$project}{$last_id}{notes}   = $notes;
  write_ticket_file($project, $ticket_file, %tickets);
  return %tickets;
}

sub remove_ticket {
  my ($project, $tid, $ticket_file, %tickets) = @_;
  if(check_project_ticket_id_exists($project, $tid) eq "F") {
    print "ID [$tid] not found in project [$project]\n";
    return;
  }
  if(exists $tickets{$project}{$tid}) {
    foreach my $id (keys %{$tickets{$project}}) {
      if($id eq $tid) {
        print "Are you sure you want to remove ticket id [$tid]? y|n\n";
        my $check = <STDIN>; chomp($check);
        if($check =~ /y/i) {
          delete $tickets{$project}{$id};
          write_ticket_file($project, $ticket_file, %tickets);
        } else {
          print "Answered 'No'. Id [$tid] not deleted. Exiting.\n";
        }
      }
    }
  } else {
    print "Id [$tid] to remove not found.\n";
    exit;
  }
  return %tickets;
}

sub edit_ticket {
  my ($project, $id, $ticket_file, %tickets) = @_;

  if(check_project_ticket_id_exists($project, $id) eq "F") {
    print "ID [$id] not found in project [$project]\n";
    return;
  }
  if(exists $tickets{$project}{$id}) {
    print "Are you sure you want to edit ticket id [$id]? y|n\n";
    my $check = <STDIN>; chomp($check);
    if($check =~ /y/i) {
      my $ok = "n";
      my ($new_project, $new_sub, $new_code, $new_ante, $new_notes) = "";
      do {
        print "       PROJECT: $project\n";
        print "   NEW PROJECT: ";
        $new_project = <STDIN>; chomp($new_project);
        print "       SUBJECT: $tickets{$project}{$id}{subject}\n";
        print "   NEW SUBJECT: ";
        $new_sub = <STDIN>; chomp($new_sub);
        print "          TYPE: $tickets{$project}{$id}{code}\n";
        print "      NEW TYPE: ";
        $new_code = <STDIN>; chomp($new_code);
        print "    ANTECEDENT: $tickets{$project}{$id}{ante}\n";
        print "NEW ANTECEDENT: ";
        $new_ante = <STDIN>; chomp($new_ante);
        print "         NOTES: $tickets{$project}{$id}{notes}\n";
        print "     NEW NOTES: ";
        $new_notes = <STDIN>; chomp($new_notes);
        print "Ok? y|n|q\n";
        $ok = <STDIN>; chomp($ok);
      } while($ok !~ /y|q|quit/i);
      return if $ok =~ /q|quit/i;
      if(!exists $tickets{$new_project}) { $id = 1; }
      $tickets{$new_project}{$id}{code}    = $new_code;
      $tickets{$new_project}{$id}{subject} = $new_sub;
      $tickets{$new_project}{$id}{ante}    = $new_ante;
      $tickets{$new_project}{$id}{notes}   = $new_notes;
      write_ticket_file($new_project, $ticket_file, %tickets);
    }
  }
}

sub write_ticket_file {
  my ($project, $ticket_file, %tickets) = @_;
  open FILE, ">$ticket_file" or die "$!";
  foreach my $project (sort keys %tickets) {
    foreach my $id (sort {$a <=> $b} keys %{$tickets{$project}}) {
      print FILE "$project" .
               "|$id" .
               "|$tickets{$project}{$id}{code}" .
               "|$tickets{$project}{$id}{subject}" .
               "|$tickets{$project}{$id}{ante}" .
               "|$tickets{$project}{$id}{notes}\n";
    }
  }
  close FILE;
}

sub wrap_cols {
  my ($cols, $str) = @_;
  $str =~ s/(.{0,$cols}(?:\s|$))/$1\n/g;
  chomp($str);
  return $str;
}

sub print_ticket_by_id {
  my ($project, $tid, $html, %tickets) = @_;
  my $str = "";

  if(check_project_ticket_id_exists($project, $tid) eq "F") {
    print "ID [$tid] not found in project [$project]\n";
    return;
  }

  foreach my $id (sort {$a <=> $b} keys %{$tickets{$project}}) {
    if($tid eq $id) {
      $str .= "<html>\n" if $html ne "F";
      if($html ne "F") {
        $str .= "<h1>PROJECT: $project</h1><br />\n"
      } else {
        $str .= "PROJECT: $project\n"
      }
      $subject = wrap_cols(71, "$tickets{$project}{$id}{subject}");
      $code    = wrap_cols(74, "$tickets{$project}{$id}{code}");
      $ante    = wrap_cols(65, "$tickets{$project}{$id}{ante}");
      $notes   = wrap_cols(75, "$tickets{$project}{$id}{notes}");

      if($html ne "F") {
        $notes =~ s/\n/<br \/>\n/g;
        $str .= "========================================" .
                "========================================<br />\n" .
                "<p>\n" .
                "<a name=\"$id\"></a><b>ID:</b> $id</a><br />\n" .
                "<b>SUBJECT:</b> $subject<br />\n" .
                "<b>TYPE:</b> $code<br />\n" .
                "<b>ANTECEDENT(s):</b> $ante<br />\n" .
                "<b>NOTES:</b><br />\n" .
                "$notes\n" .
                "</p>\n";
      } else {
        $str .= "========================================" .
                "========================================\n" .
                "ID: $id\n" .
                "SUBJECT: $subject\n" .
                "TYPE: $code\n" .
                "ANTECEDENT(s): $ante\n" .
                "NOTES:\n$notes\n";

      }
      $str .= "</html>\n" if $html ne "F";
    }
  }
  print "$str\n";
}

sub print_all_tickets {
  my ($project, $html, %tickets) = @_;
  my $str = "";

  $str .= "<html>\n" if $html ne "F";
  if($html ne "F") {
    $str .= "<h1>PROJECT: $project</h1><br />\n" if $html ne "F";
  } else {
    $str .= "PROJECT: $project\n";
  }
  foreach my $id (sort {$a <=> $b} keys %{$tickets{$project}}) {
    $subject = wrap_cols(71, "$tickets{$project}{$id}{subject}");
    $code    = wrap_cols(74, "$tickets{$project}{$id}{code}");
    $ante    = wrap_cols(65, "$tickets{$project}{$id}{ante}");
    $notes   = wrap_cols(75, "$tickets{$project}{$id}{notes}");

    if($html ne "F") {
      $notes =~ s/\n/<br \/>\n/g;
      $str .= "========================================" .
              "========================================\n" .
              "<p>\n" .
              "<a name=\"$id\"></a><b>ID:</b> $id<br />\n" .
              "<b>SUBJECT:</b> $subject<br />\n" .
              "<b>TYPE:</b> $code<br />\n" .
              "<b>ANTECEDENT(s):</b> $ante<br />\n" .
              "<b>NOTES:</b><br />\n" .
              "$notes\n" .
              "</p>\n";
    } else {
      $str .= "========================================" .
              "========================================\n" .
              "ID: $id\n" .
              "SUBJECT: $subject\n" .
              "TYPE: $code\n" .
              "ANTECEDENT(s): $ante\n" .
              "NOTES:\n$notes\n";
    }
  }
  $str .= "</html>\n" if $html ne "F";
  print "$str\n";
}

sub print_projects {
  my (%tickets) = @_;
  my @p = ();
  foreach my $project (sort keys %tickets) {
    push @p, $project;
  }
  print "Projects: " . (join " | ", @p) . "\n";
}

sub print_types {
  my @t = ();
  foreach my $type (sort keys %types) {
    push @t, "$type => $types{$type}";
  }
  print "Ticket Type Codes: " . (join " | ", @t) . "\n";
}

sub build_dep_graphs {
  my ($graph_type, $project, %tickets) = @_;
  my $g;
  my @tids = get_ticket_ids_with_antecedents($project, %tickets);
  my @dis  = get_disconnected_nodes($project, %tickets);
  my @refs = get_disconn_refs($project, \@dis,\%tickets);

  $graph = Graph::Easy->new();
  $graph->set_attributes("graph", {
    font  => "monospace",
    label => "..::[ The Bitcoin Foundation: Tickets Graph for $project ]::.."
  });
  $graph->set_attributes("node",
  {
    linkbase => "http://thebitcoin.foundation/tickets/tickets.html#",
    autolink => "name",
    color    => "black"
  });

  # Loop over each disconnected node and add to the graph
  foreach my $r (@refs) {
    my $node = $graph->add_node($r);
    $node = colorize_node($project, $node, $r, %tickets);
  }

  # Loop over each of the ticket ids with antecedents and graph
  foreach my $tid (@tids) {
    my $node = $graph->add_node($tid);
    $node = colorize_node($project, $node, $tid, %tickets);

    # get all antecedents per ticket id
    my @alist = ();
    foreach my $id (sort {$a <=> $b} keys %{$tickets{$project}}) {
      @alist = split /,/, $tickets{$project}{$id}{ante} if $id eq $tid;
    }

    foreach my $a (@alist) {
      $graph->add_edge_once($node, $a);
    }
  }

  if($graph_type =~ /ag/) {
    $g = $graph->as_ascii();
  } elsif($graph_type =~ /dg/) {
    $g = $graph->as_graphviz();
    $g =~ s/GRAPH_0/TICKET_GRAPH/;
    $g =~ s/rankdir=LR/rankdir=LR,ranksep=1.50,nodesep=0.50/;
  }
  return $g;
}

sub colorize_node {
  my ($project, $node, $tid, %tickets) = @_;
  if(get_ticket_code_by_id($project, $tid, %tickets) eq "S") {
    $node->set_attribute("fill", "green");
  }
  if(get_ticket_code_by_id($project, $tid, %tickets) eq "D") {
    $node->set_attribute("fill", "red");
  }
  return $node;
}

sub reorder_graph {
  my ($graph) = @_;
  my @lines = ();
  my @g = split /\n/, $graph;
  foreach my $l (@g) { if($l =~ /URL/) { push @lines, $l; } }
  @lines = sort {$b <=> $a} @lines;
  for($i = 0; $i < @g; $i++) {
    if($g[$i] =~ /URL/) {
      $g[$i] = shift @lines;
    }
  }
  return join("\n", @g);
}

sub print_dotgraph {
  my ($dotgraph) = @_;
  print reorder_graph($dotgraph);
}

sub print_asciigraph {
  my ($asciigraph) = @_;
  print $asciigraph;
}

sub help {
  my $help = << "END_HELP";
################################################################################
#                 ..::[ The Bitcoin Foundation: T(ickets) ]::..                #
#                                                                              #
#     Version: $version                                                        #
#      Author: mod6                                                            #
# Fingerprint: 0x027A8D7C0FB8A16643720F40721705A8B71EADAF                      #
#                                                                              #
#       Usage: t.pl [help] <ticket_file> <options> <project>                   #
#                                                                              #
#     options:                                                                 #
#  [<p|print> <project>]                       | Print all tickets             #
#  [<phtml|print_html> <project>]              | Print all tickets (w/HTML)    #
#  [<pp|print_projects>]                       | Print all projects            #
#  [<pid|print_by_id> <project> <id>]          | Print a ticket by id          #
#  [<pidhtml|print_by_id_html> <project> <id>] | Print ticket by id (w/HTML)   #
#  [<t|types>]                                 | Print all ticket type codes   #
#  [<a|add> <project>]                         | Add a new ticket              #
#  [<r|remove> <project> <id>]                 | Remove a ticket by id         #
#  [<e|edit> <project> <id>]                   | Edit a ticket by id           #
#  [<ag|asciigraph> <project>]                 | Print ascii graph             #
#  [<dg|dotgraph> <project>]                   | Print dotfile for svg graph   #
#  [<h|help>]                                  | Print this help message       #
#                                                                              #
################################################################################
END_HELP
  print "$help\n";
}

sub main {

  if(@ARGV < 1) {
    print "ERROR!\n";
    help();
    return;
  }

  if($ARGV[0] =~ /^h$|^help$/i) { help(); return; }

  my $file = shift @ARGV;
  %tickets = get_tickets(read_ticket_file($file));

  if($ARGV[0] eq "") { print "Please enter a command.\n\n"; help(); return; }
  if($ARGV[0] =~ /^pp$|^print_projects$/i) { print_projects(%tickets); return; }
  if($ARGV[0] =~ /^t$|^types$/i) { print_types(); return; }

  my $arg = "";
  $arg    = shift @ARGV;

  if(@ARGV < 1) { help(); return; }

  while(@ARGV) {
    my $project = shift @ARGV;
    if(check_project_exists($project) eq "F" && $arg !~ /^a$|^add$/i) {
      print "Project \'$project\' does not currently exist. You can create\n" .
            "a new project by adding a new ticket with a new project name.\n";
      return;
    }

    if($arg =~ /^a$|^add$/i) {
      %tickets = add_ticket($project, $file, %tickets);
    } elsif($arg =~ /^r$|^remove$/i) {
      my $id = shift @ARGV;
      %tickets = remove_ticket($project, $id, $file, %tickets);
    } elsif($arg =~ /^e$|^edit$/i) {
      my $id = shift @ARGV;
      %tickets = edit_ticket($project, $id, $file, %tickets);
    } elsif($arg =~ /^pid$|^print_by_id$/i) {
      my $id = shift @ARGV;
      print_ticket_by_id($project, $id, "F", %tickets);
    } elsif($arg =~ /^pidhtml$|^print_by_id_html$/) {
      my $id = shift @ARGV;
      print_ticket_by_id($project, $id, "T", %tickets);
    } elsif($arg =~ /^ag$|^asciigraph$|^dg$|^dotgraph$/i) {
      my $mod = "Graph::Easy";
      (my $req = $mod . ".pm") =~ s{::}{/}g;
      require $req;
      $graph = $mod->new();
      if($arg =~ /^dg$|^dotgraph$/) {
        print_dotgraph(build_dep_graphs("dg", $project, %tickets));
      } elsif($arg =~ /^ag$|^asciigraph$/) {
        print_asciigraph(build_dep_graphs("ag", $project, %tickets));
      }
    } elsif($arg =~ /^p$|^print$/) {
      print_all_tickets($project, "F", %tickets);
    } elsif($arg =~ /^phtml$|^print_html$/) {
      print_all_tickets($project, "T", %tickets);
    } elsif($arg =~ /^h$|^help$/) {
      help();
      return;
    }
  }
}

main();
