/* Copyright (c) 2021, la-ninpre
 * 
 * Permission to use, copy, modify, and/or 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 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.
 *
 * *************************************************************************
 * 
 * this file contains main code for nimisewi.
 * 
 * nimisewi is a small program that generates random toki pona noun phrase.
 * toki pona is a small constructed language created by sonja lang 
 * (see https://tokipona.org).
 *
 * functions and variables here a primarily named in tokipona, just because
 * i can. but just in case there are annotations in english.
 *
 * sona nanpa li ike. taso ona li pali e ilo pona.
 */

#include <config.h>
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#ifdef HAVE_BSD_STRING_H
#include <bsd/string.h>
#else
#include <string.h>
#endif

#include <sys/types.h>
#include <sys/time.h>

#include "nimisewi.h"
#include "nimitoki.h" /* nimi_toki, suli_pi_nimi_toki */

/* struct to hold stuff useful for random index generation */
struct nanpa_nimi {
    int nanpa_nimi; /* number of words - 1 */
    int pi_li_lon;  /* whether to insert 'pi' or not */
    int nanpa_pi;   /* index to insert 'pi', if pi_li_lon is 1 */
    int *nanpa_sewi_nimi; /* random indices of words */
    size_t suli_pi_nimi_sewi; /* length of generated phrase */
};

static void
open_e_nanpa_sewi(void)
{
    /* initialize pseudorandom generator using pid, microseconds and seconds
     *
     * for such silly program there's no need to implement cryptographically
     * secure random number generator.
     */

    pid_t pid;
    struct timeval time;

    gettimeofday(&time, NULL);
    pid = getpid();
    srand(pid * time.tv_usec * time.tv_sec);
}

static const char
*pana_e_nimi(const int nanpa_nimi)
{
    /* wrapper function to get word from array
     *
     * noun phrases in toki pona could have arbitrary amount of words and they
     * are left grouped (the leftmost word is main, and words to the right
     * are modifying it:
     *
     * ((nimi) sewi)
     *
     * special word 'pi' could be used to alter this grouping order to achieve
     * something like english preposition 'of'
     *
     * ((suli) pi ((nimi) mute))
     *
     * this functions inserts 'pi' in the middle to avoid generating
     * very heavy phrases.
     */

    if (nanpa_nimi == -1) {
        return "pi";
    } else if ((unsigned long) nanpa_nimi > suli_pi_nimi_toki) {
        err(EXIT_FAILURE, "index out of bounds");
    } else {
        return nimi_toki[nanpa_nimi];
    }
}

static void
nanpa_nimi_pana_e_pi(struct nanpa_nimi *nn)
{
    /* handle a 'pi' instertion */
    if (nn->nanpa_nimi == 2) {
        /* this is made to allow for phrases with following structures:
         *
         * - word1 word2 word3
         * - word1 pi word2 word3
         */
        nn->pi_li_lon = rand() % 2;
    } else if (nn->nanpa_nimi > 2) {
        nn->pi_li_lon = 1;
    }
    if (nn->pi_li_lon) {
        /* since we insert whole word, number of words must be increased too */
        nn->nanpa_nimi++;
        nn->nanpa_pi = (nn->nanpa_nimi / 2);
    }
}

static struct nanpa_nimi
*pana_e_nanpa_nimi(void)
{
    /* generate nanpa_nimi with all the values useful for phrase generation
     *
     * when used, one should call weka_e_nanpa_nimi();
     */

    int i;
    struct nanpa_nimi *nn;

    nn = malloc(sizeof(struct nanpa_nimi));
    if (nn == NULL) {
        return NULL;
    }

    /* initialize nanpa_nimi with default values */
    nn->pi_li_lon = 0;
    nn->nanpa_pi = -1;
    nn->nanpa_sewi_nimi = NULL;
    nn->suli_pi_nimi_sewi = 0;

    nn->nanpa_nimi = (rand() % 6);

    /* to use with arbitrary wordlist, remove following function call */
    nanpa_nimi_pana_e_pi(nn);

    nn->nanpa_sewi_nimi = calloc(nn->nanpa_nimi + 1, sizeof(int));
    if (nn->nanpa_sewi_nimi == NULL) {
        free(nn);
        return NULL;
    }
    for (i = 0; i <= nn->nanpa_nimi; i++) {
        nn->nanpa_sewi_nimi[i] = rand() % suli_pi_nimi_toki;
    }
    if (nn->pi_li_lon) {
        nn->nanpa_sewi_nimi[nn->nanpa_pi] = -1;
    }

    for (i = 0; i <= nn->nanpa_nimi; i++) {
        nn->suli_pi_nimi_sewi += sizeof(char);
        nn->suli_pi_nimi_sewi += strlen(pana_e_nimi(nn->nanpa_sewi_nimi[i]));
    }

    return nn;
}

static void
weka_e_nanpa_nimi(struct nanpa_nimi *nn)
{
    if (nn != NULL) {
        free(nn->nanpa_sewi_nimi);
        free(nn);
    }
}

char
*nimi_sewi()
{
    int i;
    char *nimi_pana;
    struct nanpa_nimi *nn;

    open_e_nanpa_sewi();

    nn = pana_e_nanpa_nimi();

    nimi_pana = calloc(nn->suli_pi_nimi_sewi + 1, sizeof(char));
    if (nimi_pana == NULL) {
        weka_e_nanpa_nimi(nn);
        return NULL;
    }

    for (i = 0; i < nn->nanpa_nimi; i++) {
#ifdef HAVE_STRLCAT
        strlcat(nimi_pana,
                pana_e_nimi(nn->nanpa_sewi_nimi[i]),
                nn->suli_pi_nimi_sewi);
        strlcat(nimi_pana, " ", nn->suli_pi_nimi_sewi);
#else
        strcat(nimi_pana,
               pana_e_nimi(nn->nanpa_sewi_nimi[i]));
        strcat(nimi_pana, " ");
#endif
    }
#ifdef HAVE_STRLCAT
    strlcat(nimi_pana,
            pana_e_nimi(nn->nanpa_sewi_nimi[i]),
            nn->suli_pi_nimi_sewi);
#else
    strcat(nimi_pana,
           pana_e_nimi(nn->nanpa_sewi_nimi[i]));
#endif

    weka_e_nanpa_nimi(nn);

    return nimi_pana;
}

void
weka_e_nimi_sewi(char *nimi_sewi)
{
    if (nimi_sewi != NULL) {
        free(nimi_sewi);
    }
}