/*
 *	aegis - project change supervisor
 *	Copyright (C) 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999 Peter Miller;
 *	All rights reserved.
 *
 *	This program 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 of the License, or
 *	(at your option) any later version.
 *
 *	This program 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 this program; if not, write to the Free Software
 *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 * MANIFEST: functions to implement new project
 */

#include <ac/ctype.h>
#include <ac/stdio.h>
#include <ac/stdlib.h>
#include <ac/string.h>

#include <ael/project/projects.h>
#include <aenbr.h>
#include <aenpr.h>
#include <arglex2.h>
#include <change.h>
#include <change_bran.h>
#include <commit.h>
#include <error.h>
#include <gonzo.h>
#include <help.h>
#include <io.h>
#include <lock.h>
#include <os.h>
#include <progname.h>
#include <project.h>
#include <project/pattr/edit.h>
#include <project/pattr/get.h>
#include <project/pattr/set.h>
#include <project_hist.h>
#include <sub.h>
#include <trace.h>
#include <undo.h>
#include <user.h>


static void new_project_usage _((void));

static void
new_project_usage()
{
	char	*progname;

	progname = progname_get();
	fprintf(stderr, "usage: %s -New_Project <name> [ <option>... ]\n", progname);
	fprintf(stderr, "       %s -New_Project -List [ <option>... ]\n", progname);
	fprintf(stderr, "       %s -New_Project -Help\n", progname);
	quit(1);
}


static void new_project_help _((void));

static void
new_project_help()
{
	help("aenpr", new_project_usage);
}


static void new_project_list _((void));

static void
new_project_list()
{
	arglex();
	while (arglex_token != arglex_token_eoln)
		generic_argument(new_project_usage);
	list_projects(0, 0);
}


static void new_project_main _((void));

static void
new_project_main()
{
	sub_context_ty	*scp;
	string_ty	*home;
	string_ty	*s1;
	string_ty	*project_name;
	project_ty	*pp;
	project_ty	*ppp;
	user_ty		*up;
	string_ty	*bl;
	string_ty	*hp;
	string_ty	*ip;
	long		version_number[10];
	int		version_number_length;
	project_ty	*version_pp[SIZEOF(version_number)];
	string_ty	*version_string;
	long		j;
	int		keep;
	pattr		pattr_data;
	edit_ty		edit;

	trace(("new_project_main()\n{\n"/*}*/));
	project_name = 0;
	home = 0;
	version_number_length = 0;
	version_string = 0;
	keep = -1;
	pattr_data = 0;
	edit = edit_not_set;
	while (arglex_token != arglex_token_eoln)
	{
		switch (arglex_token)
		{
		default:
			generic_argument(new_project_usage);
			continue;

		case arglex_token_project:
			if (arglex() != arglex_token_string)
				option_needs_name(arglex_token_project, new_project_usage);
			/* fall through... */

		case arglex_token_string:
			if (project_name)
				duplicate_option_by_name(arglex_token_project, new_project_usage);
			project_name = str_from_c(arglex_value.alv_string);
			break;

		case arglex_token_directory:
			if (home)
				duplicate_option(new_project_usage);
			if (arglex() != arglex_token_string)
				option_needs_dir(arglex_token_directory, new_project_usage);
			/*
			 * To cope with automounters, directories are stored as
			 * given, or are derived from the home directory in the
			 * passwd file.  Within aegis, pathnames have their
			 * symbolic links resolved, and any comparison of paths
			 * is done on this "system idea" of the pathname.
			 */
			home = str_from_c(arglex_value.alv_string);
			break;

		case arglex_token_edit:
			if (edit == edit_foreground)
				duplicate_option(new_project_usage);
			if (edit != edit_not_set)
			{
				too_many_edits:
				mutually_exclusive_options
				(
					arglex_token_edit,
					arglex_token_edit_bg,
					new_project_usage
				);
			}
			edit = edit_foreground;
			break;

		case arglex_token_edit_bg:
			if (edit == edit_background)
				duplicate_option(new_project_usage);
			if (edit != edit_not_set)
				goto too_many_edits;
			edit = edit_background;
			break;

		case arglex_token_file:
			if (pattr_data)
				duplicate_option(new_project_usage);
			if (arglex() != arglex_token_string)
				option_needs_file(arglex_token_file, new_project_usage);
			os_become_orig();
			pattr_data = pattr_read_file(arglex_value.alv_string);
			os_become_undo();
			assert(pattr_data);
			break;

		case arglex_token_major:
			if (version_number_length > 0 && version_number[0])
				duplicate_option(new_project_usage);
			scp = sub_context_new();
			sub_var_set(scp, "Name1", "%s", arglex_token_name(arglex_token_major));
			sub_var_set(scp, "Name2", "%s", arglex_token_name(arglex_token_version));
			error_intl(scp, i18n("warning: $name1 obsolete, use $name2 option"));
			sub_context_delete(scp);
			if (arglex() != arglex_token_number)
				option_needs_number(arglex_token_major, new_project_usage);
			if (version_number_length < 1)
				version_number_length = 1;
			version_number[0] = arglex_value.alv_number;
			if (version_number[0] <= 0)
				option_needs_number(arglex_token_major, new_project_usage);
			break;

		case arglex_token_minor:
			if (version_number_length >= 2 && version_number[1])
				duplicate_option(new_project_usage);
			scp = sub_context_new();
			sub_var_set(scp, "Name1", "%s", arglex_token_name(arglex_token_minor));
			sub_var_set(scp, "Name2", "%s", arglex_token_name(arglex_token_version));
			error_intl(scp, i18n("warning: $name1 obsolete, use $name2 option"));
			sub_context_delete(scp);
			if (arglex() != arglex_token_number)
				option_needs_number(arglex_token_minor, new_project_usage);
			if (version_number_length < 1)
				version_number[0] = 0;
			if (version_number_length < 2)
				version_number_length = 2;
			version_number[1] = arglex_value.alv_number;
			if (version_number[1] <= 0)
				option_needs_number(arglex_token_minor, new_project_usage);
			break;

		case arglex_token_version:
			if (version_string)
				duplicate_option(new_project_usage);
			switch (arglex())
			{
			default:
				option_needs_number(arglex_token_version, new_project_usage);

			case arglex_token_number:
			case arglex_token_string:
				version_string =
					str_from_c(arglex_value.alv_string);
				break;

			case arglex_token_stdio:
				version_string = str_from_c("");
				break;
			}
			break;

		case arglex_token_wait:
		case arglex_token_wait_not:
			user_lock_wait_argument(new_project_usage);
			break;

		case arglex_token_keep:
			if (keep > 0)
				duplicate_option(new_project_usage);
			if (keep >= 0)
			{
				mut_exc_keep:
				mutually_exclusive_options
				(
					arglex_token_no_keep,
					arglex_token_keep,
					new_project_usage
				);
			}
			keep = 1;
			break;

		case arglex_token_no_keep:
			if (keep == 0)
				duplicate_option(new_project_usage);
			if (keep >= 0)
				goto mut_exc_keep;
			keep = 0;
			break;
		}
		arglex();
	}
	if (keep < 0)
		keep = 0;
	if (!project_name)
	{
		error_intl(0, i18n("no project name"));
		new_project_usage();
	}
	if (version_number_length > 0)
	{
		if (version_string)
		{
			error_intl(0, i18n("don't mix old and new version options"));
			new_project_usage();
		}
		if (keep)
		{
			mutually_exclusive_options
			(
				arglex_token_keep,
				(
					version_number_length < 2
				?
					arglex_token_major
				:
					arglex_token_minor
				),
				new_project_usage
			);
		}
		while (version_number_length < 2)
			version_number[version_number_length++] = 0;
		if (version_number[0] == 0)
			version_number[0] = 1;
	}
	extract_version_from_project_name
	(
		&project_name,
		version_number,
		(int)SIZEOF(version_number),
		&version_number_length
	);
	if (!project_name_ok(project_name))
	{
		scp = sub_context_new();
		sub_var_set(scp, "Name", "%S", project_name);
		fatal_intl(scp, i18n("bad project $name"));
		/* NOTREACHED */
		sub_context_delete(scp);
	}
	if (!keep && !version_number_length && !version_string)
	{
		/*
		 * Only do this if there is no version number implicit
		 * in the project name AND no version string was given.
		 *
		 * This test is done BEFORE the version string break up,
		 * because if the version string is the empty string, it
		 * means to use NO version numbers.
		 */
		version_number_length = 2;
		version_number[0] = 1;
		version_number[1] = 0;
	}
	if (version_string && version_string->str_length)
	{
		int	err;

		if (keep)
		{
			mutually_exclusive_options
			(
				arglex_token_keep,
				arglex_token_version,
				new_project_usage
			);
		}
		err =
			break_up_version_string
			(
				version_string->str_text,
				version_number,
				(int)SIZEOF(version_number),
				&version_number_length,
				0
			);
		if (err)
		{
			scp = sub_context_new();
			sub_var_set(scp, "Number", "%S", version_string);
			fatal_intl(scp, i18n("bad version $number"));
			/* NOTREACHED */
			sub_context_delete(scp);
		}
	}
	if (keep && edit == edit_foreground)
	{
		mutually_exclusive_options
		(
			arglex_token_edit,
			arglex_token_keep,
			new_project_usage
		);
	}
	if (keep && edit == edit_background)
	{
		mutually_exclusive_options
		(
			arglex_token_edit_bg,
			arglex_token_keep,
			new_project_usage
		);
	}
	if (keep && pattr_data)
	{
		mutually_exclusive_options
		(
			arglex_token_file,
			arglex_token_keep,
			new_project_usage
		);
	}
	if (!pattr_data)
		pattr_data = pattr_type.alloc(); 
	if (!pattr_data->description)
		pattr_data->description =
			str_format("The \"%S\" program.", project_name);
	if (!(pattr_data->mask & pattr_reuse_change_numbers_mask))
	{
		pattr_data->reuse_change_numbers = 1;
		pattr_data->mask |= pattr_reuse_change_numbers_mask;
	}
	if (edit != edit_not_set)
	{
		scp = sub_context_new();
		sub_var_set(scp, "Name", "%S", project_name);
		io_comment_append(scp, "Project $name");
		sub_context_delete(scp);
		project_pattr_edit(&pattr_data, edit);

		/*
		 * You can't have no description.
		 */
		if (!pattr_data->description)
			pattr_data->description =
				str_format("The \"%S\" program.", project_name);
		if (!(pattr_data->mask & pattr_reuse_change_numbers_mask))
		{
			pattr_data->reuse_change_numbers = 1;
			pattr_data->mask |= pattr_reuse_change_numbers_mask;
		}
	}

	/*
	 * locate user data
	 */
	up = user_executing((project_ty *)0);

	/*
	 * read in the project table
	 */
	gonzo_gstate_lock_prepare_new();
	lock_take();

	/*
	 * make sure not too privileged
	 */
	if (!user_uid_check(up->name))
	{
		scp = sub_context_new();
		sub_var_set(scp, "Name", "%S", user_name(up));
		fatal_intl(scp, i18n("user \"$name\" is too privileged"));
		/* NOTREACHED */
		sub_context_delete(scp);
	}
	if (!user_gid_check(up->group))
	{
		scp = sub_context_new();
		sub_var_set(scp, "Name", "%S", user_group(up));
		fatal_intl(scp, i18n("group \"$name\" is too privileged"));
		/* NOTREACHED */
		sub_context_delete(scp);
	}

	/*
	 * it is an error if the name is already in use
	 */
	if (gonzo_alias_to_actual(project_name))
	{
		scp = sub_context_new();
		sub_var_set(scp, "Name", "%S", project_name);
		fatal_intl(scp, i18n("project alias $name exists"));
		/* NOTREACHED */
		sub_context_delete(scp);
	}
	pp = project_alloc(project_name);
	str_free(project_name);
	if (keep)
	{
		if (gonzo_project_home_path_from_name(project_name))
		{
			scp = sub_context_new();
			sub_var_set(scp, "Name", "%S", pp->name);
			fatal_intl(scp, i18n("project $name exists"));
			/* NOTREACHED */
			sub_context_delete(scp);
		}
		if (!home)
		{
			error_intl(0, i18n("aenpr -keep needs -dir"));
			new_project_usage();
			/* NOTREACHED */
		}
		project_home_path_set(pp, home);
	}
	else
		project_bind_new(pp);

	/*
	 * the user who ran the command is the project administrator.
	 * no other staff are defined at this time.
	 */
	project_administrator_add(pp, user_name(up));

	/*
	 * if no project directory was specified
	 * create the directory in their home directory.
	 */
	if (!home)
	{
		int	max;

		s1 = user_default_project_directory(up);
		assert(s1);
		os_become_orig();
		max = os_pathconf_name_max(s1);
		os_become_undo();
		if (project_name_get(pp)->str_length > max)
		{
			scp = sub_context_new();
			sub_var_set(scp, "Name", "%S", project_name_get(pp));
			sub_var_set(scp, "Number", "%d", (int)(project_name_get(pp)->str_length - max));
			sub_var_optional(scp, "Number");
			fatal_intl(scp, i18n("project \"$name\" too long"));
			/* NOTREACHED */
			sub_context_delete(scp);
		}
		home = str_format("%S/%S", s1, project_name_get(pp));
		str_free(s1);

		scp = sub_context_new();
		sub_var_set(scp, "File_Name", "%S", home);
		project_verbose(pp, scp, i18n("proj dir $filename"));
		sub_context_delete(scp);
	}
	if (!keep)
		project_home_path_set(pp, home);
	str_free(home);

	/*
	 * create the directory and subdirectories.
	 * It is an error if the directories can't be created.
	 */
	home = project_home_path_get(pp);
	bl = project_baseline_path_get(pp, 0);
	hp = project_history_path_get(pp);
	ip = project_info_path_get(pp);
	if (!keep)
	{
		project_become(pp);
		os_mkdir(home, 02755);
		undo_rmdir_errok(home);
		os_mkdir(bl, 02755);
		undo_rmdir_errok(bl);
		os_mkdir(hp, 02755);
		undo_rmdir_errok(hp);
		os_mkdir(ip, 02755);
		undo_rmdir_errok(ip);
		project_become_undo();
	}

	/*
	 * add a row to the table
	 */
	gonzo_project_add(pp);

	/*
	 * copy the attributes into the project
	 */
	if (!keep)
		project_pattr_set(pp, pattr_data);
	pattr_type.free(pattr_data);

	/*
	 * create each of the branches
	 */
	ppp = pp;
	for (j = 0; !keep && j < version_number_length; ++j)
	{
		long		change_number;

		trace(("ppp = %8.8lX\n", (long)ppp));
		change_number = magic_zero_encode(version_number[j]);
		trace(("change_number = %ld;\n", change_number));
		ppp = new_branch_internals(up, ppp, change_number, (string_ty *)0);
		version_pp[j] = ppp;
	}

	/*
	 * write the project state
	 *	(the trunk change state is implicitly written)
	 *
	 * Write each of the branch state.  You must write *after* the
	 * next branch down is created, because creating a branch alters
	 * pstate.
	 */
	project_pstate_write(pp);
	for (j = 0; j < version_number_length; ++j)
		project_pstate_write(version_pp[j]);
	gonzo_gstate_write();

	/*
	 * release locks
	 */
	commit();
	lock_release();

	/*
	 * verbose success message
	 */
	project_verbose(pp, 0, i18n("new project complete"));
	for (j = 0; j < version_number_length; ++j)
		project_verbose(version_pp[j], 0, i18n("new branch complete"));

	project_free(pp);
	user_free(up);
	trace((/*{*/"}\n"));
}


void
new_project()
{
	trace(("new_project()\n{\n"/*}*/));
	switch (arglex())
	{
	default:
		new_project_main();
		break;

	case arglex_token_help:
		new_project_help();
		break;

	case arglex_token_list:
		new_project_list();
		break;
	}
	trace((/*{*/"}\n"));
}
