/*
 * ss_info_utils.c
 *
 * Copyright (c) 2018-2021 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 <66/info.h>

#include <unistd.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <wchar.h>

#include <string.h>
#include <sys/types.h> //ssize_t

#include <oblibs/sastr.h>
#include <oblibs/log.h>

#include <skalibs/buffer.h>
#include <skalibs/lolstdio.h>

#include <s6/supervise.h>

#include <66/resolve.h>
#include <66/state.h>

unsigned int MAXDEPTH = 1 ;

ss_resolve_graph_style graph_utf8 = {
    UTF_VR UTF_H,
    UTF_UR UTF_H,
    UTF_V " ",
    2
} ;

ss_resolve_graph_style graph_default = {
    "|-",
    "`-",
    "|",
    2
} ;

int info_getcols_fd(int fd)
{
    int width = -1;

    if(!isatty(fd)) return 0;

#if defined(TIOCGSIZE)
    struct ttysize win;
    if(ioctl(fd, TIOCGSIZE, &win) == 0)
        width = win.ts_cols;
#elif defined(TIOCGWINSZ)
    struct winsize win;
    if(ioctl(fd, TIOCGWINSZ, &win) == 0)
        width = win.ws_col;
#endif

    // return abitrary value
    if(width <= 0) return 100 ;

    return width;
}

void info_field_align (char buf[][INFO_FIELD_MAXLEN],char fields[][INFO_FIELD_MAXLEN],wchar_t const field_suffix[],size_t buflen)
{
    log_flow() ;

    size_t a = 0, b = 0, maxlen = 0, wlen[buflen], len = INFO_FIELD_MAXLEN+nb_el(field_suffix) ;

    int maxcol = 0, wcol[buflen] ;

    wchar_t wbuf[buflen][len] ;

    for(a = 0; a < buflen; a++)
        for (b = 0; b < len; b++)
            wbuf[a][b] = 0 ;

    for(a = 0; a < buflen; a++)
    {
        wlen[a] = mbstowcs(wbuf[a], buf[a], strlen(buf[a]) + 1) ;
        wcol[a] = wcswidth(wbuf[a], wlen[a]) ;
        if(wcol[a] > maxcol) {
            maxcol = wcol[a] ;
        }
        if(wlen[a] > maxlen) {
            maxlen = wlen[a] ;
        }
    }

    for(a = 0; a < buflen; a++)
    {
        size_t padlen = maxcol - wcol[a] ;
        wmemset(wbuf[a] + wlen[a], L' ', padlen) ;
        wmemcpy(wbuf[a] + wlen[a] + padlen, field_suffix, nb_el(field_suffix)) ;
        wcstombs(fields[a], wbuf[a], sizeof(wbuf[a])) ;
    }
}

size_t info_length_from_wchar(char const *str)
{
    log_flow() ;

    ssize_t len ;
    wchar_t *wcstr ;
    if(!str || !str[0]) return 0 ;

    len = strlen(str) + 1 ;
    wcstr = calloc(len, sizeof(wchar_t)) ;
    len = mbstowcs(wcstr, str, len) ;
    len = wcswidth(wcstr, len) ;
    free(wcstr) ;

    return len == -1 ? 0 : (size_t)len  ;
}

size_t info_display_field_name(char const *field)
{
    log_flow() ;

    size_t len = 0 ;
    if(field)
    {
        len = info_length_from_wchar(field) + 1 ;
        if (!bprintf(buffer_1,"%s%s%s ", log_color->info, field, log_color->off)) log_dieusys(LOG_EXIT_SYS,"write to stdout") ;
    }
    return len ;
}

void info_display_list(char const *field, stralloc *list)
{
    log_flow() ;

    size_t a = 0 , b, cols, padding = 0, slen = 0 ;

    unsigned short maxcols = info_getcols_fd(1) ;

    padding = info_length_from_wchar(field) + 1 ;

    cols = padding ;

    for (; a < list->len ; a += strlen(list->s + a) + 1)
    {
        char const *str = list->s + a ;
        slen = info_length_from_wchar(str) ;
        if((maxcols > padding) && (cols + slen + 2 >= maxcols))
        {
            cols = padding ;
            if (buffer_puts(buffer_1,"\n") == -1) goto err ;
            for(b = 1 ; b <= padding ; b++)
                if (buffer_puts(buffer_1," ") == -1) goto err ;
        }
        else if (cols != padding)
        {
            if (buffer_puts(buffer_1," ") == -1) goto err ;
            cols += 2 ;
        }
        if (!bprintf(buffer_1,"%s",str)) goto err ;
        cols += slen ;
    }
    if (buffer_puts(buffer_1,"\n") == -1) goto err ;

    return ;
    err:
        log_dieusys(LOG_EXIT_SYS,"write to stdout") ;
}

void info_display_nline(char const *field,char const *str)
{
    log_flow() ;

    size_t pos = 0, padding = info_length_from_wchar(field) + 1 ;
    stralloc tmp = STRALLOC_ZERO ;
    stralloc cp = STRALLOC_ZERO ;
    if (!stralloc_cats(&tmp,str) ||
    !stralloc_0(&tmp)) log_die_nomem("stralloc") ;
    if (!sastr_split_string_in_nline(&tmp)) log_dieu(LOG_EXIT_SYS,"split string in nline") ;
    for (;pos < tmp.len ; pos += strlen(tmp.s + pos) + 1)
    {
        cp.len = 0 ;
        if (!stralloc_cats(&cp,tmp.s + pos) ||
        !stralloc_0(&cp)) log_die_nomem("stralloc") ;
        if (field)
        {
            if (pos)
            {
                if (!bprintf(buffer_1,"%*s",padding,""))
                    log_dieusys(LOG_EXIT_SYS,"write to stdout") ;
            }
        }
        info_display_list(field,&cp) ;
    }
    stralloc_free(&tmp) ;
    stralloc_free(&cp) ;
}

void info_graph_display(ss_resolve_t *res, depth_t *depth, int last, int padding, ss_resolve_graph_style *style)
{
    log_flow() ;

    int level = 1 ;
    char str_pid[UINT_FMT] ;
    uint8_t pid_color = 0 ;
    const char *tip = "" ;
    char *ppid ;
    ss_state_t sta = STATE_ZERO ;
    s6_svstatus_t status = S6_SVSTATUS_ZERO ;
    char *name = res->sa.s + res->name ;

    if (res->type == TYPE_CLASSIC || res->type == TYPE_LONGRUN)
    {
        s6_svstatus_read(res->sa.s + res->runat ,&status) ;
        pid_color = !status.pid ? 1 : 2 ;
        str_pid[uint_fmt(str_pid, status.pid)] = 0 ;
        ppid = &str_pid[0] ;
    }
    else
    {
        char *ste = res->sa.s + res->state ;
        char *name = res->sa.s + res->name ;
        if (!ss_state_check(ste,name))
        {
            ppid = "unitialized" ;
            goto dis ;
        }

        if (!ss_state_read(&sta,ste,name))
            log_dieusys(LOG_EXIT_SYS,"read state of: ",name) ;

        if (sta.init) {
            ppid = "unitialized" ;
        }
        else if (!sta.state)
        {
            ppid = "down" ;
            pid_color = 1 ;
        }
        else if (sta.state)
        {
            ppid = "up" ;
            pid_color = 2 ;
        }
    }
    dis:

    tip = last ? style->last : style->tip ;

    while(depth->prev)
        depth = depth->prev ;

    while(depth->next)
    {
        if (!bprintf(buffer_1,"%*s%-*s",style->indent * (depth->level - level) + (level == 1 ? padding : 0), "", style->indent, style->limb)) return ;
        level = depth->level + 1 ;
        depth = depth->next ;
    }

    if (!bprintf(buffer_1,"%*s%*s%s(%s%s%s,%s%s%s,%s) %s", \
                level == 1 ? padding : 0,"", \
                style->indent * (depth->level - level), "", \
                tip, \
                pid_color > 1 ? log_color->valid : pid_color ? log_color->error : log_color->warning, \
                ppid, \
                log_color->off, \
                res->disen ? log_color->off : log_color->error, \
                res->disen ? "Enabled" : "Disabled", \
                log_color->off, \
                get_key_by_enum(ENUM_TYPE,res->type), \
                name)) return ;

    if (buffer_putsflush(buffer_1,"\n") < 0) return ;
}

int info_walk(ss_resolve_t *res,char const *src,int reverse, depth_t *depth, int padding, ss_resolve_graph_style *style)
{
    log_flow() ;

    size_t pos = 0, idx = 0 ;
    stralloc sadeps = STRALLOC_ZERO ;
    ss_resolve_t dres = RESOLVE_ZERO ;

    if((!res->ndeps) || (depth->level > MAXDEPTH))
        goto freed ;

    if (!sastr_clean_string(&sadeps,res->sa.s + res->deps)) goto err ;
    if (reverse) sastr_reverse(&sadeps) ;

    for(; pos < sadeps.len ; pos += strlen(sadeps.s + pos) + 1,idx++ )
    {
        int last =  idx + 1 < res->ndeps  ? 0 : 1 ;
        char *name = sadeps.s + pos ;

        if (!ss_resolve_check(src,name)) goto err ;
        if (!ss_resolve_read(&dres,src,name)) goto err ;

        info_graph_display(&dres, depth, last,padding,style) ;

        if (dres.ndeps)
        {
            depth_t d =
            {
                depth,
                NULL,
                depth->level + 1
            } ;
            depth->next = &d;

            if(last)
            {
                if(depth->prev)
                {
                    depth->prev->next = &d;
                    d.prev = depth->prev;
                    depth = &d;

                }
                else
                    d.prev = NULL;
            }
            if (!info_walk(&dres, src, reverse, &d, padding, style)) goto err;
            depth->next = NULL;
        }
    }
    freed:
    ss_resolve_free(&dres) ;
    stralloc_free(&sadeps) ;
    return 1 ;
    err:
        ss_resolve_free(&dres) ;
        stralloc_free(&sadeps) ;
        return 0 ;
}

int info_graph_init (ss_resolve_t *res,char const *src,unsigned int reverse, int padding, ss_resolve_graph_style *style)
{
    log_flow() ;

    depth_t d = {
        NULL,
        NULL,
        1
    } ;

    if(!info_walk(res,src,reverse,&d,padding,style)) return 0 ;

    return 1 ;
}