#include "lesstest.h"

extern TermInfo terminfo;

// EnvBuf has a char buffer (env_buf) which holds both the env var
// string table and also the array of pointers to individual strings (env_list).
// env_estr points to the end of the string table.
// The env_list array grows backwards from the end of env_buf.

void env_init(EnvBuf* env) {
       env->env_estr = (char*) env->env_buf;
       env->env_list = env->env_buf + sizeof(env->env_buf)/sizeof(char*);
       *--(env->env_list) = NULL;
}

static void env_check(EnvBuf* env) {
       if (env->env_estr >= (const char*) env->env_list) {
               fprintf(stderr, "ENVBUF_SIZE too small!\n");
               abort();
       }
}

// Add a char to the string table.
static void env_addchar(EnvBuf* env, char ch) {
       *(env->env_estr)++ = ch;
       env_check(env);
}

// Add a delimited string to the string table.
static void env_addlstr(EnvBuf* env, const char* str, int strlen) {
       while (strlen-- > 0)
               env_addchar(env, *str++);
}

// Add a null-terminated string to the string table.
static void env_addstr(EnvBuf* env, const char* str) {
       env_addlstr(env, str, strlen(str));
}

// Add an env variable name/value pair to an EnvBuf.
// The name is delimited and the value is null-terminated.
static void env_addlpair(EnvBuf* env, const char* name, int namelen, const char* value) {
       *--(env->env_list) = env->env_estr;
       env_check(env);
       env_addlstr(env, name, namelen);
       env_addstr(env, "=");
       env_addstr(env, value);
       env_addchar(env, '\0');
}

// Add an env variable name/value pair to an EnvBuf.
void env_addpair(EnvBuf* env, const char* name, const char* value) {
       env_addlpair(env, name, strlen(name), value);
}

// Add an env variable name/value pair to an EnvBuf where the value is an integer.
void env_addintpair(EnvBuf* env, const char* name, int value) {
       char buf[64];
       snprintf(buf, sizeof(buf), "%d", value);
       env_addpair(env, name, buf);
}

// Is a given env var name one which should be passed to less?
static int is_less_env(const char* name, int name_len) {
       static char* const less_names[] = {
               "LESS*", "COLUMNS", "LINES", "LANG", "LC_CTYPE", "MORE", NULL
       };
       char* const* n;
       for (n = less_names; *n != NULL; ++n) {
               int ln = strlen(*n);
               if (ln == name_len && strncmp(*n, name, ln) == 0)
                       return 1;
               if ((*n)[ln-1] == '*' && strncmp(*n, name, ln-1) == 0)
                       return 1;
       }
       return 0;
}

// Create a list of env vars to be given to an instance of less,
// as an EnvBuf.
static void env_setup(EnvBuf* env, char* const* prog_env, int interactive) {
       char* const* envp;
       struct tcvar { char const* name; char const* value; } tcvars[] = {
               { "LESS_TERMCAP_am", "1" },
               { "LESS_TERMCAP_cd", "\33S" },
               { "LESS_TERMCAP_ce", "\33L" },
               { "LESS_TERMCAP_cl", "\33A" },
               { "LESS_TERMCAP_cr", "\33<" },
               { "LESS_TERMCAP_cm", "\33%p2%d;%p1%dj" },
               { "LESS_TERMCAP_ho", "\33h" },
               { "LESS_TERMCAP_ll", "\33l" },
               { "LESS_TERMCAP_mb", "\33b" },
               { "LESS_TERMCAP_md", "\33[1m" },
               { "LESS_TERMCAP_me", "\33[m" },
               { "LESS_TERMCAP_se", "\33[m" },
               { "LESS_TERMCAP_so", "\33[7m" },
               { "LESS_TERMCAP_sr", "\33r" },
               { "LESS_TERMCAP_ue", "\33[24m" },
               { "LESS_TERMCAP_us", "\33[4m" },
               { "LESS_TERMCAP_vb", "\33g" },
               { "LESS_TERMCAP_kr", terminfo.key_right },
               { "LESS_TERMCAP_kl", terminfo.key_left },
               { "LESS_TERMCAP_ku", terminfo.key_up },
               { "LESS_TERMCAP_kd", terminfo.key_down },
               { "LESS_TERMCAP_kh", terminfo.key_home },
               { "LESS_TERMCAP_@7", terminfo.key_end },
       };
       if (interactive) {
               int i;
               for (i = 0; i < countof(tcvars); ++i) {
                       struct tcvar* tc = &tcvars[i];
                       env_addpair(env, tc->name, tc->value);
                       log_env(tc->name, strlen(tc->name), tc->value);
               }
       }
       for (envp = prog_env; *envp != NULL; ++envp) {
               const char* ename = *envp;
               const char* eq = strchr(ename, '=');
               if (eq == NULL) continue;
               if (!interactive || is_less_env(ename, eq-ename)) {
                       env_addlpair(env, ename, eq-ename, eq+1);
                       log_env(ename, eq-ename, eq+1);
               }
       }
}

// Return the value of a named env var.
const char* get_envp(char* const* envp, const char* name) {
       for (; *envp != NULL; ++envp) {
               const char* ename = *envp;
               const char* eq = strchr(ename, '=');
               if (eq != NULL && strlen(name) == eq-ename && strncmp(name, ename, eq-ename) == 0)
                       return eq+1;
       }
       return NULL;
}

// Return a list of env vars to be given to an instance of less,
// as an array of strings.
char* const* less_envp(char* const* envp, int interactive) {
       static EnvBuf less_env;
       static int init = 0;
       if (!init) {
               env_init(&less_env);
               env_setup(&less_env, envp, interactive);
               init = 1;
       }
       return less_env.env_list;
}