#!/usr/bin/perl -w
# /*=========================================================================*
#  *      Title   : BibTeX Database Generator (Interactive script)
#  *      File    : bibtex.pl
#  *
#  *      Author  : Pallav Gupta
#  *      Created : Sat Aug 23 20:36:59 2003
#  *
#  * This source code can be redistributed under the GNU Free Public Licence.
#  *=========================================================================*/

# /* $Id: bibtex.pl,v 1.4 2003/10/09 04:04:27 pgupta Exp $ */

# Usage: bibtex.pl

require Term::ReadKey;
require Term::ANSIColor;
require File::Basename;

use File::Basename;
use Term::ReadKey;
use Term::ANSIColor qw(:constants);
use strict;

# Contains template for the bibtex entries
my $program_dir = dirname($0);
my $template = "$program_dir/bibtex.template";

# Global variables
# Interactive prompt
my $prompt = "bibtex>";
# Map each of the entries to its shortcut command
my %types;
# Hash table to store all the entries
my %entries;

# Main program
&bibtex_header(*STDOUT);
&bibtex_initialize_structures();
&exec_bibtex_script(*STDIN);
exit 0;

# Options interpreter
sub exec_bibtex_script {
  my $fp = shift @_;

  print "\nType 'h(elp)' for help message.\n";
  print "\n$prompt";
  while(<$fp>) {
    chomp($_);

    if (/^$/ || /^\#/) {
      next;
    }
    if (/^q(uit)?/) {
      last;
    }
    elsif (/^c(reate)?\s+(\w+)/) {
      &bibtex_new_entry($2);
    }
    elsif (/^h(elp)?/) {
      &bibtex_help();
    }
    elsif (/^s(how)?\s*(.*)?/) {
      &bibtex_show_entry(*STDOUT, $2);
    }
    elsif (/^d(del)?\s*(.*)?/) {
      &bibtex_del_entry($2);
    }
    elsif (/^m(odify)?\s+(.*)/) {
      &bibtex_modify_entry($2);
    }
    elsif (/^k(eys)?/) {
      &bibtex_show_keys();
    }
    elsif (/^w(rite)?\s+(.*)/) {
      &bibtex_write_to_file(0,$2); # 0 is for no appending
    }
    elsif (/^a(append)?\s+(.*)/) {
      &bibtex_write_to_file(1,$2); # 1 is for appending
    }
    else {
      &bibtex_error("Invalid option '$_'. Type 'h(elp)' for help message.");
    }

    print "\n$prompt";
  }
}

# Here we initialize the fields each type of bibtex entry must have
sub bibtex_initialize_structures {

  die "File $template does not exist. Aborting $0." if (!-e "$template");
  open(IN, "$template") || die "Unable to open file $template";
  print "Reading $template and initializing ...\n";

  while (<IN>) {
    chomp $_;

    next if ($_ eq '');

    # ignore comments that begin with #
    m/^\#|^\s/ and do {
      next;
    };

    m/(.*);\s*(.*);\s*(.*)/ and do {
      my $type = $1;
      my $symb = $3;
      $type =~ s/\s//g;
      $symb =~ s/\s//g;
      my @fields = split /,\s+/,$2;
      unshift @fields, $type;
      print "Read $symb : @fields\n";
      $types{$symb} = \@fields;
      next;
    };

    die "Parse error: unable to parse '$_'\n";
  }

  close(IN);
}

# Create a new entry of type 'type'
sub bibtex_new_entry {
  my $type = shift @_;

  my @entry;
  my %data;

  if (!exists $types{$type}) {
    bibtex_error("I don't know how to process entry of type '$type'.");
    return;
  }

  @entry = @{$types{$type}};
  my $char = &bibtex_readchar("Create entry type '$entry[0]'");
  return if ($char eq 'n');
  $data{"type"} = $type;

  for (my $i = 1; $i <= $#entry; $i++) {
    print "$entry[$i]>";
    my $line = <STDIN>;
    chomp($line);
    $data{$entry[$i]} = $line;
  }

  print "key>";
  my $key = <STDIN>;
  chomp($key);
  $key =~ s/\s//g;
  while (1) {
    if (exists $entries{$key}) { print "An entry with key '$key' already exists. Enter new key>"; }
    elsif ($key eq "") { print "Invalid key '$key'. Enter new key>"; }
    else { last; }
    $key = <STDIN>;
    chomp($key);
    $key =~ s/\s//g;
  }
  $entries{$key} = \%data;
}

# Displays the key of each entry in database
sub bibtex_show_keys {

  if (scalar keys %entries == 0) {
    &bibtex_error("There are no entries.");
    return;
  }

  my $i = 0;
  foreach my $key (keys %entries) {
    print "$key";
    if (($i++)%5 == 0) { print "\n"; }
    else { print ", "; }
  }
}

# Modify an entry with key 'key'
sub bibtex_modify_entry {
  my $key = shift @_;

  if (scalar keys %entries == 0) {
    &bibtex_error("There are no entries.");
    return;
  }

  if (defined $key && $key ne "") {
    chomp ($key);

    if (!exists $entries{$key}) {
      &bibtex_error("No entry found with key $key");
      return;
    }
    else {
      my %data = %{$entries{$key}};
      my @fields = @{$types{$data{"type"}}};

      print "\nModifying entry '$fields[0]'. To keep old value of a field, press <ENTER>\n";

      for (my $i = 1; $i <= $#fields; $i++) {
	print "$fields[$i]: ", RED, "$data{$fields[$i]}\n", RESET;
	print "Enter new value>";
	my $str = <STDIN>;
	chomp($str);
	if ($str ne "") { $data{$fields[$i]} = $str; }
      }
      $entries{$key} = \%data;
    }
    print "\n";
  }
  else {
    &bibtex_error("No key supplied. Aborted!");
    return;
  }
}


# Display all or a single entry
sub bibtex_show_entry {
  my ($fh, $key) = (@_);

  if (scalar keys %entries == 0) {
    &bibtex_error("There are no entries.");
    return;
  }

  if (defined $key && $key ne "") {
    chomp($key);

    if (exists $entries{$key}) {
      &bibtex_print_entry($fh, $key);
    }
    else {
      &bibtex_error("No entry found with key $key");
      return;
    }
  }
  else {
    foreach $key (sort keys %entries) {
      &bibtex_print_entry($fh, $key);
    }
  }
}

# Delete an entry with key 'key'
sub bibtex_del_entry {
  my $key = shift @_;

  if (scalar keys %entries == 0) {
    &bibtex_error("There are no entries.");
    return;
  }

  if (defined $key && $key ne "") {
    chomp ($key);

    if (exists $entries{$key}) { delete $entries{$key}; }
    else {
      &bibtex_error("No entry found with key $key");
      return;
    }
  }
  else {
    my $char = &bibtex_readchar("Delete all entries");
    return if ($char eq 'n');
    %entries = ();
  }
}

# Print an entry with key 'key'
sub bibtex_print_entry {
  my ($fh, $key) = (@_);

  my %entry_data = %{$entries{$key}};
  my @entry_type = @{$types{$entry_data{"type"}}};

  print $fh "\n\@$entry_type[0]\{$key,\n";

  for (my $i = 1; $i <= $#entry_type; $i++) {
    print $fh "$entry_type[$i] = \{$entry_data{$entry_type[$i]}\}";
    print $fh ",\n" if ($i != $#entry_type);
  }
  print $fh "\n};\n";
}

# Write bibtex to file <file>
sub bibtex_write_to_file {
  my ($append, $file) = (@_);

  if (not defined $file || $file eq "") {
    &bibtex_error("Invalid name '$file' for file specified");
    return;
  }

  if (scalar keys %entries == 0) {
    &bibtex_error("There are no entries.");
    return;
  }

  my $t;
  if ($append) { $t = ">>$file"; }
  else { $t = ">$file"; }

  if (!open(OUT, $t)) {
    &bibtex_error("Unable to open $file for writing. Aborted!");
    return;
  }

  &bibtex_show_entry(*OUT, "");
  close(OUT);
}

# Help msg
sub bibtex_help {
  print "\nCommands that are currently supported:\n";
  print CYAN, "h(elp)", RESET, "\t\t\t  - this help\n";
  print CYAN, "c(reate) ";

  my @keys = sort keys %types;
  print "[";
  for (my $i = 0; $i < $#keys; $i++) {
    print "$keys[$i]|";
  }
  print "$keys[$#keys]]", RESET, " - create an entry type\n";

  foreach my $i (@keys) {
    print RED "\t $i", RESET, "\t: @{$types{$i}}[0]\n";
  }

  print CYAN, "d(el) [<key>]", RESET, "\t\t - delete bibtex entry with key <key>\n";
  print "\t\t\t NOTE: not supplying a key will delete everything!\n";
  print CYAN, "s(how) [<key>]", RESET, "\t\t - show bibtex entry with key <key>\n";
  print "\t\t\t NOTE: not supplying a key will show everything!\n";
  print CYAN, "m(odify) <key>", RESET, "\t\t - modify values of entry with key <key>\n";
  print CYAN, "w(rite) <file>", RESET, "\t\t - write bibtex database to file <file> by sorting keys\n";
  print CYAN, "a(ppend) <file>", RESET, "\t\t - append bibtex database to file <file> by sorting keys\n";
  print CYAN, "k(eys)", RESET, "\t\t\t - show all keys in current database\n";
  print CYAN, "q(uit)", RESET, "\t\t\t - quit\n";
}

# Print error messages
sub bibtex_error {
  my @msgs = (@_);

  foreach my $msg (@msgs) {
    print "\nERROR: $msg\n";
  }
}

# Read a char from stdin
sub bibtex_readchar {
  my $msg = shift @_;

  my $char = 'g';
  ReadMode('cbreak');
  while (!($char eq 'y' || $char eq 'n')) {
    print "\n$msg [y|n]?";
    $char = ReadKey(0);
  }
  ReadMode('normal');
  print "\n";
  return $char;
}

# Script header info
sub bibtex_header {
  my $fh = shift @_;

  my $prog_name = "BibTeX Database Generator";
  my $email = "pallav\@ieee.org";
  my $author = "Pallav Gupta";
  my $version = "1.0";
  my $date = `date`;

  print $fh "Starting $prog_name \t\t $date\n";
  print $fh "Author: $author\n";
  print $fh "Version: $version\n";
  print $fh "Report bugs: $email\n";
}
