/*******************************************************************************/
/* Permission is hereby granted, free of charge, to any person or organization */
/* obtaining a copy of the software and accompanying documentation covered by  */
/* this license (the "Software") to use, reproduce, display, distribute,       */
/* execute, and transmit the Software, and to prepare derivative works of the  */
/* Software, and to permit third-parties to whom the Software is furnished to  */
/* do so, all subject to the following:                                        */
/*                                                                             */
/* The copyright notices in the Software and this entire statement, including  */
/* the above license grant, this restriction and the following disclaimer,     */
/* must be included in all copies of the Software, in whole or in part, and    */
/* all derivative works of the Software, unless such copies or derivative      */
/* works are solely in the form of machine-executable object code generated by */
/* a source language processor.                                                */
/*                                                                             */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR  */
/* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,    */
/* FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT   */
/* SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE   */
/* FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, */
/* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER */
/* DEALINGS IN THE SOFTWARE.                                                   */
/*******************************************************************************/

#include <lfp/stdlib.h>
#include <lfp/string.h>
#include <lfp/unistd.h>
#include <lfp/stat.h>
#include <lfp/fcntl.h>
#include <lfp/errno.h>
#include <lfp/time.h>
#include "aux/inlines.h"

#include <limits.h>
#include <stdbool.h>
#include <pthread.h>

#if !defined(HAVE_MKOSTEMP)

static char*
_valid_template_p(char *s, size_t len)
{
    size_t start_offset = len - 6;
    for (size_t off = start_offset; off < len; off++) {
        if (s[off] != 'X') return NULL;
    }
    return s + start_offset;
}

static int
_randomize_template(uint32_t *seed, char *s)
{
    uint32_t internalSeed = *seed;
    int retval = 0;

    char random_value[6];
    memcpy(random_value, &internalSeed, 4);
    internalSeed = _xorshift(internalSeed);
    memcpy(random_value + 4, &internalSeed, 2);

    char bag[] = "0123456789QWERTYUIOPASDFGHJKLZXCVBNM";
    for (unsigned i = 0; i < 6; i++) {
        s[i] = bag[random_value[i]%(sizeof(bag)-1)];
        //random_value /= (sizeof(bag)-1);
    }

    *seed = internalSeed;
    return retval;
}

static inline uint32_t
_pid_seed(void)
{ 
        /* Try to pick a seed different from everyone else */
        struct timespec ts;
        SYSGUARD(lfp_clock_gettime(CLOCK_REALTIME, &ts));
        return (ts.tv_nsec << 2) + getpid() + ts.tv_sec;
}

#endif // HAVE_MKOSTEMP

DSO_PUBLIC int
lfp_mkostemp(char *template, uint64_t flags)
{
    SYSCHECK(EFAULT, template == NULL);
#if defined(HAVE_MKOSTEMP)
    return mkostemp(template, flags);
#else
    static uint32_t seed = 0;

    size_t len = strlen(template);
    SYSCHECK(EINVAL, len < 6 || template[0] != '/');

    char *x_start = _valid_template_p(template, len);
    SYSCHECK(EINVAL, x_start == NULL);

    for (int i = 0; i < 1024; i++) {
        SYSGUARD(_randomize_template(&seed, x_start));
        int fd = lfp_open(template, O_EXCL | O_CREAT | O_RDWR | flags, S_IRUSR | S_IWUSR);
        if (fd >= 0) {
            return fd;
        } else {
            switch (lfp_errno()) {
            case EEXIST:
                /* If we hit here, we may be colliding with
                 * a forked process, so increment the seed */
                if(i == 0) {
                    seed = _pid_seed();
                }
                //seed+=1;
                continue;
            default:
                return -1;
            }
        }
    }
    SYSERR(EEXIST);
#endif
}

DSO_PUBLIC int
lfp_mkstemp(char *template)
{
    return lfp_mkostemp(template, O_CLOEXEC);
}

static bool
valid_path_p(const char *path, size_t len)
{
    return path[len] == '/' && path[len+1] != '\0' ? true : false;
}

static char*
_lfp_getenv(const char *name, size_t len, char *const envp[])
{
    if (envp == NULL || *envp == NULL)
        return NULL;
    do {
        if (strncmp(name, *envp, len) == 0 && valid_path_p(*envp, len))
            return *envp+len;
    } while(*(++envp) != NULL);
    return NULL;
}

// FIXME: add autoconf check that confstr(_CS_PATH) returns sane values
static char*
_lfp_default_path(void)
{
    size_t default_path_size = confstr(_CS_PATH, NULL, 0);
    char *default_path = malloc(default_path_size);
    confstr(_CS_PATH, default_path, default_path_size);
    return default_path;
}

DSO_PUBLIC char*
lfp_getpath(char *const envp[])
{
    char *envpath = _lfp_getenv("PATH=", sizeof("PATH=") - 1,
                                envp ? envp : lfp_get_environ());
    if (envpath != NULL) {
        return strdup(envpath);
    } else {
        return _lfp_default_path();
    }
}

DSO_PUBLIC int
lfp_ptsname(int masterfd, char *buf, size_t buflen)
{
    SYSCHECK(EFAULT, buf == NULL);
#if defined(HAVE_PTSNAME_R)
    return ptsname_r(masterfd, buf, buflen);
#elif defined(HAVE_PTSNAME)
    char *pts = ptsname(masterfd);
    size_t ptslen = lfp_strnlen(pts, buflen);
    if (ptslen >= buflen) {
        SYSERR(EINVAL);
    } else {
        memcpy(buf, pts, ptslen);
        return 0;
    }
#endif
}
