#!/usr/bin/perl
#
# TOFUUP - upgrade your todo layout to the current one.
#

# Copyright (c) 2010 Sébastien Boillod <sbb at tuxfamily dot org>.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

use strict;
use warnings;
use File::Basename;
use File::Copy;
use File::Path;

use constant TOTEM => "Ciboulette";
use constant TOFUDIR => $ENV{"TOFUDIR"} ? $ENV{"TOFUDIR"} : "$ENV{HOME}/.tofu";
use constant BACKUPDIR => $ENV{"BACKUPDIR"} ? $ENV{"BACKUPDIR"} : TOFUDIR . "-backup";
use constant TMPDIR =>  $ENV{"TMPDIR"} ? $ENV{"TMPDIR"} : "$ENV{HOME}/.tofuup-tmp";
use constant TESTDIR =>  "/tmp/tofuup-test";
use constant TESTBACKUPDIR =>  "/tmp/tofuup-test-old";

# === Library ==================================================================

sub ciboulette {
    # Upgrade the 2.x todo layout into the 3.x one.
    # $f
    print "  upgrade   Old2.x -> Ciboulette\n";
    my ($id, %pr, %todo) = qw(1 COOL 2 WARM 1 HOT 0);
    open(TEXT, ">", TMPDIR."/text") or die;
    for my $stack (glob TMPDIR."/*.stack") {
        open(STACK, "<", $stack);
        my ($project, %trans) = (substr(basename($stack),0, -6)); # %trans keeps old/new id to sync content.
        while(<STACK>) {
            next if /^>/; # don't care about status.
            my ($old, $p, @tags) = split(" ", $_);
            $trans{$old} = $id;
            @tags = sort(@tags, $project); # project as tag.
            $p = sprintf("%i%015i%s", $pr{$p}, $old, $project); # unique and sortable id.
            $todo{$p} = sprintf("%i\t%s\t%i\n", $id++, "@tags", 0);
        }
        close STACK;
        open(CONTENT, "<", TMPDIR."/$project.content");
        my $check = -1;
        while (<CONTENT>) {
            s/^([0-9]+)>// or next; # ignore mess...
            my $id = $1; # avoid mess due to $1 silent re-initialization.
            print TEXT "@ $_" and next if ($check == $id);
            # first line of each content must contain a valid title
            # and we ignore potential orphan content...
            next unless (exists $trans{$id} and /[^\s\n]/);
            print TEXT $trans{$id}." $_";
            delete $trans{$id}; # only one content per task.
            $check = $id; # OK...
        }
        close CONTENT;
        for (keys %trans) {
            # Feed potential tasks without content.
            print TEXT $trans{$_}." <empty task>\n";
        }
    }
    close TEXT;
    open(TODO, ">", TMPDIR."/todo");
    print TODO "Ciboulette\tmain\t$id\n";
    for (sort keys %todo) {
        print TODO $todo{$_};
    }
    close TODO;
    unlink(glob TMPDIR."/*.stack ".TMPDIR."/*.content ".TMPDIR."/*.txt"); # don't care about trailing *.txt...
    return undef;
}

sub getopt {
    # Parse the command-line arguments.
    # $f
    my ($force, $test) = (0, 0);
    while(my $opt = shift @ARGV) {
        ++$force and next if $opt =~ /^-(f|-force)$/;
        ++$test and next if $opt =~ /^-(t|-test)$/;
        die "(E) Invalid \"$opt\" argument.\n" unless $opt =~ /^-(h|--help)$/;
        print "tofuup [-f|--force|-h|--help|-t|--test]\n\n".
              "       Further info into \"man 1 tofuup\".\n\n";
        exit 0;
    }
    return ($force, $test);
}

sub maketest {
    # Make a pseudo-todo using the original tofu layout (< 2.x)
    # $f FORCE MODE
    my $force = shift @_;
    rmtree(TESTDIR);
    mkpath(TESTDIR, 0, 0755);
    my @tasks = ("HOT one", "WARM one", "COOL one", "WARM one two", "HOT one two",
                 "COOL one two",  "COOL one three", "HOT two", "WARM three", "COOL four two",
                 "WARM", "COOL", "HOT");
    for my $stack ("alpha", "beta", "gamma") {
        mkdir(TESTDIR."/$stack", 0755);
        open(STACK, ">", TESTDIR."/$stack/stack");
        my $id = @tasks;
        print STACK ">" .($id+2)."\n"; # "+2" becaus of the empty task.
        while ($id > 0) {
            open(TXT, ">", TESTDIR."/$stack/$id.txt");
            print TXT "An old fashioned task resurrected to test\n\n    Zombie #$id.\n\n";
            close TXT;
            print STACK $id--." ".$tasks[$id]."\n";
        }
        print STACK "14 COOL\n"; # add an empty task.
        close STACK;
    }
    return maketmp(1, $force);

}

sub maketmp {
    # Make a temporary directory, exporting the current todo layout.
    # $f TEST MODE, FORCE MODE
    my $src = (shift @_ > 0) ? TESTDIR : TOFUDIR;
    rmtree(shift @_ > 0 ? [TMPDIR, BACKUPDIR] : TMPDIR);
    die "(E) \"".BACKUPDIR."\" already exists, rename it or use the \"--force\" option.\n" if (-e BACKUPDIR);
    mkpath(TMPDIR, 0, 0755);
    my $totem = "Undef";
    if (open (TODO, "<", "$src/todo")) {
        ($totem) = split(/\t/, <TODO>);
        close TODO;
        copy("$src/text", TMPDIR);
        copy("$src/todo", TMPDIR);
    } elsif (defined(glob "$src/*/stack")) {
        # Old layout...
        $totem = defined(glob "$src/*/content") ? "Old2.x" : "Old1.x";
        for (glob "$src/*/stack $src/*/content $src/*/*.txt") {
           copy($_,TMPDIR."/".basename(dirname($_)).".".basename($_));
       }
    }
    my %layout = ("Old1.x", \&old2dotx, "Old2.x", \&ciboulette, TOTEM, "", "Undef", undef);
    return $layout{$totem} if ref($layout{$totem});
    rmtree(TMPDIR); # we won't do anything...
    print "Your todo layout is up-to-date.\n" and exit 0 if defined($layout{$totem});
    die "(E) Unknown todo layout, maybe corrupted or more recent than your tofu version.\n";
}

sub old2dotx {
    # Concatenate the *.txt files into content files (2.x layout).
    # $f
    print "  upgrade   Old1.x -> Old2.x\n";
    my %content = ();
    for my $txt (glob TMPDIR."/*.txt") {
        my ($fd, $id) = split(/\./, basename($txt));
        open($content{$fd}, ">", TMPDIR."/$fd.content") unless exists $content{$fd};
        open(TXT, "<", $txt);
        while(<TXT>) {
            print { $content{$fd} } "$id>$_";
        }
        close TXT;
    }
    for (keys %content) {
        close $content{$_};
    }
    unlink(glob TMPDIR."/*.txt");
    return \&ciboulette;
}

sub switchtodo {
    # Install the  new layout and backup the old.
    # $f TEST MODE
    if (shift @_ > 0) {
        print "  backup    ".TESTDIR." -> ".TESTBACKUPDIR."\n";
        rmtree(TESTBACKUPDIR);
        move(TESTDIR, TESTBACKUPDIR) or die "(E) Failed!\n";
        print "  install   ".TMPDIR." -> ".TESTDIR."\n";
        move(TMPDIR, TESTDIR);
    } else {
        print "  backup    ".TOFUDIR." -> ".BACKUPDIR."\n";
        move(TOFUDIR, BACKUPDIR) or die "(E) Failed!\n";
        print "  install   ".TMPDIR." -> ".TOFUDIR."\n";
        move(TMPDIR, TOFUDIR);
    }
}

# === Main =====================================================================

my ($force, $test) = getopt();
my $step = ($test > 0) ? maketest($force) : maketmp(0, $force);
while (defined($step)) {
    $step = &$step();
}
switchtodo($test);
exit 0;

# EoF
