/*
 * parse_module.c
 *
 * Copyright (c) 2018-2022 Eric Vidal <eric@obarun.org>
 *
 * All rights reserved.
 *
 * This file is part of Obarun. It is subject to the license terms in
 * the LICENSE file found in the top-level directory of this
 * distribution.
 * This file may not be copied, modified, propagated, or distributed
 * except according to the terms contained in the LICENSE file./
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h> //chdir, unlink

#include <oblibs/log.h>
#include <oblibs/string.h>
#include <oblibs/types.h>
#include <oblibs/sastr.h>
#include <oblibs/directory.h>

#include <skalibs/stralloc.h>
#include <skalibs/djbunix.h> //hiercopy

#include <66/module.h>
#include <66/resolve.h>
#include <66/info.h>
#include <66/constants.h>
#include <66/instance.h>
#include <66/utils.h>
#include <66/parse.h>
#include <66/sanitize.h>

static void convert_tomodule(resolve_service_t *res, char const *module)
{
    resolve_wrapper_t_ref wres = resolve_set_struct(DATA_SERVICE, res) ;
    stralloc sa = STRALLOC_ZERO ;
    size_t pos = 0, mlen = strlen(module) ;

    if (res->dependencies.ndepends) {

        if (!sastr_clean_string(&sa, res->sa.s + res->dependencies.depends))
            log_die_nomem("stralloc") ;

        size_t len = (mlen + 1 + SS_MAX_TREENAME + 2) * res->dependencies.ndepends ;
        char n[len] ;

        memset(n, 0, len) ;

        FOREACH_SASTR(&sa, pos) {

            auto_strings(n + strlen(n), module, ":", sa.s + pos, " ") ;
        }

        n[strlen(n) - 1] = 0 ;

        res->dependencies.depends = resolve_add_string(wres, n) ;

    }

    if (res->dependencies.nrequiredby) {

        sa.len = 0 ;

        if (!sastr_clean_string(&sa, res->sa.s + res->dependencies.requiredby))
            log_die_nomem("stralloc") ;

        size_t len = (mlen + 1 + SS_MAX_TREENAME + 2) * res->dependencies.nrequiredby ;

        char n[len] ;

        memset(n, 0, len) ;

        pos = 0 ;

        FOREACH_SASTR(&sa, pos) {

            char n[mlen + 1 + strlen(sa.s + pos) + 1] ;

            auto_strings(n + strlen(n), module, ":", sa.s + pos, " ") ;
        }

        n[strlen(n) - 1] = 0 ;

        res->dependencies.requiredby = resolve_add_string(wres, n) ;

    }

    stralloc_free(&sa) ;
    free(wres) ;
}

void parse_module(resolve_service_t *res, resolve_service_t *ares, unsigned int *areslen, ssexec_t *info, uint8_t force)
{
    log_flow() ;

    int r, insta = -1 ;
    size_t pos = 0, copylen = 0, len = 0 ;
    char name[strlen(res->sa.s + res->name) + 1] ;
    auto_strings(name,res->sa.s + res->name) ;
    char *src = res->sa.s + res->path.frontend ;
    char dirname[strlen(src)] ;
    char copy[SS_MAX_PATH_LEN] ;
    char ainsta[strlen(name)] ;
    stralloc list = STRALLOC_ZERO ;
    resolve_wrapper_t_ref wres = 0 ;

    log_trace("parse module: ", name) ;

    if (!ob_dirname(dirname, src))
        log_dieu(LOG_EXIT_SYS, "get directory name of: ", src) ;

    insta = instance_check(name) ;
    instance_splitname_to_char(ainsta, name, insta, 0) ;

    if (!getuid()) {

        auto_strings(copy, SS_SERVICE_ADMDIR, name) ;

        copylen = strlen(copy) ;

    } else {

        if (!set_ownerhome_stack(copy))
            log_dieusys(LOG_EXIT_SYS, "unable to find the home directory of the user") ;

        copylen = strlen(copy) ;

        auto_strings(copy + copylen, SS_SERVICE_USERDIR, name) ;
    }

    uint8_t conf = res->environ.env_overwrite ;

    /** check mandatory directories */
    parse_module_check_dir(dirname, SS_MODULE_CONFIG_DIR) ;
    parse_module_check_dir(dirname, SS_MODULE_ACTIVATED) ;
    parse_module_check_dir(dirname, SS_MODULE_FRONTEND) ;

    r = scan_mode(copy, S_IFDIR) ;
    if (r == -1) {
        errno = EEXIST ;
        log_dieusys(LOG_EXIT_SYS, "conflicting format of: ", copy) ;

    } else if (!r) {

        if (!hiercopy(dirname, copy))
            log_dieusys(LOG_EXIT_SYS, "copy: ", dirname, " to: ", copy) ;

    } else {

        /** Must reconfigure all services of the module */
        if (!force) {

            log_warn("skip configuration of the module: ", name, " -- already configured") ;
            goto deps ;
        }

        log_trace("remove directory: ", copy) ;

        if (!dir_rm_rf(copy))
            log_dieusys (LOG_EXIT_SYS, "remove: ", copy) ;

        log_trace("copy: ", dirname, " to: ", copy) ;
        if (!hiercopy(dirname, copy))
            log_dieusys(LOG_EXIT_SYS,"copy: ", dirname, " to: ", copy) ;
    }

    auto_strings(copy + copylen, "/", ainsta) ;

    /** remove the original service frontend file inside the copied directory
     * to avoid double frontend service file for a same service.*/
    errno = 0 ;
    if (unlink(copy) < 0 && errno != ENOENT)
        log_dieusys(LOG_EXIT_ZERO, "unlink: ", copy) ;

    copy[copylen] = 0 ;

    auto_strings(copy + copylen, SS_MODULE_FRONTEND) ;

    /** contents */
    get_list(&list, copy, name, S_IFREG) ;
    regex_replace(&list, res) ;

    copy[copylen] = 0 ;

    /** directories */
    get_list(&list, copy, name, S_IFDIR) ;
    regex_rename(&list, res, res->regex.directories) ;

    /** filename */
    get_list(&list, copy, name, S_IFREG) ;
    regex_rename(&list, res, res->regex.files) ;

    /** configure script */
    regex_configure(res, info, copy, name) ;

    deps:

    auto_strings(copy + copylen, SS_MODULE_ACTIVATED) ;

    get_list(&list, copy, name, S_IFREG) ;

    auto_strings(copy + copylen, SS_MODULE_FRONTEND) ;

    {
        /* parse each activated services */
        len = list.len ;
        char l[len + 1] ;
        stralloc sa = STRALLOC_ZERO ;

        sastr_to_char(l, &list) ;

        list.len = 0 ;

        for (pos = 0 ; pos < len ; pos += strlen(l + pos) + 1) {

            sa.len = 0 ;

            char fname[strlen(l + pos)] ;

            if (!ob_basename(fname, l + pos))
                log_dieusys(LOG_EXIT_ZERO, "basename of: ", l + pos) ;

            /** cannot call itself */
            if (!strcmp(name, fname))
                log_die(LOG_EXIT_SYS, "cyclic call detected -- ", name, " call ", fname) ;

            /** Search first inside the frontend directory.
             * If not found, search in the entire system. */

            if (!service_frontend_path(&sa, fname, info->owner, copy)) {

                if (!service_frontend_path(&sa, fname, info->owner, 0))
                    log_dieu(LOG_EXIT_USER, "find service frontend file of: ", fname) ;

                /*if (!hiercopy(sa.s, copy))
                    log_dieusys(LOG_EXIT_SYS, "copy: ", sa.s, " to: ", copy) ;
                */
            }

            char n[strlen(name) + 1 + strlen(fname) + 1] ;

            auto_strings(n, name, ":", fname) ;

            if (!sastr_add_string(&list, n))
                log_die_nomem("stralloc") ;

            parse_frontend(sa.s, ares, areslen, info, force, conf, copy, fname, name) ;
        }

        stralloc_free(&sa) ;
    }

    {
        /* make a good list of dependencies/requiredby*/
        len = list.len ;

        char l[len + 1] ;

        sastr_to_char(l, &list) ;

        list.len = 0 ;

        for (pos = 0 ; pos < len ; pos += strlen(l + pos) + 1) {

            int aresid = service_resolve_array_search(ares, *areslen, l + pos) ;
            if (aresid < 0)
                log_die(LOG_EXIT_USER, "service: ", l + pos, " not available -- please make a bug report") ;

            if (ares[aresid].dependencies.ndepends || ares[aresid].dependencies.nrequiredby)
                convert_tomodule(&ares[aresid], name) ;

            if (ares[aresid].logger.want && ares[aresid].type == TYPE_CLASSIC) {

                char n[strlen(ares[aresid].sa.s + ares[aresid].name) + SS_LOG_SUFFIX_LEN + 1] ;

                auto_strings(n, ares[aresid].sa.s + ares[aresid].name, SS_LOG_SUFFIX) ;

                if (!sastr_add_string(&list, n))
                    log_die_nomem("stralloc") ;
            }

            if (!sastr_add_string(&list, l + pos))
                log_die_nomem("stralloc") ;
        }
    }

    wres = resolve_set_struct(DATA_SERVICE, res) ;

    res->dependencies.contents = parse_compute_list(wres, &list, &res->dependencies.ncontents, 0) ;

    free(wres) ;
    stralloc_free(&list) ;
}