package NewSpirit;

$VERSION  = "0.01";

use strict;
use Carp;
use NewSpirit::Passwd;
use NewSpirit::Session;
use NewSpirit::LKFile;
use NewSpirit::DataFile;
use Time::Local;
use FileHandle;
use File::Basename;
use File::Copy;
use File::Find;
use File::Path;

my %MONTH = (
	"01" => "Jan",
	"02" => "Feb",
	"03" => "Mar",
	"04" => "Apr",
	"05" => "May",
	"06" => "Jun",
	"07" => "Jul",
	"08" => "Aug",
	"09" => "Sep",
	"10" => "Oct",
	"11" => "Nov",
	"12" => "Dec"
);

sub crypt_credentials {
	my ($username, $password) = @_;
	
	my $credentials = "$username:$password";
	for ( my $i=0; $i < length($credentials); ++$i ) {
		substr($credentials,$i,1) =
			chr(ord(substr($credentials,$i,1))+3);
	}
	
	return $credentials;
}

sub decrypt_credentials {
	my ($credentials) = @_;

	for ( my $i=0; $i < length($credentials); ++$i ) {
		substr($credentials,$i,1) =
			chr(ord(substr($credentials,$i,1))-3);
	}
	
	my ($username, $password) = split (/:/, $credentials, 2);
	
	return ($username, $password);
}

sub check_session_and_init_request {
	my $q = shift;
	
	my ($window, $username);

	my $project     = $q->param('project');
	my $ticket      = $q->param('ticket');
	my $credentials = $q->param('credentials');
	
	my ($username, $password);

	my $sh;
	if ( $ticket eq '' and $credentials ne '' ) {
		# on the fly login via newspirit command line client
		my $ph = new NewSpirit::Passwd ($q);
		($username, $password) = decrypt_credentials($credentials);
		if ( $ph->check_password ($username, $password) ) {
			$ph = undef;	# unlock passwd
			$sh = new NewSpirit::Session;
			$ticket  = $sh->create ($q->remote_addr(), $username);
			$q->param('ticket',$ticket);
		}
		
		#
	} else {
		$sh = new NewSpirit::Session;
	}
	
	eval { ($username, $window) = $sh->check ($ticket, $q->remote_addr) };
	my $error = $@;

	if ( $q->param('window') ) {
		$window = 1;
	}

	if ( not $error ) {
		if ( $project ) {
			$sh = undef; # unlock session (prevents deadlock)
			my $ph = new NewSpirit::Passwd ($q);
			if ( not $ph->check_project_access($username, $project) ) {
				$error = "You have no access on this project!";
			}
			$ph = undef; # unlock passwd

			# create session object again
			$sh = NewSpirit::Session->new;
			$sh->check ($ticket, $q->remote_addr());
		}
	}

	if ( $error ) {
		print <<__HTML;
<html>
<head><title>$CFG::window_title</title></head>
<body bgcolor="$CFG::BG_COLOR">
$CFG::FONT
<b>Your user session is invalid. Please <a target="NEWSPIRIT" href="$CFG::admin_url">login</a> again.</b>
</FONT>
</body>
</html>
__HTML
		exit;
	}
	
	$q->param('username', $username);
	$q->param('window', $window);

	read_user_config($username);

	return $sh;
}

sub remove_on_the_fly_session {
	my ($q) = @_;
	return if not $q->param('credentials');
	
	delete_session($q);
	
	1;
}

sub clone_session {
	my ($q, $window) = @_;

	# ok, session data should be copied from our actual session,
	# so we add our ticket to the $sh->create call.

	my $sh = new NewSpirit::Session;
	my $ticket = $sh->create (
		$q->remote_addr(),
		$q->param('username'),
		$q->param('ticket'),
		$window
	);
	$sh = undef;
	
	# update the ticket in query object
	$q->param('ticket', $ticket);

	1;
}

sub print_error {
	my ($err) = @_;
	
	print "</td></tr></table>\n";
	print "</td></tr></table>\n";
	print "</td></tr></table>\n";
	print "</td></tr></table>\n";
	print "</td></tr></table>\n";
	print "<P>$CFG::FONT<B>Internal Error</B></font><P><PRE>$err</PRE>\n";
}

sub blank_page {
	print <<__HTML;
<html>
<head><title>$CFG::window_title</title></head>
<body bgcolor="$CFG::BG_COLOR">
</body>
</html>
__HTML
}

sub read_user_config {
	my ($username) = @_;
	
	my $filename = "$CFG::user_conf_dir/$username.conf";
	
	return if not -f $filename;

	my $lf = new NewSpirit::LKFile ($filename);
	my $data = $lf->read;

	{
		no strict;
		eval $$data;
		croak "error reading user config '$filename': $@" if $@;
	}
	
	1;
}

sub get_project_info {
	my ($project) = @_;
	
	my $filename = "$CFG::project_conf_dir/$project.conf";
	
	my $df = new NewSpirit::DataFile ($filename);
	
	return $df->read;
}

sub start_page {
	my %par = @_;
	$par{title} ||= $CFG::window_title;
	$par{bgcolor} ||= $CFG::BG_COLOR;
	$par{marginheight} ||= 1;
	$par{marginwidth} ||= 1;

	my $head;
	if ( $par{link_style} eq 'plain' ) {
		$head .= q|<style type="text/css">A:visited,A:link,A:|.
			 q|active{text-decoration:none}</style>|;
	}
	
	print <<__HTML;
<html>
<head>
  <title>$par{title}</title>
  $head
</head>
<body bgcolor="$par{bgcolor}" link="$CFG::LINK_COLOR"
      alink="$CFG::ALINK_COLOR" vlink="$CFG::VLINK_COLOR"
      text="$CFG::TEXT_COLOR" marginheight="$par{marginheight}"
      marginwidth="$par{marginwidth}">
__HTML
}

sub end_page {
	print <<__HTML;
</body>
</html>
__HTML
}

sub open_session_file {
	my ($ticket) = @_;
	
	return new NewSpirit::LKDB ("$CFG::session_dir/$ticket");
}

sub js_open_window {
	my $q = shift;

	my $ticket = $q->param('ticket');
	my $r = int(rand(10000));
	my $rand_window_name = "WIN$ticket$r";

	print <<__HTML;
<SCRIPT LANGUAGE="JavaScript">
  function open_window (url, name, sizex, sizey, posx, posy, return_obj, with_status_bar) {
    if ( name == null ) {
      var r = Math.floor(Math.random()*100000);
      name = 'WIN$ticket'+r;
    }

    var geometry = '';
    
    if ( sizex > 0 ) {
      geometry=",width="+sizex+",height="+sizey;
    }

    if ( posx > 0 ) {
      geometry = geometry + ',screenX='+posx + ',screenY='+posy;
    }

    var window_options;
    if ( ! with_status_bar ) {
        window_options =
        'toolbar=no,location=no,directories=no,status=no,menubar=no,'+
        'resizable=yes,scrollbars=yes';
    } else {
        window_options =
        'toolbar=yes,location=yes,directories=yes,status=yes,menubar=yes,'+
        'resizable=yes,scrollbars=yes';
    }
    window_options += geometry;

    // we open the window without URL, maybe it exists already
    var w = window.open (
        url,
        name,
	window_options
    );

    w.focus();
  
    if ( return_obj ) {
      return w;
    }
    return;

    // the code beyond is actually disabled...

    // does the window have a location.href? If so, it was already
    // existant and we will not modify its geometry

    alert ('window name='+name);

    if ( w.document.location.href == '' ) {
      if ( sizex > 0 ) {
        w.outerWidth = sizex;
	w.outerHeight = sizey;
      }
      if ( posx > 0 ) {
        w.pageXOffset = posx;
	w.pageYOffset = posy;
      }
    }

    if ( url != '' ) {
      w.document.location.href = url;
    }

    w.focus();
  
    if ( return_obj ) {
      return w;
    }
  }
</SCRIPT>
__HTML
}

sub get_timestamp {
        my ($sec, $min, $hour, $mday, $mon, $year) = localtime(time);

        ++$mon;

        $mon = "0".$mon if $mon < 10;
        $mday = "0".$mday if $mday < 10;
        $hour = "0".$hour if $hour <10;
        $min = "0".$min if $min < 10;
        $sec = "0".$sec if $sec < 10;

	$year += ($year < 97) ? 2000 : 1900;

	return "$year.$mon.$mday-$hour:$min:$sec";
}

sub timestamp2time {
	my ($timestamp) = @_;
	
	# never tested this routine!!!!
	
	return timelocal (reverse(split(/[-:.]/,$timestamp)));
}

sub format_timestamp {
	my ($timestamp) = @_;

	$timestamp =~ /^(\d+)\.(\d+)\.(\d+)-(\d+):(\d+):(\d+)$/;
	my $day = $3;
	my $date = $MONTH{$2}." $1 - $4:$5:$6";
	$day =~ s/^0//;	
	$date = "$day $date";
	$date =~ s/\s/&nbsp;/g;
	
	return $date;
}

sub strip_exception {
	my ($exception) = @_;
	
	$exception =~ s! at /.*!!;
	
	return $exception;
}

sub dump {
	eval {
		require "Data/Dumper.pm";
		print STDERR "$0 $$\n", Data::Dumper::Dumper (@_), "\n";
		croak "called from";
	};
	$@ =~ s/from at/from/;
	print STDERR $@;
}

sub dump_html {
	require "Data/Dumper.pm";
	print "<pre>", Data::Dumper::Dumper(@_), "</pre><p>\n";
}

sub std_header {
	my %par = @_;
	
	my $page_title   = $par{page_title};
	my $close        = $par{close};
	my $window_title = $par{window_title};
	
	$window_title ||= $page_title;
	
	NewSpirit::start_page (
		title => $window_title,
		marginwidth => 5,
		marginheight => 5,
		link_style => 'plain'
	);
	
	print <<__HTML;
<table BORDER=0 BGCOLOR="$CFG::TABLE_FRAME_COLOR"
       CELLSPACING=0 CELLPADDING=1 WIDTH="100%">
<tr><td>
  <table $CFG::TABLE_OPTS width="100%">
  <tr><td>
    $CFG::FONT_BIG<b>$page_title</b></FONT>
  </td>
__HTML
	if ( $close ) {
		print <<__HTML;
  <td valign="center" align="right">
    $CFG::FONT<b>
    <a href="javascript:window.close()">CLOSE WINDOW</a>
    </b></FONT>
  </td>
__HTML
	}
	
	print <<__HTML;  
  </tr>
  </table>
  </td></tr>
</table>
<p>
__HTML
}

sub delete_lock {
	my $q = shift;

	my $project = $q->param('project');

	return 1 if not $project;
	
	my $project_info = get_project_info ($project);

	my $lock = new NewSpirit::Lock (
		project_meta_dir  => "$project_info->{root_dir}/meta",
		username          => $q->param('username'),
		ticket            => $q->param('ticket')
	);
	$lock->delete;

	1;
}

sub delete_session {
	my $q = shift;
	
	my $sh = new NewSpirit::Session;
	$sh->delete ($q->param('ticket'));
	$sh = undef;

	1;
}

sub filename_glob {
	my %par = @_;
	
	my $dir      = $par{dir};
	my $regex    = $par{regex};

	my $dh = new FileHandle;
	opendir $dh, $dir or 
	    die ("Can't open directory '$dir'");
	my @filenames = map "$dir/$_", grep /$regex/, readdir $dh;
	closedir $dh;
	
	return \@filenames;
}

sub copy_tree {
	my %par = @_;
	
	my $from_dir = $par{from_dir};
	my $to_dir   = $par{to_dir};
	my $verbose  = $par{verbose};
	my $filter   = $par{filter};

#	print "from_dir='$from_dir'<br>\n";
#	print "to_dir='$to_dir'<p>\n";

	# content of $from_dir will be copied inside $to_dir
	# missing paths will be created!
	
	my $cnt = 1;	# counter for verbosity
	
	find (
		sub {
			my $dir  = $File::Find::dir;
			my $file = $_;

			my $from_file = "$dir/$file";
			$dir =~ s!^$from_dir!!;	# make relative
			$dir =~ s!/$!!;
			my $to_file   = "$to_dir/$dir/$file";

			return if $file eq '.';
			return if $filter and $file !~ /$filter/;

			if ( $verbose ) {
				--$cnt;
				if ( $cnt == 0 ) {
					print "copying...<br>\n";
					print "<script>self.window.scroll(0,5000000)</script>\n";
					print "<script>self.window.scroll(0,5000000)</script>\n";
					$cnt = 50;
				}
			}

			if ( $filter ) {
				# if filtering is on, it may happen, that
				# the base directory does not exist. So
				# we may need to do a mkpath here.
				my $to_dir = dirname $to_file;
				if ( not -d $to_dir ) {
					my $from_dir = dirname $from_file;
					my $dir_mode = (stat($from_dir))[2];
					mkpath ([$to_dir], 0, $dir_mode)
						or croak "can't create dir '$to_dir': $!";
				}
			}

			my @stat = stat($from_file);
			my $mode  = $stat[2];
			my $atime = $stat[8];
			my $mtime = $stat[9];

			if ( -d $from_file ) {
				# Ok, this is a directory.
				# create $target_dir, if not existent yet
				if ( not -d $to_file ) {
					mkpath ([$to_file], 0, $mode)
						or croak "can't create dir '$to_file': $!";
				}
			} else {
				# This is a file: copy it.
				copy ($from_file, $to_file)
					or croak "can't copy file '$from_file' to '$to_file': $!";
			}

			# set filemode, atime and mtime
			chmod $mode, $to_file;
			utime $atime, $mtime, $to_file;
		},
		$from_dir
	);

}

1;