/* $OpenBSD$ */

/*
* Copyright (c) 2007 Nicholas Marriott <[email protected]>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include <sys/types.h>

#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "tmux.h"

/*
* Set of paste buffers. Note that paste buffer data is not necessarily a C
* string!
*/

struct paste_buffer {
       char            *data;
       size_t           size;

       char            *name;
       time_t           created;
       int              automatic;
       u_int            order;

       RB_ENTRY(paste_buffer) name_entry;
       RB_ENTRY(paste_buffer) time_entry;
};

static u_int    paste_next_index;
static u_int    paste_next_order;
static u_int    paste_num_automatic;
static RB_HEAD(paste_name_tree, paste_buffer) paste_by_name;
static RB_HEAD(paste_time_tree, paste_buffer) paste_by_time;

static int      paste_cmp_names(const struct paste_buffer *,
                   const struct paste_buffer *);
RB_GENERATE_STATIC(paste_name_tree, paste_buffer, name_entry, paste_cmp_names);

static int      paste_cmp_times(const struct paste_buffer *,
                   const struct paste_buffer *);
RB_GENERATE_STATIC(paste_time_tree, paste_buffer, time_entry, paste_cmp_times);

static int
paste_cmp_names(const struct paste_buffer *a, const struct paste_buffer *b)
{
       return (strcmp(a->name, b->name));
}

static int
paste_cmp_times(const struct paste_buffer *a, const struct paste_buffer *b)
{
       if (a->order > b->order)
               return (-1);
       if (a->order < b->order)
               return (1);
       return (0);
}

/* Get paste buffer name. */
const char *
paste_buffer_name(struct paste_buffer *pb)
{
       return (pb->name);
}

/* Get paste buffer order. */
u_int
paste_buffer_order(struct paste_buffer *pb)
{
       return (pb->order);
}

/* Get paste buffer created. */
time_t
paste_buffer_created(struct paste_buffer *pb)
{
       return (pb->created);
}

/* Get paste buffer data. */
const char *
paste_buffer_data(struct paste_buffer *pb, size_t *size)
{
       if (size != NULL)
               *size = pb->size;
       return (pb->data);
}

/* Walk paste buffers by time. */
struct paste_buffer *
paste_walk(struct paste_buffer *pb)
{
       if (pb == NULL)
               return (RB_MIN(paste_time_tree, &paste_by_time));
       return (RB_NEXT(paste_time_tree, &paste_by_time, pb));
}

int
paste_is_empty(void)
{
       return RB_ROOT(&paste_by_time) == NULL;
}

/* Get the most recent automatic buffer. */
struct paste_buffer *
paste_get_top(const char **name)
{
       struct paste_buffer     *pb;

       pb = RB_MIN(paste_time_tree, &paste_by_time);
       while (pb != NULL && !pb->automatic)
               pb = RB_NEXT(paste_time_tree, &paste_by_time, pb);
       if (pb == NULL)
               return (NULL);
       if (name != NULL)
               *name = pb->name;
       return (pb);
}

/* Get a paste buffer by name. */
struct paste_buffer *
paste_get_name(const char *name)
{
       struct paste_buffer     pbfind;

       if (name == NULL || *name == '\0')
               return (NULL);

       pbfind.name = __UNCONST(name);
       return (RB_FIND(paste_name_tree, &paste_by_name, &pbfind));
}

/* Free a paste buffer. */
void
paste_free(struct paste_buffer *pb)
{
       notify_paste_buffer(pb->name, 1);

       RB_REMOVE(paste_name_tree, &paste_by_name, pb);
       RB_REMOVE(paste_time_tree, &paste_by_time, pb);
       if (pb->automatic)
               paste_num_automatic--;

       free(pb->data);
       free(pb->name);
       free(pb);
}

/*
* Add an automatic buffer, freeing the oldest automatic item if at limit. Note
* that the caller is responsible for allocating data.
*/
void
paste_add(const char *prefix, char *data, size_t size)
{
       struct paste_buffer     *pb, *pb1;
       u_int                    limit;

       if (prefix == NULL)
               prefix = "buffer";

       if (size == 0) {
               free(data);
               return;
       }

       limit = options_get_number(global_options, "buffer-limit");
       RB_FOREACH_REVERSE_SAFE(pb, paste_time_tree, &paste_by_time, pb1) {
               if (paste_num_automatic < limit)
                       break;
               if (pb->automatic)
                       paste_free(pb);
       }

       pb = xmalloc(sizeof *pb);

       pb->name = NULL;
       do {
               free(pb->name);
               xasprintf(&pb->name, "%s%u", prefix, paste_next_index);
               paste_next_index++;
       } while (paste_get_name(pb->name) != NULL);

       pb->data = data;
       pb->size = size;

       pb->automatic = 1;
       paste_num_automatic++;

       pb->created = time(NULL);

       pb->order = paste_next_order++;
       RB_INSERT(paste_name_tree, &paste_by_name, pb);
       RB_INSERT(paste_time_tree, &paste_by_time, pb);

       notify_paste_buffer(pb->name, 0);
}

/* Rename a paste buffer. */
int
paste_rename(const char *oldname, const char *newname, char **cause)
{
       struct paste_buffer     *pb, *pb_new;

       if (cause != NULL)
               *cause = NULL;

       if (oldname == NULL || *oldname == '\0') {
               if (cause != NULL)
                       *cause = xstrdup("no buffer");
               return (-1);
       }
       if (newname == NULL || *newname == '\0') {
               if (cause != NULL)
                       *cause = xstrdup("new name is empty");
               return (-1);
       }

       pb = paste_get_name(oldname);
       if (pb == NULL) {
               if (cause != NULL)
                       xasprintf(cause, "no buffer %s", oldname);
               return (-1);
       }

       pb_new = paste_get_name(newname);
       if (pb_new != NULL)
               paste_free(pb_new);

       RB_REMOVE(paste_name_tree, &paste_by_name, pb);

       free(pb->name);
       pb->name = xstrdup(newname);

       if (pb->automatic)
               paste_num_automatic--;
       pb->automatic = 0;

       RB_INSERT(paste_name_tree, &paste_by_name, pb);

       notify_paste_buffer(oldname, 1);
       notify_paste_buffer(newname, 0);

       return (0);
}

/*
* Add or replace an item in the store. Note that the caller is responsible for
* allocating data.
*/
int
paste_set(char *data, size_t size, const char *name, char **cause)
{
       struct paste_buffer     *pb, *old;

       if (cause != NULL)
               *cause = NULL;

       if (size == 0) {
               free(data);
               return (0);
       }
       if (name == NULL) {
               paste_add(NULL, data, size);
               return (0);
       }

       if (*name == '\0') {
               if (cause != NULL)
                       *cause = xstrdup("empty buffer name");
               return (-1);
       }

       pb = xmalloc(sizeof *pb);

       pb->name = xstrdup(name);

       pb->data = data;
       pb->size = size;

       pb->automatic = 0;
       pb->order = paste_next_order++;

       pb->created = time(NULL);

       if ((old = paste_get_name(name)) != NULL)
               paste_free(old);

       RB_INSERT(paste_name_tree, &paste_by_name, pb);
       RB_INSERT(paste_time_tree, &paste_by_time, pb);

       notify_paste_buffer(name, 0);

       return (0);
}

/* Set paste data without otherwise changing it. */
void
paste_replace(struct paste_buffer *pb, char *data, size_t size)
{
       free(pb->data);
       pb->data = data;
       pb->size = size;

       notify_paste_buffer(pb->name, 0);
}

/* Convert start of buffer into a nice string. */
char *
paste_make_sample(struct paste_buffer *pb)
{
       char            *buf;
       size_t           len, used;
       const int        flags = VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL;
       const size_t     width = 200;

       len = pb->size;
       if (len > width)
               len = width;
       buf = xreallocarray(NULL, len, 4 + 4);

       used = utf8_strvis(buf, pb->data, len, flags);
       if (pb->size > width || used > width)
               strlcpy(buf + width, "...", 4);
       return (buf);
}