/*
 *  This file is part of DVS, a simple distributed versioning system
 *  See http://www.cs.colorado.edu/~carzanig/dvs/
 *
 *  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
 *
 *  Copyright (C) 1998-2002 University of Colorado
 *
 *  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-1307,
 *  USA, or send email to carzanig@cs.colorado.edu.
 *
 *
 *  $Id: misclib.c,v 1.13 2002/05/01 17:36:12 carzanig Exp $
 */
#include <dvsconf.h>

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#ifdef STDARG
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#include <dirent.h>
#include <unistd.h>

#include <nc.h>

#include "paths.h"
#include "misclib.h"
#include "dvsmsg.h"
#include "dirstack.h"

t_server nucmserver;

const char *nucm_err[] = {
  "unknown error",
  "not a valid NUCM database",
  "artifact not found",
  "object exists already",
  "artifact is forwarded",
  "artifact is referenced to",
  "no workspace present",
  "no collection present",
  "permission too long",
  "no-op on root of repository",
  "artifact already open",
  "artifacts not the same",
  "artifact not open",
  "artifact or member checked-out for change",
  "can not list an atom",
  "artifact not member of collection",
  "artifact not checked-out for change",
  "server not reachable",
  "Communication failure",
  "NUCM protocol failure",
  "service not available",
  "attribute does not exist",
  "can not read or write file: generic I/O error"
};

const char *Empty = "";
const char *Cwd = ".";
const char *DotDot = "..";
static const char *RCSMode = "RCSMode";

int please_wait_is_quiet = 1;

void please_wait() 
{
    if (please_wait_is_quiet == 0)
	fprintf(stderr,"...please wait...\r");fflush(stderr);
}

void wipe_please_wait()
{
    if (please_wait_is_quiet == 0)
	fprintf(stderr,"                 \r");fflush(stderr);
}

int commit_change(const char *pathname, char *newversion) 
{
  static char filemode[32];
  struct stat pathstat;
  mode_t modemask;
  const char *filename;
  int result=0;

  filename = pathfilename(pathname);

  if (stat(filename, &pathstat)<0) {
    fprintf(stderr, E_Stat, filename, strerror(errno));
    return -1;
  }
  sprintf(filemode, "%lu", (unsigned long)pathstat.st_mode);
  if (nc_commitchange(filename,Empty,newversion)<0) {
    fprintf(stderr, E_Commit, filename, NUCMERROR);
    result = -1;
  }
  if (nc_testandsetattribute(pathname, Empty, RCSMode, filemode)<0) {
    fprintf(stderr, E_SetMode, filename, NUCMERROR);
    result = -1;
  }
  if (S_ISDIR(pathstat.st_mode)) {
    modemask = ~(S_IWOTH | S_IWGRP );
  }
  else {
    modemask = ~(S_IWOTH | S_IWGRP | S_IWUSR);
  }
  pathstat.st_mode &= modemask;
  if (chmod(filename, pathstat.st_mode)<0) {
    fprintf(stderr, E_Chmod, filename, strerror(errno));
    return -1;
  }
  return result;
}

int commit_change_replace(const char *pathname) 
{
  struct stat pathstat;
  mode_t modemask;
  const char *filename;
  int result = 0;

  filename = pathfilename(pathname);

  if (stat(filename, &pathstat)<0) {
    fprintf(stderr, E_Stat, pathname, strerror(errno));
    return -1;
  }
  if (nc_commitchangeandreplace(filename,Empty)<0) {
    fprintf(stderr, E_Commit, filename, NUCMERROR);
    result = -1;
  }
  if (S_ISDIR(pathstat.st_mode)) {
    modemask = ~(S_IWOTH | S_IWGRP );
  }
  else {
    modemask = ~(S_IWOTH | S_IWGRP | S_IWUSR);
  }
  pathstat.st_mode &= modemask;
  if (chmod(filename, pathstat.st_mode)<0) {
    fprintf(stderr, E_Commit, filename, strerror(errno));
    return -1;
  }
  return result;
}

int check_non_dvs(const char *pathname)
{
  char version[MAX_VERSION_LENGTH];
  struct stat pathstat;
  int result;

  if (stat(pathname, &pathstat)<0) return 0;
  if (nc_version(pathname, Empty, version)<0) 
    if (NdbErrno == NdbNotOpen) { return 1; }

  if (S_ISDIR(pathstat.st_mode)) {
    DIR *d;
    struct dirent *de;
    
    if ((d=opendir(pathname))==NULL) {
      fprintf(stderr, E_ReadDir, 
	      pathname, strerror(errno));
      return 1;
    }
    if (pushdir(pathname)<0) {
      closedir(d);
      return 0;
    }
    result = 0;
    while((de = readdir(d)) != NULL) {
      if (not_dot_or_dotdot(de->d_name))
	if (check_non_dvs(de->d_name)) {
	  result = 1; 
	  break;
	}
    }
    closedir(d);
    if (popdir(DotDot)<0) return -1;
    return result;
  }
  else {
    return 0;
  }
}

int abort_change(const char *pathname, int force)
{
  struct stat pathstat;
  mode_t modemask;
  const char *filename;
  int result = 0;

  filename = pathfilename(pathname);

  if (force == 0)
    if (check_non_dvs(filename))
     if (!ask_question(0, M_CheckNonDvs, filename))
       return -1;

  if (stat(filename, &pathstat)<0) {
    if (errno == ENOENT) {
      fprintf(stderr, W_Stat, filename, strerror(errno));
      return 0;
    } else {
      fprintf(stderr, E_Stat, filename, strerror(errno));
      return -1;
    }
  }
  
  if ((result=nc_abortchange(filename,Empty,force))<0) {
    if (NdbErrno==NdbNotInit) { 
      return 0; 
    }
    else {
      fprintf(stderr, E_Abort, filename, NUCMERROR);
      result = -1;
    }
  }
  if (S_ISDIR(pathstat.st_mode)) {
    modemask = ~(S_IWOTH | S_IWGRP );
  }
  else {
    modemask = ~(S_IWOTH | S_IWGRP | S_IWUSR);
  }
  pathstat.st_mode &= modemask;
  if (chmod(filename, pathstat.st_mode)<0) {
    fprintf(stderr, E_Chmod, filename, strerror(errno));
    return -1;
  }
  return result;
}

int add_new_artifact(const char *pathname) 
{
  static char filemode[32];
  struct stat pathstat;
  mode_t modemask;
  const char *filename;
  int result = 0;

  filename = pathfilename(pathname);

  if (stat(filename, &pathstat)<0) {
    fprintf(stderr, E_Stat, pathname, strerror(errno));
    return -1;
  }
  sprintf(filemode, "%lu", (unsigned long)pathstat.st_mode);
  if (nc_add(filename,Empty,Cwd,Empty)<0) {
    fprintf(stderr, E_AddCurrent, filename, NUCMERROR);
    result = -1;
  }
  if (nc_testandsetattribute(filename, Empty, RCSMode, filemode)<0) {
    fprintf(stderr, E_SetMode, filename, NUCMERROR);
    result = -1;
  }
  if (S_ISDIR(pathstat.st_mode)) {
    modemask = ~(S_IWOTH | S_IWGRP );
  }
  else {
    modemask = ~(S_IWOTH | S_IWGRP | S_IWUSR);
  }
  pathstat.st_mode &= modemask;
  if (chmod(filename, pathstat.st_mode)<0) {
    fprintf(stderr, E_Chmod, filename, strerror(errno));
    return -1;
  }
  return result;
}

char *resolve_last(char *pathname)
{
  int ok;
  char lastversion[MAXVERSIONLEN];

  strip_version_x(pathname);
  ok = 0;
  if (nc_lastversion(pathname, Empty, lastversion)<0) {
    if (NdbErrno == NdbUnknown && local_path(pathname)) {
      printf(M_Trying, pathname, nucmserver.host, nucmserver.port, pathname);
      attach_server_x(pathname, &nucmserver);
      if (nc_lastversion(pathname, Empty, lastversion)==0) 
	ok = 1;
    }
  } 
  else 
    ok = 1;

  if (!ok) {
    fprintf(stderr, E_GetLatest, pathname, NUCMERROR);
    return NULL;
  }
  else 
    return set_version_x(pathname, lastversion);
}

int patched_nc_close(const char *filename, const char *pcwd, int forced) 
{
  if (strcmp(strip_version(filename),Cwd)==0) {
    fprintf(stderr, E_NotCurrDir);
    return -1;
  }
  if (nc_close(filename, pcwd, forced)<0) {
    fprintf(stderr, E_Close, filename, NUCMERROR);
    return -1;
  }
  return 0;
};

int open_artifact(char *pathname, int Force)
{
  static char filemode[32];
  int confirm_close = 0;
  const char *filename;

  if (strcmp(strip_version(pathname), Cwd)==0) {
    fprintf(stderr, E_NotCurrDir);
    return -1;
  }
  while(nc_open(pathname, Empty,Cwd,Empty) < 0) {
    if (NdbErrno == NdbOpen) {
      filename = pathfilename(pathname);
      if (Force == 1) { confirm_close = 1; 
      } else {
	int isinit;
	if ((isinit = nc_isinitiated(filename, Empty))) {
	  if (isinit < 0) {
	    fprintf(stderr, "Could not get information on %s.\n(%s)\n",
		    filename, NUCMERROR);
	    return -1;
	  }
	  confirm_close = ask_question(0, M_AlreadyOpen, filename);
	}
	else {
	  char current_version[MAXVERSIONLEN];
	  char pathversion[MAXVERSIONLEN];
	  if (nc_version(pathname, Empty, pathversion) < 0) {
	    fprintf(stderr, E_Version, pathname, NUCMERROR);
	    return -1;
	  }
	  if (nc_version(filename, Empty, current_version)<0) {
	    fprintf(stderr, E_Version, filename, NUCMERROR);
	    return -1;
	  }
	  if (strcmp(current_version, pathversion)==0) {
	    return 0;
	  }
	  confirm_close = -1;
	}
      }
      if (confirm_close != 0) {
	int forced;
	forced = (Force || (confirm_close == 1));
	if (!Force)
	  if (check_non_dvs(filename))
	    if (!ask_question(0, M_CheckNonDvs, filename))
	      return -1;

	if (patched_nc_close(filename, Empty, forced)<0)
	  return -1;
      } else return -1;
    }
    else if (NdbErrno == NdbUnknown && local_path(pathname)) {
      if (*version_position(pathname) == '\0') {
	if (resolve_last(pathname) == NULL) {
	  return -1;
	}
      }
      else { 
	printf(M_Trying, pathname, nucmserver.host, nucmserver.port, pathname);
	attach_server_x(pathname, &nucmserver);
      }
    }
    else {
      fprintf(stderr, E_CheckOut, pathname, NUCMERROR);
      return -1;
    }
  }
  pathfilename_x(pathname);
  if (nc_getattributevalue(pathname,Empty,RCSMode, filemode)<0) {
    if (NdbErrno != NdbNonExist)
      fprintf(stderr, E_Permissions, pathname, NUCMERROR);
  }
  else {
    mode_t fmode;
    struct stat pathstat;
    unsigned long modemask;
    
    fmode = atol(filemode);
    
    if (stat(pathname, &pathstat)<0) {
      fprintf(stderr, E_Stat, pathname, strerror(errno));
      return -1;
    }   
    if (S_ISDIR(pathstat.st_mode)) {
      modemask = ~(S_IWOTH | S_IWGRP );
    }
    else {
      modemask = ~(S_IWOTH | S_IWGRP | S_IWUSR);
    }
    fmode &= modemask;
    if (chmod(pathname, fmode) != 0) {
      fprintf(stderr, E_Chmod, pathname, strerror(errno));
      return -1;
    }
  }
  return 0;
}

int initiate_change(const char *pathname)
{
  struct stat pathstat;
  const char *filename;

  filename = pathfilename(pathname);

  if (stat(filename, &pathstat)<0) {
    if (errno == ENOENT) {
       fprintf(stderr, W_Stat, filename, strerror(errno));
       return 0;
     } else {
       fprintf(stderr, E_Stat, filename, strerror(errno));
       return -1;
     }
  }
  if (nc_initiatechange(filename,Empty)<0) {
    fprintf(stderr, E_Initiate, filename, NUCMERROR);
    return -1;
  }
  pathstat.st_mode |= S_IWUSR;
  if (chmod(filename, pathstat.st_mode)<0) {
    fprintf(stderr, E_Chmod, filename, strerror(errno));
    return -1;
  }
  return 0;
}

int is_in_current_collection(const char *pathname)
{
  t_memberlist *files;
  t_memberlist *aux;
  const char *filename;
  int found;

  if (nc_list(Cwd,Empty,&files)<0)
    return -1;
  filename = pathfilename(pathname);
  
  found = 0;
  while(files != NULL) {
    if (!found)
      if (strcmp(filename, files->name)==0)
	found = 1;
    aux = files->next;
    free(files);
    files = aux;
  }
  return found;
}


/*
 * ask the user a question and returns the answer (y=true, n=false)
 * deflt is the default answer.
 */
int ask_question(int deflt, const char *format,...) 
{
  va_list  args;
  char answer;

#ifdef STDARG
   va_start(args, format);
#else
   va_start(args);
#endif
   vfprintf(stdout, format, args);
   printf(" (%s) ", (deflt ? "Y/n" : "N/y"));
  if (scanf("%c", &answer)<1) {
    fflush(stdin);
    return deflt;
  }
  fflush(stdin);
  return deflt ? 
    (answer != 'N' && answer != 'n') : (answer == 'Y' || answer == 'y');
}

int not_dot_or_dotdot(const char *path) 
{
    if (*path != '.') return 1;
    if (*(++path) == '\0') return 0;
    if (*path == '.' && path[1] == '\0') return 0;
    if (strcmp(path, "collection")==0) return 0;
    if (strcmp(path, "workspace")==0) return 0;
    return 1;
}
