
//  ------------------------------------------------------------------
//  GoldED+
//  Copyright (C) 1990-1999 Odinn Sorensen
//  Copyright (C) 1999-2000 Alexander S. Aganichev
//  ------------------------------------------------------------------
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License as
//  published by the Free Software Foundation; either version 2 of the
//  License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
//  MA 02111-1307 USA
//  ------------------------------------------------------------------
//  $Id$
//  ------------------------------------------------------------------
//  Global utility functions (not overlayed in 16-bit DOS).
//  ------------------------------------------------------------------

#include <cstdarg>
#include <golded.h>

#define UPDATE_STATUSLINE_ERROR    " ERROR! Please look a log file and make a report to developers! "

//  ------------------------------------------------------------------

extern bool in_arealist;
extern GPickArealist* PickArealist;


//  ------------------------------------------------------------------

void update_statuslines()
{
# define BUFSIZE 200
# define BUFLEN  199
    char buf[BUFSIZE]="";    /* FIXME: it is need to use dynamic arrays in this fuction to prevent buffer overflow or screen garbage */
    char * const buf_end = buf+BUFLEN;
    static char old_status_line[BUFSIZE] = "";
    char * const old_status_line_end = old_status_line+BUFLEN;
    static int called = NO;
    const int WIDE= BUFLEN>MAXCOL? MAXCOL : BUFLEN;

    HandleGEvent(EVTT_REMOVEVOCBUF);

    if(CFG->switches.get(dispstatusline) or not called) // FIXME: Strange, status line displayed always in first call. May be need to change?
    {

        called = YES;

        vchar sep = _box_table(W_BSTAT, 3);
        char help[BUFSIZE], clkinfo[BUFSIZE];
        *clkinfo = NUL;
        *help = NUL;

        if(CFG->switches.get(statuslineclock))
        {
            time32_t t = gtime(NULL);
            struct tm tm;
            glocaltime(&tm, &t);
            if( strftimei(help, 40, LNG->StatusLineTimeFmt, &tm) )
                gsprintf(PRINTF_DECLARE_BUFFER(clkinfo), "   %s", help);
        }

        if(CFG->statuslinehelp == -1)
            *help = NUL;
        else if(CFG->statuslinehelp)
            gsprintf(PRINTF_DECLARE_BUFFER(help), "%s   ", LNG->StatusLineHelp);
        else
            gsprintf(PRINTF_DECLARE_BUFFER(help), "%s%s%s%s %s%i.%i.%i%s   ",
                     __gver_prename__,
                     __gver_name__,
                     __gver_postname__,
                     __gver_platform__,
                     __gver_preversion__,
                     __gver_major__,
                     __gver_minor__,
                     __gver_release__,
                     __gver_postversion__
                    );

        int help_len = strlen(help);
        int clk_len = strlen(clkinfo);
        int len = WIDE-help_len-clk_len-2;
        gsprintf(PRINTF_DECLARE_BUFFER(buf), "%c%s%-*.*s%s ", goldmark, help, len, len, information, clkinfo);

        char *begin = buf;
        char *obegin = old_status_line;
        char *end = buf + WIDE-1;               // last position before final '\0'
        char *oend = old_status_line + WIDE-1;  // last position before final '\0'
        while((*begin != NUL) and (*begin == *obegin) and (begin<buf_end) and (obegin<old_status_line_end))
        {
            ++begin;
            ++obegin;
        }
        if(begin == end)
            return;
        // we have at least one mismatch
        if(*obegin)
        {
            while((*end == *oend) and (buf<end) and (old_status_line<oend) )
            {
                --end;
                --oend;
            }
        }
        len = end-begin+1;
        memcpy( obegin, begin, (len<sizeof(old_status_line))? len : sizeof(old_status_line) );

#ifdef GOLD_MOUSE
        gmou.GetStatus();
        if(gmou.Row() == MAXROW-1)
            gmou.HideCursor();
#endif
        int row, col;
        vposget(&row, &col);
        *(++end) = NUL;
        wwprintstr(W_STAT, 0,begin-buf, C_STATW, begin);
        if(*help and ((begin - buf) < (help_len-1)) and ((end - buf) > (help_len-1)))
            wwprintc(W_STAT, 0,help_len-1, C_STATW, sep);
        if(((begin - buf) < (WIDE-clk_len)) and ((end - buf) > (WIDE-clk_len)))
            wwprintc(W_STAT, 0,WIDE-clk_len, C_STATW, sep);
        vposset(row, col);
#ifdef GOLD_MOUSE
        if(gmou.Row() == MAXROW-1)
            gmou.ShowCursor();
#endif
    }
# undef BUFSIZE
# undef BUFLEN
} /* update_statuslines() */


//  ------------------------------------------------------------------

void update_statusline(const char* info)
{

    if ( !(info) )
    {
        LOG.errpointer(__FILE__, __LINE__);
        LOG.printf( "! Parameter is NULL pointer: update_statusline(NULL).");
        strxcpy(information, UPDATE_STATUSLINE_ERROR, sizeof(information));
    }
    else if (*info)
    {
        strxcpy(information, info, sizeof(information));
    }
    else
        *information = '\0';
    update_statuslines();
}


//  ------------------------------------------------------------------

void update_statuslinef(const char *format, const char *token, ...)
{
    if ( !(format && *format && token) )
    {
        LOG.errpointer(__FILE__, __LINE__);
        LOG.printf( "! Parameter is NULL pointer or empty string: update_statuslinef(\"%s\",\"%s\",...).",
                    (format?(*format)?format:"":"(NULL)"), (token?token:"(NULL)") );
        update_statusline(UPDATE_STATUSLINE_ERROR);
        return;
    }

    bool error = false;
    char winfobuf[350];
    va_list argptr;
    va_start(argptr, token);
    vsprintf(winfobuf, format, argptr);
    va_end(argptr);
    update_statusline(winfobuf);

    if (error)
    {
        SayBibi();
        waitkeyt(1000);
    }
}


//  ------------------------------------------------------------------

void w_shadow()
{

    if(CFG->switches.get(screenshadows))
        wshadow(C_SHADOW);
}


//  ------------------------------------------------------------------

void w_info(const char* info)
{

    static int wh=-1;
    static int srow;
    static int scol;
    static int erow;
    static int ecol;
    static int len;
    static char buf[150] = { "" };
    char* buf2 = NULL;

    int prev_wh = whandle();
    if(wh != -1)
        wactiv_(wh);

    if(info)
    {
        int tmp = strlen(info);
        if(tmp > MAXCOL-5)
        {
            buf2 = (char *)throw_malloc(MAXCOL-5);
            strxcpy(buf2, info, MAXCOL-5);
            info = buf2;
            tmp = MAXCOL-6;
        }
        if(wh == -1)
        {
            len = tmp;
            srow = inforow;
            erow = srow+3-1;
            scol = ((MAXCOL-len)/2)-1;
            ecol = scol+len+1;
            wh = wopen(srow, scol, erow, ecol, W_BINFO, C_INFOB, C_INFOW);
            w_shadow();
        }
        else
        {
            if(len != tmp)
            {
                len = tmp;
                scol = ((MAXCOL-len)/2)-1;
                ecol = scol+len+1;
                wclose();
                wh = wopen(srow, scol, erow, ecol, W_BINFO, C_INFOB, C_INFOW);
                w_shadow();
            }
        }
        if(not streql(buf, info))
        {
            strcpy(buf, info);
            wprints(0, 0, C_INFOW, buf);
        }
    }
    else
    {
        if(wh != -1)
        {
            *buf = NUL;
            wclose();
            wh = -1;
        }
    }

    wactiv_(prev_wh);

    if(buf2)
        throw_free(buf2);
}


//  ------------------------------------------------------------------


void w_infof(const char* format, ...)
{

    char winfobuf[350];
    va_list argptr;
    va_start(argptr, format);
    vsprintf(winfobuf, format, argptr);
    va_end(argptr);
    w_info(winfobuf);
}


//  ------------------------------------------------------------------

void w_progress(int mode, vattr attr, long pos, long size, const char* title)
{

    static int wh = -1;
    static long prev_pos = 0;

    int prev_wh = whandle();
    if(wh != -1)
        wactiv_(wh);

    switch(mode)
    {
    case MODE_NEW:
oops:
        if(wh == -1)
        {
            wh = wopen_(inforow, ((MAXCOL-63)/2)-1, 3, 63, W_BINFO, C_INFOB, C_INFOW);
            set_title(title, TCENTER, C_INFOT);
            title_shadow();
            goto force_update;
        }
    case MODE_UPDATE:
        if(wh == -1)
            goto oops;  // Oops, someone forgot to open the window..
        if((pos*58/size) != (prev_pos*58/size))
        {
force_update:
            wpropbar(1, 0, 59, attr, pos, size);
        }
        prev_pos = pos;
        break;
    case MODE_QUIT:
        if(wh != -1)
        {
            wclose();
            wunlink(wh);
            wh = -1;
            prev_pos = 0;
        }
        break;
    }

    wactiv_(prev_wh);
}


//  ------------------------------------------------------------------

void maketitle()
{

    wtitle(m_title, m_titlepos, m_titleattr);
}


//  ------------------------------------------------------------------

int maketitle_and_status(char *dir)
{

    maketitle();
    update_statuslinef(LNG->ImportStatus, "ST_IMPORTSTATUS", dir);
    return 0;
}

//  ------------------------------------------------------------------

void set_title(const char* t, int p, vattr a)
{

    strcpy(m_title, t);
    m_titlepos  = p;
    m_titleattr = a;
}


//  ------------------------------------------------------------------

void title_shadow()
{

    maketitle();
    w_shadow();
}

//  ------------------------------------------------------------------
// UUE character decode: maps ASCII to 6-bit value, -1 = invalid.
static int uu_xlat(unsigned char c)
{
    if (c >= ' ' && c < (' ' + 64))     return c - ' ';
    if (c >= '`' && c < ('`' + 32))     return c - '`'; // lowercase alias
    return -1;
}

// UUE Detection Heuristic
bool is_uue_line(const char* ptr)
{
    if (!ptr || !*ptr) return false;

    const unsigned char* s = (const unsigned char*)ptr;
    int linelen = 0;
    while (s[linelen] && s[linelen] != '\r' && s[linelen] != '\n') {
        linelen++;
    }

    if (linelen == 0) return false;

    int decoded_bytes = uu_xlat(s[0]);
    if (decoded_bytes < 0 || decoded_bytes > 45) return false;

    // An empty line encoding 0 bytes (e.g. at the end of UUE blocks)
    if (decoded_bytes == 0) return (linelen <= 2);

    int expected = 1 + ((decoded_bytes + 2) / 3) * 4;
    int datalen = linelen;

    // Tolerate one trailing checksum character
    if (datalen == expected + 1) datalen = expected;

    bool length_ok = false;
    if (datalen == expected) {
        length_ok = true;
    } else {
        int max_expected = 61; // 1 + ceil(45/3)*4
        if (datalen > expected && datalen <= max_expected) {
            length_ok = true;
        } else {
            // Padding variance handling
            switch (decoded_bytes % 3) {
                case 1: if (expected - 2 == datalen) length_ok = true; break;
                case 2: if (expected - 1 == datalen) length_ok = true; break;
            }
        }
    }

    if (!length_ok) return false;

    // Validate overall data stream
    for (int i = 0; i < datalen; i++) {
        if (uu_xlat(s[i]) < 0) return false;
    }

    return true;
}

//  ------------------------------------------------------------------

int IsQuoteChar(const char* s)
{

    if(*s)
    {
        if(*s == '>')
            return true;
        if(*AA->Quotechars())
            if(strchr(AA->Quotechars(), *s))
                return true;
    }
    return false;
}


//  ------------------------------------------------------------------

int is_quote(const char* ptr)
{
    // Prevent corrupting UUE Lines
    if (is_uue_line(ptr)) return false;

    const char* endptr = ptr + 11;

    // Skip leading whitespace
    while((*ptr == ' ') or (*ptr == LF) or issoftcr(*ptr))
        ptr++;

    // Prevent corrupting UUE Lines with accidental leading whitespace
    if (is_uue_line(ptr)) return false;

    // Check for empty string
    if((*ptr == NUL) or (ptr >= endptr))
        return false;

    // Check for userdefined quotechars after first whitespace
    if(IsQuoteChar(ptr))
        return true;

    endptr = ptr + 11; // match 10 chars after whitespaces

    while (*ptr && !IsQuoteChar(ptr) &&
            !iscntrl(*ptr) &&
            !strchr(AA->Quotestops(), *ptr) &&
            !isspace(*ptr)) ptr++;

    if ((ptr < endptr) && IsQuoteChar(ptr))
        return true;

    /*
      int spaces = 0;
      while((ptr < endptr) and *ptr) {

        if(*ptr == LF or issoftcr(*ptr)) {
          // Ignore LF's and SOFTCR's and extend check zone if found
          endptr++;
        }
        else if(*ptr == '>') {
          // Found true quote char
          return true;
        }
        else if(*ptr == ' ') {
          spaces++;
          if(spaces > 1)
            return false;
        }
        else if(iscntrl(*ptr) or strchr(AA->Quotestops(), *ptr)) {
          // Found a char that cannot occur in a quotestring
          return false;
        }
        ptr++;
      }
    */

    return false;
}


//  ------------------------------------------------------------------

bool is_quote2(Line* line, const char* ptr)
{
    // Prevent treating UUE lines as quotes
    if (is_uue_line(ptr)) return false;

    if (!CFG->quoteusenewai) return true;

    char *head = (char *)ptr;

    // search first '>' before CR, NUL or any other quote character
    for (bool found = false; !found; ptr++)
    {
        if (*ptr == '>')
            found = true;
        else
        {
            if (IsQuoteChar(ptr) || (*ptr == CR) || !*ptr)
                return true;
        }
    }

    // line is double quoted?
    if (is_quote(ptr))
        return true;

    // if "SPACE*[a-zA-Z]{0, 3}>"
    for (ptr--; isspace(*head); head++);

    int nr = 0;
    for (char *tmp = head; tmp < ptr; tmp++)
    {
        char ch = g_toupper(*tmp);
        if ((ch >= 'A') && (ch <= 'Z'))
            nr++;
    }

    if ((nr < 4) && (nr == (ptr-head)))
        return true;

    // take a look at previous lines
    Line *paragraph = NULL;
    for (Line *ln = line->prev; ln; ln = ln->prev)
    {
        // previous line is quoted?
        if (ln->isquote())
            return true;
        // or begin of paragraph?
        if ((ln->txt.length() == 0) ||
                (ln->txt[0] == LF) ||
                (ln->txt[0] == CR))
        {
            if (paragraph) return true;
            else
            {
                paragraph = ln;
                continue;
            }
        }
        // or kludge?
        if (ln->txt[0] == CTRL_A)
            return true;

        // found begin of citation?
        const char *begin = strrchr(ln->txt.c_str(), '<');
        if (begin)
        {
            // found both '<' and '>'?
            if (strchr(begin, '>'))
                return true;

            for (Line *ln2 = ln->next; ln2 != line; ln2 = ln2->next)
            {
                // found both '<' and '>'?
                if (strchr(ln2->txt.c_str(), '>'))
                    return true;
            }

            // hide false paragraph
            if (paragraph) paragraph->type |= GLINE_TXTH;
            return false;   // don't quote current line
        }
    }

    return true;
}


//  ------------------------------------------------------------------

vattr quotecolor(const char* line)
{

    char buf[MAXQUOTELEN];
    uint len;

    GetQuotestr(line, buf, &len);
    uint qc = 0;

    for(uint i=0; i<len; i++)
        if(IsQuoteChar(&buf[i]))
            qc++;

    return (qc & 1) ? C_READQ : C_READQ2;
}


//  ------------------------------------------------------------------

int GetQuotestr(const char* ptr, char* qbuf, uint* qlen)
{

    if(is_quote(ptr))
    {

        const char* lp = ptr;
        int n, x;
        /*
            for(;;) {

              // Skip leading spaces
              while(isspace(*lp) or issoftcr(*lp))
                lp++;
              if(IsQuoteChar(lp)) {      // Type 1 : ">xxxx>" and ">xxxx:"
                lp++;
                while(isspace(*lp) or issoftcr(*lp))
                  lp++;
                if(is_quote(lp))
                  continue;
                if(not (IsQuoteChar(lp-1) or (*(lp-1) == ':'))) {
                  while(not IsQuoteChar(lp))
                    lp--;
                  lp++;
                }
              }
              else {                     // Type 2: "xxxx>"
                while(not (IsQuoteChar(lp) and not IsQuoteChar(lp+1)) and (*lp != CR) and (*lp != NUL))
                  ++lp;
                if(is_quote(lp))
                  continue;
                if(*lp)
                  lp++;
              }

              break;
            }
        */

        while (isspace(*lp) or issoftcr(*lp)) lp++;
        while (!IsQuoteChar(lp)) lp++;
        while (IsQuoteChar(lp)) lp++;

        // lp now points to the character after the quotestring

        x = lp - ptr;
        if((*lp != NUL) and (isspace(*lp) or issoftcr(*lp)))
            x++;

        for(*qlen = n = 0; (n < x) and ((*qlen) < MAXQUOTELEN-1); n++, ptr++)
        {
            if((*ptr != LF) and not issoftcr(*ptr))
            {
                *qbuf++ = *ptr;
                (*qlen)++;
            }
        }
        *qbuf = NUL;
    }
    else
    {
        *qbuf = NUL;
        *qlen = 0;
    }

    return *qlen;
}


//  ------------------------------------------------------------------

static int KeyCmp(const gkey* a, const gkey* b)
{

    return CmpV(*a, *b);
}


//  ------------------------------------------------------------------

gkey SearchKey(gkey key, std::list<CmdKey>::iterator keys, int totkeys)
{

    std::list<CmdKey>::iterator kmin;
    int again = 0;

    do
    {
        kmin = keys;
        int tkeys=totkeys;
        while(tkeys > 0)
        {
            int j = KeyCmp(&key, &(kmin->key));
            if(j == 0)
                return(kmin->cmd);
            else if(j < 0)
                break;
            else
            {
                kmin++;
                tkeys--;
            }
        }

        // Key not found. Try again, this time without the scancode.
        key &= 0x00FF;
    }
    while(not again++ and key);

    return 0;
}


//  ------------------------------------------------------------------

static void call_func_geutil(VfvCP func)
{

    struct _menu_t* menu;
    int row, col;

    bool hidden = vcurhidden();
    vposget(&row, &col);
    menu = gwin.cmenu;
    (*func)();
    gwin.cmenu = menu;
    vposset(row, col);
    if(hidden)
        vcurhide();
    else
        vcurshow();
}


//  ------------------------------------------------------------------

void call_help()
{

    // search through onkey linked list for a
    // matching defined onkey.  if one is found,
    // then save the current environment, call the
    // onkey's function, and restore the environment.

    KBnd* onkey = gkbd.onkey;
    while(onkey != NULL)
    {
        if(onkey->keycode == Key_F1)
        {
            call_func_geutil(onkey->func);
            break;
        }
        onkey = onkey->prev;
    }
}


//  ------------------------------------------------------------------

void CheckTick(gkey quitkey)
{
    if (gkbd.tickvalue < gkbd.tickpress)
        gkbd.tickpress = gkbd.tickvalue;

    Clock idle_secs = (gkbd.tickvalue - gkbd.tickpress)/10;

    if (CFG->timeout)
    {
        if (idle_secs >= CFG->timeout)
        {
            kbput(quitkey);
            return;
        }
    }

    IdleCheckSemaphores();

    if(CFG->screenblanker)
    {
        if(idle_secs >= CFG->screenblanker)
        {

            blanked = true;
            ScreenBlankIdle();
            kbdsettickfunc(ScreenBlankIdle);

            getxch();

            blanked = false;
            ScreenBlankIdle();
            kbdsettickfunc(update_statuslines);

            //maybe we've scanned areas while in screenblanker, so
            //update screen
            if(in_arealist)
            {
                PickArealist->update();
                PickArealist->do_delayed();
            }
        }
    }
}


//  ------------------------------------------------------------------

void IdleCheckSemaphores()
{
    // I don't like this solution either... :(
    static Clock last_secs = 0;

    if (gkbd.tickvalue < gkbd.tickpress)
        gkbd.tickpress = gkbd.tickvalue;

    Clock idle_secs = (gkbd.tickvalue - gkbd.tickpress)/10;

    // Make sure the stuff below is only run once in a second
    if(not idle_secs or (idle_secs - last_secs == 0))
        return ;

    if(in_arealist)
    {
        if(CFG->semaphore.idletime)
        {
            if((idle_secs % CFG->semaphore.idletime) == 0)
                CheckSemaphores();
        }
    }

    last_secs = idle_secs;
}


//  ------------------------------------------------------------------

char* strtmp(const char* str)
{

    static INam tmp;
    return strxcpy(tmp, str, sizeof(tmp));
}


//  ------------------------------------------------------------------

bool find(const std::vector<const char *> &vec, const char *str)
{

    std::vector<const char *>::const_iterator i;

    for(i = vec.begin(); i != vec.end(); i++)
        if(streql(*i, str))
            return true;

    return false;
}

bool find(const std::vector<std::string> &vec, const std::string &str)
{
    std::vector<std::string>::const_iterator it = vec.begin();
    std::vector<std::string>::const_iterator end = vec.end();

    for (; it != end; it++)
    {
        if ((*it) == str)
            return true;
    }

    return false;
}
