/*
 * ssexec_reconfigure.c
 *
 * Copyright (c) 2018-2024 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 <stdint.h>

#include <oblibs/log.h>
#include <oblibs/types.h>
#include <oblibs/graph.h>
#include <oblibs/string.h>

#include <skalibs/sgetopt.h>
#include <skalibs/genalloc.h>

#include <66/ssexec.h>
#include <66/config.h>
#include <66/graph.h>
#include <66/state.h>
#include <66/svc.h>
#include <66/sanitize.h>
#include <66/service.h>
#include <66/constants.h>
#include <66/tree.h>

int ssexec_reconfigure(int argc, char const *const *argv, ssexec_t *info)
{
    log_flow() ;

    int r, e = 0 ;
    uint32_t flag = 0 ;
    uint8_t siglen = 0, issupervised = 0 ;
    graph_t graph = GRAPH_ZERO ;
    struct resolve_hash_s *hres = NULL ;
    resolve_service_t_ref pres = 0 ;
    struct resolve_hash_s tostate[argc] ;
    struct resolve_hash_s toenable[argc] ;
    unsigned int list[SS_MAX_SERVICE + 1], visit[SS_MAX_SERVICE + 1], ntostate = 0, ntoenable = 0, nservice = 0, n = 0 ;
    ss_state_t sta = STATE_ZERO ;

    memset(list, 0, (SS_MAX_SERVICE + 1) * sizeof(unsigned int)) ;
    memset(visit, 0, (SS_MAX_SERVICE + 1) * sizeof(unsigned int)) ;
    memset(tostate, 0, argc * sizeof(struct resolve_hash_s)) ;
    memset(toenable, 0, argc * sizeof(struct resolve_hash_s)) ;
    FLAGS_SET(flag, STATE_FLAGS_TOPROPAGATE|STATE_FLAGS_TOPARSE|STATE_FLAGS_WANTUP) ;

    {
        subgetopt l = SUBGETOPT_ZERO ;

        for (;;) {

            int opt = subgetopt_r(argc, argv, OPTS_SUBSTART, &l) ;
            if (opt == -1) break ;

            switch (opt) {

                case 'h' :

                    info_help(info->help, info->usage) ;
                    return 0 ;

                case 'P' :

                    siglen++ ;
                    break ;

                default :

                    log_usage(info->usage, "\n", info->help) ;
            }
        }
        argc -= l.ind ; argv += l.ind ;
    }

    if (argc < 1)
        log_usage(info->usage, "\n", info->help) ;

    /** build the graph of the entire system */
    graph_build_service(&graph, &hres, info, flag) ;

    if (!graph.mlen)
        log_die(LOG_EXIT_USER, "services selection is not available -- have you already parsed a service?") ;

    for (; n < argc ; n++) {

        struct resolve_hash_s *hash = hash_search(&hres, argv[n]) ;
        if (hash == NULL)
            log_die(LOG_EXIT_USER, "service: ", argv[n], " not available -- did you parse it?") ;

        pres = &hash->res ;
        char status[strlen(pres->sa.s + pres->path.servicedir) + SS_STATE_LEN + 1] ;

        auto_strings(status, pres->sa.s + pres->path.servicedir, SS_STATE) ;

        if (!state_read(&sta, pres))
            log_dieu(LOG_EXIT_SYS, "read state file of: ", argv[n]) ;

        sta.toparse = STATE_FLAGS_TRUE ;

        if (!state_write(&sta, pres))
            log_dieusys(LOG_EXIT_SYS, "write status file of: ", argv[n]) ;

        /** need to reverse the previous state change to
         * for current live service.*/
        tostate[ntostate++] = *hash ;

        issupervised = sta.issupervised == STATE_FLAGS_TRUE ? 1 : 0 ;

        if (pres->enabled && !pres->inns)
            toenable[ntoenable++] = *hash ;

        if (!issupervised) {
            /* only force to parse it again */
            continue ;

        } else {

            if (!state_write_remote(&sta, status))
                log_dieusys(LOG_EXIT_SYS, "write status file of: ", argv[n]) ;

            /** services of group boot cannot be restarted, the changes will appear only at
             * next reboot.*/
            r = tree_ongroups(pres->sa.s + pres->path.home, pres->sa.s + pres->treename, TREE_GROUPS_BOOT) ;

            if (r < 0)
                log_dieu(LOG_EXIT_SYS, "get groups of service: ", argv[n]) ;

            if (r)
                continue ;

            graph_compute_visit(*hash, visit, list, &graph, &nservice, 1) ;
        }
    }

    r = svc_scandir_ok(info->scandir.s) ;
    if (r < 0)
        log_dieusys(LOG_EXIT_SYS, "check: ", info->scandir.s) ;

    if (nservice && r) {

        /** User may request for a specific tree with the -t options.
         * The tree specified may be different from the actual one.
         * So, remove the -t option for the stop process and use it again
         * for the parse and start process. */
        char tree[info->treename.len + 1] ;
        auto_strings(tree, info->treename.s) ;

        info->treename.len = 0 ;
        info->opt_tree = 0 ;

        unsigned int m = 0 ;
        int nargc = 3 + nservice + siglen ;
        char const *prog = PROG ;
        char const *newargv[nargc] ;

        char const *help = info->help ;
        char const *usage = info->usage ;

        info->help = help_stop ;
        info->usage = usage_stop ;

        newargv[m++] = "stop" ;
        if (siglen)
            newargv[m++] = "-P" ;
        newargv[m++] = "-u" ;

        for (n = 0 ; n < nservice ; n++) {

            char *name = graph.data.s + genalloc_s(graph_hash_t,&graph.hash)[list[n]].vertex ;
            /** the stop process will handle logger. Remove those from the list.*/
            if (get_rstrlen_until(name,SS_LOG_SUFFIX) < 0)
                newargv[m++] = name ;
        }

        newargv[m] = 0 ;

        PROG = "stop" ;
        e = ssexec_stop(m, newargv, info) ;
        PROG = prog ;
        if (e)
            goto freed ;

        info->help = help ;
        info->usage = usage ;

        info->treename.len = 0 ;
        if (!auto_stra(&info->treename, tree))
            log_die_nomem("stralloc") ;
        info->opt_tree = 1 ;
    }

    /** force to parse again the service */
    for (n = 0 ; n < argc ; n++)
        sanitize_source(argv[n], info, flag) ;

    for (n = 0 ; n < ntostate ; n++) {

        /** live of the service still exist.
         * Reverse to the previous state of the toparse flag. */
        if (state_read_remote(&sta, tostate[n].res.sa.s + tostate[n].res.live.statedir)) {

            sta.toparse = STATE_FLAGS_FALSE ;

            if (!state_write_remote(&sta, tostate[n].res.sa.s + tostate[n].res.live.statedir))
                log_warnusys("write status file of: ", tostate[n].res.sa.s + tostate[n].res.live.statedir) ;
        }
    }

    if (nservice && r) {

        unsigned int m = 0 ;
        int nargc = 2 + nservice + siglen ;
        char const *prog = PROG ;
        char const *newargv[nargc] ;

        char const *help = info->help ;
        char const *usage = info->usage ;

        info->help = help_start ;
        info->usage = usage_start ;

        newargv[m++] = "start" ;
        if (siglen)
            newargv[m++] = "-P" ;

        for (n = 0 ; n < nservice ; n++) {

            char *name = graph.data.s + genalloc_s(graph_hash_t,&graph.hash)[list[n]].vertex ;
            if (get_rstrlen_until(name,SS_LOG_SUFFIX) < 0)
                newargv[m++] = name ;
        }

        newargv[m] = 0 ;

        PROG= "start" ;
        e = ssexec_start(m, newargv, info) ;
        PROG = prog ;

        info->help = help ;
        info->usage = usage ;
    }

    if (ntoenable) {

        /** enable again the service if it was enabled */
        unsigned int m = 0 ;
        int nargc = 2 + ntoenable ;
        char const *prog = PROG ;
        char const *newargv[nargc] ;

        char const *help = info->help ;
        char const *usage = info->usage ;

        info->help = help_enable ;
        info->usage = usage_enable ;

        newargv[m++] = "enable" ;

        n = 0 ;
        for (; n < ntoenable ; n++) {

            char *name = toenable[n].name ;
             if (get_rstrlen_until(name,SS_LOG_SUFFIX) < 0)
                newargv[m++] = name ;
        }

        newargv[m] = 0 ;

        PROG= "enable" ;
        e = ssexec_enable(m, newargv, info) ;
        PROG = prog ;

        info->help = help ;
        info->usage = usage ;
    }

    freed:
        hash_free(&hres) ;
        graph_free_all(&graph) ;

    return e ;
}