/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <opencm.h>
#include <ctype.h>
#include <strings.h>
#include "opencmclient.h"

#ifdef HAVE_READLINE_H
#include <readline.h>
#endif
#ifdef HAVE_READLINE_READLINE_H
#include <readline/readline.h>
#endif
#ifdef HAVE_READLINE_HISTORY_H
#include <readline/history.h>
#endif

int
opencm_confirm(const char *prompt, int dflt_yes)
{
  char *check = opencm_readline(prompt, dflt_yes ? "yes" : "no");
  if (strcasecmp(check, "y") == 0 || strcasecmp(check, "yes") == 0)
    return 1;

  return 0;
}

char *
opencm_readline(const char *prompt, const char *dflt)
{
  char *result;

#ifdef HAVE_LIBREADLINE
  if (dflt) {
    prompt = xstrcat(prompt, " [");
    prompt = xstrcat(prompt, dflt);
    prompt = xstrcat(prompt, "]: ");
  }
  else
    prompt = xstrcat(prompt, ": ");

  result = readline((char *) prompt);
  
  if (result && strlen(result) == 0)
    result = dflt ? xstrdup(dflt) : xstrdup("");

  return result;
#else
  char *message;
  char *argMsg = opt_Message;
  opt_Message = 0;
  
  message = xstrcat("Please enter ", prompt);
  message = xstrcat(message, "\n");
  result = get_message(message, "OPENCM", dflt);
  opt_Message = argMsg;
  
  return result;
#endif
}

/* Strip all pure-comment lines from the buffer. */
void
strip_comments(char *buf, const char *leader)
{
  char *ptr = buf;		/* in output */
  char *pos = buf;		/* in input */
  char *eol;
  
  while ((eol = strchr(pos, '\n'))) {
    eol ++;

    /* If we are looking at a line that begins with the leader string,
     * skip it. Otherwise, copy the line we are currently looking at
     * and examine the next one. */
    if (leader && strncmp(pos, leader, strlen(leader)) == 0) {
      pos = eol;
      continue;
    }
    else {
      while (pos != eol)
	*ptr++ = *pos++;
    }
  }  
  /* The last bit might not have a newline... */
  while (*pos)
    *ptr++ = *pos++;
  *ptr = 0;

  {
    /* Also strip off initial blank lines: */
    char *nonblank = buf;
    ptr = buf;
    
    while (isspace(*ptr)) {
      if (*ptr == '\n')
	nonblank = ptr + 1;
      ptr++;
    }
    if (nonblank != buf) {
      ptr = buf;
      while (*nonblank)
	*ptr++ = *nonblank++;
      *ptr = 0;
    }
  }

  {
    /* Also strip off trailing blanks, and then append newline. Caller
     * must ensure space for the newline (which is a bug, but avoids a
     * reallocation): */
    char *last = buf;
    while (*last)
      last++;
    last--;
    
    while (isspace(*last))
      *last-- = 0;

    *++last = '\n';
  }
}

static void
write_tmpmsg(FILE *f, const char *msg, const char *leader, const char *dflt)
{
  const char *newline;
  
  while (*msg) {
    newline = strchr(msg, '\n');
    if (!newline)		/* not newline terminated... */
      THROW(ExMalformed, "Internal error -- message not newline terminated");
    newline++;

    if (fwrite(leader, 1, strlen(leader), f) < strlen(leader))
      THROW(ExTruncated, "Could not write message to temp file");

    if (fwrite("  ", 1, 2, f) < 2)
      THROW(ExTruncated, "Could not write message to temp file");

    if (fwrite(msg, 1, newline - msg, f) < (newline - msg))
      THROW(ExTruncated, "Could not write message to temp file");

    msg = newline;
  }

  if (fwrite("\n", 1, 1, f) < 1)
    THROW(ExTruncated, "Could not write message to temp file");

  if (dflt) {
    if (fwrite(dflt, 1, strlen(dflt), f) < strlen(dflt))
      THROW(ExTruncated, "Could not write message to temp file");
  }
}

static int
tmpmsg_nlines(const char *msg)
{
  int n = 0;
  char *eol;
  
  while ((eol = strchr(msg, '\n'))) {
    n++;
    msg = eol+1;
  }

  return n;    
}

/* Ensure that message ends in at least one newline and at most one
 * newline. */
static void
message_fixup(char *msgbuf)
{
  unsigned len = strlen(msgbuf);

  while (len > 1 && msgbuf[len-1] == '\n' && msgbuf[len-2] == '\n')
    msgbuf[--len] = 0;
}

char *
read_message_file(const char *fname, const char *leader)
{
  FILE *f = 0;

  /* We now need to re-extract the file content, stripping it of the
     comment lines. Note extra character for trailing newline! */
  int len = path_file_length(fname); /* upper bound if text */
  char *msgbuf = (char *) GC_MALLOC(sizeof(char) * (len + 2));
    
  f = xfopen(fname, 'r', 't');

  /* Length may get truncated if this is a WIN32 text file */
  len = fread(msgbuf, 1, len, f);

  msgbuf[len] = 0;
  msgbuf[len+1] = 0;
  fclose(f);

  strip_comments(msgbuf, leader);

  return msgbuf;
}

char *
get_message(const char *msg, const char *leader, const char *dflt)
{
  FILE *f = 0;
  int len;
  const char *tmpname;
  char *msgbuf = 0;
  char arg_nlines[20];
  int msg_lines = tmpmsg_nlines(msg);
  const char *contentHash;
  SubProcess *proc;
  
  if (opt_Message) {
    len = strlen(opt_Message);
    msgbuf = (char *) GC_MALLOC(sizeof(char) * (len + 2));
    strcpy(msgbuf, opt_Message);
    msgbuf[len] = '\n';
    msgbuf[len+1] = '\0';
    message_fixup(msgbuf);

    return msgbuf;
  }
  
  sprintf(arg_nlines, "+%u", msg_lines + 1);
  
  if (opt_Editor == 0)
    THROW(ExEnviron, "Do not know what editor to use");

  TRY {
    tmpname = path_mktmpnm(path_scratchdir(), "msg-");
      
    f = xfopen(tmpname, 'w', 't');
    TRY {
      write_tmpmsg(f, msg, leader, dflt);
      xfclose(f);
    }
    DEFAULT(ex) {
      xfclose(f);
      RETHROW(ex);
    }
    END_CATCH;

    {
      Buffer *b = buffer_FromFile(tmpname, 'T');
      contentHash = ser_getTrueName(b);
    }

    proc  = subprocess_create();
    subprocess_AddArg(proc, opt_Editor);
    subprocess_AddArg(proc, arg_nlines);
    subprocess_AddArg(proc, tmpname);
  
    if (subprocess_Run(proc, 0, 0, 0, SPF_NORMAL) < 0)
      THROW(ExSubprocess, "Subcommand execution failed");

    {
      Buffer *b = buffer_FromFile(tmpname, 'T');
      const char *newHash = ser_getTrueName(b);

      if (nmequal(newHash, contentHash) &&
          !opencm_confirm("Commit message is unchanged. Proceed?", 1))
        THROW(ExNoConnect, "Commit aborted at user request");
    }

    msgbuf = read_message_file(tmpname, leader);

    path_remove_editor_file(tmpname);
  } END_CATCH;

  return msgbuf;
}
