#!/usr/bin/perl

# $Id: //depot/cad/tools/1.0/icp4-1.1/bin/p4ci#31 $
#
# CSWITCH CORPORATION CONFIDENTIAL PROPRIETARY
# 
# This file contains information which is the
# proprietary property of Cswitch Corporation.
# This file is confidential and its contents may
# not be disclosed without the expressed written
# consent of Cswitch Corporation.


# For each cell given:
#   error check
#   make list of cells to add/submit
#   bring up edit page
#   Do together:
#     revert -a
#     add
#     submit

use strict;
use warnings;

use FindBin qw($Bin);
use lib "$Bin/../lib";

use Icp4;
use Getopt::Std;
use File::Basename;
use Cwd;

our ($Program, %opt, $islayout, $nflag, $rflag, $client, $cells, $support);

our $attr_sfx = ".Ic_cell_template.attr";
our $cell_sfx = ".iccel_[0-9]+";

our $keep = 1;

# each file in raw_list has a file status:
#   raw
#   edit
#   add
#   revert
our %file_status;
our @raw_list;

our @revert_list;
our @add_list;
our @edit_list;

our @user_add_list;
our @user_edit_list;

# turn on logging of p4ci to ~/p4ci.log
my $P4CI_LOG = 1;
#my $P4CI_LOG = 0;


MAIN: {

    $Program = $FindBin::Script;

    getopts('hDnirk:', \%opt);

    if ($opt{h} || $#ARGV < 0) {
	print "Usage: $Program [-h] [-n] [-i] [-k num] [-r] cell+\n";
	print "   -h: help usage\n";
	print "   -n: show operation only\n";
	print "   -i: ignore pending changes (dangerous!)\n";
	print "   -k: number of versions to keep\n";
	print "       (default: $keep, works for layout only for now)\n";
	print "   -r: remain open for edit after checkin\n";
	print "       (default: locked after checkin)\n";
	print " cell: cell name(s)\n";
	print "       (hint: use \\\$ for cells with \$ character in their name)\n";
	exit 0;
    }

    $nflag = ($opt{n}) ? "-n" : "" ;
    $rflag = ($opt{r}) ? "-r" : "" ;
    $keep = $opt{k} if ($opt{k});

    #$Icp4::debug = ($opt{D}) ? 1 : 0 ;
    # keep Icp4 quiet
    $Icp4::debug = 0;
    $Icp4::nflag = ($opt{n}) ? 1 : 0 ;

    # make sure p4 is configured
    if (&Icp4::where() eq 'error') {
	print "ERROR: Current directory not in client.  Please check P4 variables.\n";
	exit -1;
    }
    $client = &Icp4::p4client();
    $support = "ryu\@cswitch.com";

    # check for outstanding changes
    if (!$opt{i} && &Icp4::outstanding_changes()) {
	exit -1;
    }

    if ($islayout = &Icp4::is_layout_view()) {

	# At this point, the raw_list contains only attr and iccel files
	# that are known to exist in unix.
	get_raw_list();
	if ($opt{D}) {
	    &Icp4::list_debug_print("raw_list", @raw_list);
	}

	# revert list is a subset of raw_list and are files that have not been modified
	get_revert_list();
	if ($opt{D}) {
	    &Icp4::list_debug_print("revert_list", @revert_list);
	}

	# edit list is a subset of raw_list and are files that have been modified
	get_edit_list();
	if ($opt{D}) {
	    &Icp4::list_debug_print("edit_list", @edit_list);
	}

	# add list is a subset of raw_list and new files to p4
	get_add_list();
	if ($opt{D}) {
	    &Icp4::list_debug_print("add_list", @add_list);
	}

	# Bring up the form.  all files listed will be
	# reverted if not changed, added if it's new, and then submitted.
	p4_add_submit_revert();

    } else {
	old_p4ci();
    }

    exit 0;
}


sub get_raw_list {
    my ($cell, $orig_cell, $path);
    my ($attr_file, $orig_attr_file, $ic_pat, $ic_file);
    my ($openStat, $expectedStat);
    my (@keep_list, $i, $s, $orig_cnt, $exists);

    $cells = "";
    foreach $cell (@ARGV) {

	# get rid of any trailing "/" and convert to full path
	$cell =~ s/\/$//;
	$path =  Cwd::abs_path(dirname($cell));

	$orig_cell = $cell;

	# escape the name for p4
	if ($cell =~ /\$/) {
	    $cell =~ s/\$/\\\$/g;
	    if ($opt{D}) {
		print "# DEBUG: path      = $path\n";
		print "# DEBUG: cell      = $cell\n";
		print "# DEBUG: orig_cell = $orig_cell\n";
	    }
	}

	if (-d $orig_cell) {

	    # remove any .iclck files
	    unlink <${orig_cell}/.*.iclck>;

	    $cells = $cells . "${cell}/... ";

	    #$cell_attr = $cell . $attr_sfx;
	    $ic_pat    = $cell . $cell_sfx;

	    $orig_attr_file = "${orig_cell}/${orig_cell}${attr_sfx}";
	    $attr_file = "${cell}/${cell}${attr_sfx}";

	    if (has_lckfiles($cell)) {
		print "WARNING: Cell \"$cell\" is locked by icstudio.  Skipping cell \"$cell\".\n";
		next;
	    }

	    # workaround for some attribute files that have .s
	    if (! -f "$orig_attr_file") {
		print "WARNING: $orig_attr_file not found.  Trying the .s trick.\n";
		$orig_attr_file = "${orig_cell}/${orig_cell}.s${attr_sfx}";
		$attr_file = "${cell}/${cell}.s${attr_sfx}";
		if (! -f "$orig_attr_file") {
		    print "WARNING: $orig_attr_file not found either.  Skipping \"$orig_cell\".\n";
		    next;
		} else {
		    print "INFO: Found $orig_attr_file.\n";
		}
	    }

	    &Icp4::get_keep_list($orig_attr_file, \@keep_list);

	    if ($opt{D}) {
		&Icp4::list_debug_print("keep_list", @keep_list);
	    }

	    $orig_cnt = $#keep_list;
	    for ($i=0; $i <= ($orig_cnt - $keep); $i++) {
		shift @keep_list;
	    }

	    if ($opt{D}) {
		print "# DEBUG: After trimming ... $#keep_list\n";
		&Icp4::list_debug_print("keep_list", @keep_list);
	    }

	    # existence check for @keep_list files
	    $exists = 1;
	    foreach $i (@keep_list) {
		$ic_file = "$path/$orig_cell/$i";
		if (! -f $ic_file) {
		    $exists = 0;
		    print "ERROR: \"$ic_file\" not found.  Skipping cell \"$cell\".\n"; 
		    last;
		}
	    }

	    if ($exists) {
		$s = "${path}/${orig_attr_file}";
		push (@raw_list, $s);
		$file_status{$s} = "raw";
		foreach $i (@keep_list) {
		    $s = "${path}/${orig_cell}/$i";
		    push (@raw_list, $s);
		    $file_status{$s} = "raw";
		}
	    }
	}
    }
    close FOUT;
}


sub get_revert_list {
    my($rawfile) = ".rawlist.$$";
    my ($s);

    open (FOUT, ">$rawfile") or die "ERROR: cannot open $rawfile.";
    foreach $s (@raw_list) {
	print FOUT "$s\n";
    }
    close FOUT;

    open (REVERT, "p4 -x $rawfile revert -a -n 2>&1 |") or die "ERROR: cannot run p4 revert.";
    while (<REVERT>) {
	if (($s) = /(.*)#\d+ - was edit, reverted/) {
	    # change name
	    $s =~ s#//depot#$client#;
	    push (@revert_list, $s);
	    if ($file_status{$s}) {
		$file_status{$s} = "revert";
	    } else {
		die "INTERNAL ERROR: file status of $s not found.  Please notify $support.\n";
	    }
	}
    }
    close REVERT;
    unlink $rawfile unless ($opt{D});
}


sub get_edit_list {
    my (%p4files) = Icp4::fstat(@raw_list);
    my ($s);
    @edit_list = ();
    foreach $s (sort keys %p4files) {
	# print "# DEBUG: file \"$s\" has p4 status \"$p4files{$s}\".\n" if ($opt{D});
	if ($p4files{$s} eq 'edit') {
	    if ($file_status{$s}) {
		if ($file_status{$s} ne 'revert') {
		    push (@edit_list, $s);
		    $file_status{$s} = "edit";
		}
	    } else {
		die "INTERNAL ERROR: file status of \"$s\" not found during get_edit_list.  Please notify $support.\n";
	    }
	}
    }
}


sub get_add_list {
    my (%p4files) = Icp4::fstat(@raw_list);
    my ($s);
    @add_list = ();
    foreach $s (sort keys %p4files) {
	if (($p4files{$s} eq '???') || ($p4files{$s} eq 'delete')) {
	    if ($file_status{$s}) {
		push (@add_list, $s);
		$file_status{$s} = "add";
	    } else {
		die "INTERNAL ERROR: file status of \"$s\" not found during get_add_list.  Please notify $support.\n";
	    }
	}
    }
}


sub p4_add_submit_revert {
    my($file, $editor);
    my($proceed);
    my($extract);
    my($list_ptr, $s, $type, $depot_file);

    # create a changelist by combining list of opened
    # files with changelist template
    my($template) = ".template.$$";
    my($changelist) = ".changelist.$$";

    system("p4 change -o > $template");

    # When a workspace has nothing opened, the $template file will have no "Files:" line
    # Add it to the bottom of the file                      David Guan
    my($cnt);
    chop($cnt = `egrep -c Files $template`);
    if ($cnt == 1) {
	system("echo 'Files:' >> $template");
    }

    open (TEMPLATE, "$template") or die "ERROR: cannot open $template.";
    open (CHANGELIST, ">$changelist") or die "ERROR: cannot open $changelist.";

    while (<TEMPLATE>) {
	if (/^Files:/) {
	    print CHANGELIST;

	    foreach $file (sort keys %file_status) {
		$depot_file = $file;
		$depot_file =~ s#$client#//depot#;
		print CHANGELIST "\t$depot_file\t# add\n" if ($file_status{$file} eq "add");
	    }

	    foreach $file (sort keys %file_status) {
		$depot_file = $file;
		$depot_file =~ s#$client#//depot#;
		print CHANGELIST "\t$depot_file\t# edit\n" if ($file_status{$file} eq "edit");
	    }

	    last;

	} else {
	    print CHANGELIST;
	}
    }
    close CHANGELIST;

    $editor = $ENV{'P4EDITOR'} ? $ENV{'P4EDITOR'} :
		$ENV{'EDITOR'} ? $ENV{'EDITOR'} : "vim" ;

    # now bring up the editor and have user say something nice
    system ("$editor $changelist");

    # Read the editted changelist, and look for the "<enter description here>".
    # If found, then do no p4 action, otherwise, create the real p4 changelist.

    open (CHANGELIST, "$changelist") or die "ERROR: cannot open $changelist.";

    $proceed = 1;
    $extract = 0;
    while (<CHANGELIST>) {
	if (/<enter description here>/) {
	    $proceed = 0;
	    next;
	}

	if (/^Files:/) {
	    if ($proceed == 0) {
		print "ERROR:  Change description missing.  You must enter a description to continue.\n";
		exit -1;
	    }
	    $extract = 1;
	    next;
	}

	if ($extract) {
	    if (($s, $type) = /^\t(.*)\t# (add|edit)/) {
		$s =~ s#//depot#$client#;
		if ($type eq "add") {
		    push (@user_add_list, $s);
		} else {
		    push (@user_edit_list, $s);
		}
		# check the file are still under the same list
		if ($file_status{$s} ne $type) {
		    print "ERROR: file \"$s\" should have been \"$file_status{$s}\" but was set to \"$type\".\n";
		    exit -1;
		}
		next;
	    }
	}
    }
    close CHANGELIST;

    if ($opt{D}) {
	&Icp4::list_debug_print("user_add_list", @user_add_list);
	&Icp4::list_debug_print("user_edit_list", @user_edit_list);
    }

    # now do these together
    p4_add(\@user_add_list) if ($#user_add_list >= 0);
    p4_submit($changelist) if (($#user_add_list >= 0) || ($#user_edit_list >= 0));
    p4_revert_cells() if (!$opt{r});

    # cleanup
    unlink $template unless ($opt{D});
    unlink $changelist unless ($opt{D});
}


sub p4_add {
    my($list_ptr) = @_;
    my($s, $cmd);
    my($template) = ".addfiles.$$";
    open (TEMPLATE, ">$template") or die "ERROR: cannot open $template.";
    foreach $s (@{$list_ptr}) {
	print TEMPLATE "$s\n";
    }
    close TEMPLATE;
    $cmd = "p4 -x $template add $nflag";
    if ($opt{n}) {
	print "INFO: Adding (not run) new files using (\"$cmd\")\n";
    } else {
	print "INFO: Adding new files using (\"$cmd\")\n";
    }
    system $cmd;
    unlink $template unless ($opt{D});
}


sub p4_submit {
    my ($changelist) = @_;
    my ($s, $cmd);
    $cmd = "p4 submit $rflag -i < $changelist";
    if ($opt{n}) {
	print "INFO: Submitting (not run) changes using (\"$cmd\")\n";
    } else {
	print "INFO: Submitting changes using (\"$cmd\")\n";
    }
    system $cmd unless ($opt{n});
}


# Unused: this may leave some files in edit mode because p4co did 'p4 edit cell/...'
# Better to use p4_revert_cells instead
sub p4_revert {
    my($list_ptr) = @_;
    my($s, $cmd);
    my($template) = ".revertfiles.$$";
    open (TEMPLATE, ">$template") or die "ERROR: cannot open $template.";
    foreach $s (@{$list_ptr}) {
	print TEMPLATE "$s\n";
    }
    close TEMPLATE;
    $cmd = "p4 -x $template revert -a $nflag";
    if ($opt{n}) {
	print "INFO: Reverting (not run) unchanged files using (\"$cmd\")\n";
    } else {
	print "INFO: Reverting unchanged files using (\"$cmd\")\n";
    }
    system $cmd;
    unlink $template unless ($opt{D});
}


sub p4_revert_cells {
    my($cmd);
    $cmd = "p4 revert -a $nflag $cells > /dev/null 2>&1";
    if ($opt{n}) {
	print "INFO: Reverting (not run) all unchanged files using (\"$cmd\")\n";
    } else {
	print "INFO: Reverting all unchanged files using (\"$cmd\")\n";
    }
    system $cmd;
}


# has_lckfiles --
#	returns true if lck files found
sub has_lckfiles {
    my($dir) = @_;

    open(FIND, "find $dir -name \*.lck\* -print |") or die "ERROR: cannot run find.";
    while (<FIND>) {
	return 1;
    }
    return 0;
}

# ----------------------------------------------------------------------------------------

# TODO: support schematics

# old_p4ci --
#	old p4ci, which is used for schematics
sub old_p4ci {

    my ($p4local_flag, $cell, $cell_dirname);

    if ($P4CI_LOG) {
	$p4local_flag = "-L $ENV{HOME}/p4ci.log";
    }

    $cells = "";

    # Let's check for lck files first.  And if found any, just quit
    foreach $cell (@ARGV) {
	$cell_dirname = $cell;
	if ($cell =~ /\$/) {
	    $cell =~ s/\$/\\\$/g;
	}

	if (-d $cell_dirname) {
	    if (has_lckfiles($cell)) {
		print "ERROR: Cell \"$cell\" is locked by icstudio.  Please remove lock and try again.\n";
		exit -1;
	    }
	}
    }

    foreach $cell (@ARGV) {
	$cell_dirname = $cell;
	if ($cell =~ /\$/) {
	    $cell =~ s/\$/\\\$/g;
	}

	if (-d $cell_dirname) {

	    # remove any .iclck files
	    unlink <${cell_dirname}/.*.iclck>;

	    # add any new $cell.* files ...
	    system "p4local $p4local_flag -a ${cell}.*";

	    # or files under the $cell directory
	    if ((system "p4local $p4local_flag -a $cell") == 0) {
		$cells = $cells . " ${cell}.* ${cell}/...";
	    } else {
		# some bad happened, maybe a lck file found
		print "INFO: No operation performed for cell $cell.\n";
	    }
	}
    }

    if ($cells ne "") {
	if (!$opt{r}) {
	    print "INFO: Reverting any unmodified files (\"p4 revert $nflag -a $cells\")\n";
	    system "p4 revert $nflag -a $cells";
	}

	&Icp4::submit_open_files($cells, $rflag);
    }

    exit 0;
}