package VSD::Undo;
require VSD;

# FIX VSD and VSD::* orders etc...

# Perl module for logging and backing up config changes
#  made by VSD.
# Copyright (c) 2000 Idaya Ltd.
# Written by Sol Burt <sol@idaya.co.uk>

# This file is part of the Virtual Server Administrator (FreeVSD)

# FreeVSD is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option)
# any later version.

# FreeVSD is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with FreeVSD; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.


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

use vars qw( @ISA @EXPORT @EXPORT_OK ); 

@ISA = ('Exporter', 'VSD');
#@EXPORT = qw( ); # FIX hmmmmmmm...
#@EXPORT_OK qw( ); # FIX all functions in this module to be exported on request...



my $vsdconfdir = $VSD::path{vsdconfdir};

# FIX temp hardcoded...
my $undo_log = "$vsdconfdir/undo/vsd.undo";


# Add method 'script' to change script and update file_list and command list etc.


sub new {
    my $self = {};
    bless ( $self, shift );
    return $self->_init( @_ );
}

sub _init {
    my $self = shift;
    if (@_) {          #  user can set own id or anything else here...
        my %init = @_;
        @$self{keys %init} = values %init;
    }

    -e $undo_log or return undef;

 # Assign id to an individual object, by looking in log file first
 # But only if the script to undo isn't given...
    (defined $self->{script} && $self->id ('script')) || $self->id ( 'undo' );
    until (keys %{$self->file_list()} || keys %{$self->command_list()} || !$self->{id}) {
	$self->{id}--;
	$self->{action} = 'back';
    }
#    print "\n  id: $self->{id}\n";
    $self->{action} = '';
    !$self->{id} && return undef;
    $self->{bak_path} = "$vsdconfdir/undo/$self->{id}";
    (-e $self->{bak_path}) or die "No backup file path found, $self->{bak_path}: $!";
  # FIX check for existence of log file...
    $self->log('start');
    return $self;
}


sub id {
    my $self = shift;

    @_ && ($self->{id} = $self->log( "$_[0]_id" ));
    return $self->{id};
}

# FIX method to remove dir tree...

sub log {
    my $self = shift;
    my $action = shift;
    my %handlers = (
		    start => sub {
			my $self = shift;
			my ($p, $f, $l, $s) = caller(3);
			defined $self->{log} && die "Log is already started";
			$self->{log} = "<Undo script=\"$f\" date=\"\" time=\"\" id=\"$self->{id}\">\n";
		    },
		    entry => sub {
		        my $self = shift;
			my ($p, $f, $l, $s) = caller(2);
		        $self->{log} .= "\t<revert action=\"$s\">".
			       (defined @_ ? "@_" : '')."</revert>\n";
		    },
		    end => sub {
			$_[0]->{log} .= "</Undo>";
		    },
                    write => sub {
			$_[0]->{log} !~ /^<Undo .+<\/Undo>$/s && $_[0]->log('end');
			open LOG, ">>$undo_log" or 
			    die "Can't open $undo_log :$!";
			print LOG $_[0]->{log}."\n";
		        close LOG;
			undef $self->{log};
			undef $self->{id};
		    },
		    undo_id => sub {
			my $self = shift;
			open LOG, "$undo_log" or 
			    return 0;
			my $id = 0;
			while (<LOG>){     # FIX better way to do this...
                                      /<Change.+?id="(\d+?)"/ && ($id++);
                         # FIX by default, undoes last change  /<Undo.+?id="(\d+?)"/ && ($id--);
                                     }
		        close LOG;
			return $id;
		    },
		    script_id => sub {
			my $self = shift;
			open LOG, "$undo_log" or 
			    return 0;
			my $id = 0;
			while (<LOG>){
                                      /<Change.+?script=".*?([^\/]+?)"/ && ($1 eq $self->{script}) && 
					  /<Change.+?id="(\d+?)"/ && ($id = $1);
                                     }
		        close LOG;
			return $id;
		    }
		    );
    return $handlers{ $action }->($self, @_);
}

sub file {
    my $self = shift;
    my $file = shift;

    my $backup = $self->{bak_path}.$file;
    (-f $backup) || die "Can't find backup file, $backup: $!";
    copy $backup, $file or die "Can't copy $backup to $file: $!";
    $self->log('entry', $file);
}

sub command {
    my $self = shift;
    my $command = shift;

# error checking...
    print `$self->{command_list}->{$command}`;
#	warn "$0: Command failed, ".$self->{command_list}->{$command};
    $self->log('entry', $command);
}

sub file_list {
    my $self = shift;
    (!defined $self->{file_list} || $self->{action} eq 'back') && do {
	$self->{file_list} = {};
	    $self->undone( 'file' );
	    $self->{file_list} = {};
	    my $change = $self->change();
	    $change || die "No change to undo:"; # FIX debug only...
	    while ( $change =~ /<edit .*?file="(.+?)"/sgi ) {
		defined $self->{undone_file_list}->{$1} || ($self->{file_list}->{$1} = $self->{bak_path}.$1);
	    }
    };
    return $self->{file_list};
}

sub command_list {
    my $self = shift;
    (!defined $self->{command_list} || $self->{action} eq 'back') && do {
	$self->{command_list} = {};
	$self->undone( 'command' );
	my $change = $self->change();
	$change || die "No change to undo:"; # FIX debug only...
      # FIX allow for commands without reverse. Comments too
	while ( $change =~ /<command>(.+?)<reverse>(.+?)<\/reverse>(<comment>(.+?)<\/comment>)?/sgi ) {
	    ($self->{command_list}->{$1} = $2) unless defined $self->{undone_command_list}->{$1};
	    defined $3 && ($self->{command_comments}->{$1} = $4);
	}
      # FIX if two commands in the same change are the same...
    };
    return $self->{command_list};
}

sub command_comments {
    my $self = shift;
    return ( defined $self->{command_comments} ? $self->{command_comments} : undef );
}

# FIX not being used yet and needs more. Find perl module that does it already...
#    For commands.
sub unescape {
    $_ = shift;
    s/&lt;/</g;
    s/&gt;/>/g;
    return $_;
}

# By default, look for last change made. Also should be able to reverse 
#  changes made by a particular script..

# FIX temp...

sub change {
    my $self = shift;
    !defined $self->{change} && do {
	open LOG, $undo_log or die "Can't open $undo_log: $!";
	while (<LOG>) { 
	    /<Change .+?id="($self->{id})"/ && do { $self->{change} = $_; last };
	}
	while (<LOG>){
	    $self->{change} .= $_;
	    /<\/Change>/ && last;
	}
	close LOG;
    };
    return $self->{change};
}

# If $self->{script} specified, look for what has already been undone in the log...

sub undone {
    my $self = shift;
    my $what = shift;
    eval "\$self->{undone_${what}_list} = {}";
	open LOG, $undo_log or die "Can't open $undo_log: $!";
	while (<LOG>) { 
	    /<Undo .+?id="($self->{id})"/ && do {
		while (<LOG>){
		    /<revert .+?::$what.+?>(.+?)<\/revert>/ && 
			                 (eval "\$self->{undone_${what}_list}->{\$1}++");
		    /<\/Undo>/ && last;
		} 
	    };
	}
	close LOG;
}



sub DESTROY {
    my $self = shift;
    # write changes here if the Change object disappears B4 being written...
    defined $self->{log} && $self->log('write');

# FIX FIX 
#
#  Perhaps make this rewrite log without last change, so that ->undo always just undoes last change.
#        Then can do while ($undo->undo){    loop back over changes...   }
#
######################################
#
#
#
#

}























