
//  ------------------------------------------------------------------
//  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$
//  ------------------------------------------------------------------
//  Advanced search functions.
//  ------------------------------------------------------------------

#include <golded.h>
#include <gmnubase.h>
#include <geval.h>
#include <gesrch.h>


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

golded_search_manager::golded_search_manager()
{

}


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

golded_search_manager::~golded_search_manager()
{

}


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

inline void search_item_option(bool& a, const char* s)
{

    if(s[1] == '^')
        a = not a;
    else
        a = s[1] != '\'';
}


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

const char* search_item_set(search_item& item, const char* s, int what)
{
    bool flag = false;

    while(*s)
    {
        switch(*s)
        {
        case ')':
            search_item_option(item.where.from, s);
            search_item_option(item.where.to, s);
            search_item_option(item.where.subject, s);
            flag = true;
            break;
        case '(':
            search_item_option(item.where.body, s);
            search_item_option(item.where.tagline, s);
            search_item_option(item.where.tearline, s);
            search_item_option(item.where.origin, s);
            search_item_option(item.where.signature, s);
            search_item_option(item.where.kludges, s);
            flag = true;
            break;
        case '!':
            search_item_option(item.reverse, s);
            break;
        case '=':
            search_item_option(item.case_sensitive, s);
            break;
        case '<':
            search_item_option(item.where.from, s);
            flag = true;
            break;
        case '>':
            search_item_option(item.where.to, s);
            flag = true;
            break;
        case ':':
            search_item_option(item.where.subject, s);
            flag = true;
            break;
        case '#':
            search_item_option(item.where.body, s);
            flag = true;
            break;
        case '.':
            search_item_option(item.where.tagline, s);
            flag = true;
            break;
        case '_':
            search_item_option(item.where.tearline, s);
            flag = true;
            break;
        case '*':
            search_item_option(item.where.origin, s);
            flag = true;
            break;
        case '@':
            search_item_option(item.where.signature, s);
            flag = true;
            break;
        case '%':
            search_item_option(item.where.kludges, s);
            flag = true;
            break;
        case '?':
            s++;
            switch(g_tolower(*s))
            {
            case 'r':
                item.type = gsearch::regex;
                break;
            case 'w':
                item.type = gsearch::wildcard;
                break;
            case 'p':
                item.type = gsearch::plain;
                break;
            case 'f':
                item.type = gsearch::fuzzy;
                if(isdigit(s[1]))
                {
                    item.fuzzydegree = atoi(s+1);
                    while(isdigit(s[1]))
                        s++;
                }
                break;
            }
            break;
        case '^':
        case '\'':
            break;
        default:
            if (isspace(*s)) break;
            else
                goto lblLoopExit;
        }
        s++;
    }

lblLoopExit:
    if (what && !flag)
    {
        if (what & GFIND_FROM)      item.where.from      = true;
        if (what & GFIND_TO)        item.where.to        = true;
        if (what & GFIND_SUBJECT)   item.where.subject   = true;
        if (what & GFIND_BODY)      item.where.body      = true;
        if (what & GFIND_TAGLINE)   item.where.tagline   = true;
        if (what & GFIND_TEARLINE)  item.where.tearline  = true;
        if (what & GFIND_ORIGIN)    item.where.origin    = true;
        if (what & GFIND_KLUDGES)   item.where.kludges   = true;
        if (what & GFIND_SIGNATURE) item.where.signature = true;
    }

    return s;
}


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

void golded_search_manager::prepare_from_string(const char* prompt, int what)
{
    // Get defaults
    reverse = false;
    direction = DIR_NEXT;

    const char* p = prompt;

    if (*p == '-' or *p == '+')
    {
        direction = (*p == '-') ? DIR_PREV : DIR_NEXT;
        p++;
    }

    search_item default_item;
    p = search_item_set(default_item, p, what);

    search_item item = default_item;

    default_item.reverse         = false;
    default_item.where.from      = false;
    default_item.where.to        = false;
    default_item.where.subject   = false;
    default_item.where.body      = false;
    default_item.where.tagline   = false;
    default_item.where.tearline  = false;
    default_item.where.origin    = false;
    default_item.where.kludges   = false;
    default_item.where.signature = false;

    char buf[256];
    char* b = buf;
    bool item_complete = false;

    do
    {

        switch(*p)
        {

        // Logic AND
        case '&':
            item.logic = search_item::logic_and;
            item_complete = true;
            p++;
            break;

        // Logic OR
        case '|':
            item.logic = search_item::logic_or;
            item_complete = true;
            p++;
            break;

        // Get quoted string
        case '\"':
        case '\'':
        {
            char q = *p++;
            while(*p)
            {
                if(*p == q)
                {
                    p++;
                    break;
                }
                switch(*p)
                {
                case '\\':
                    if(*(++p))
                        *b++ = *p++;
                    break;
                default:
                    *b++ = *p++;
                }
            }
        }
        break;

        // Get literal escaped character
        case '\\':
            if(*(++p))
                *b++ = *p++;
            break;

        // Skip whitespace
        case ' ':
            p++;

        case NUL:
            break;

        default:
            *b++ = *p++;
        }

        if(item_complete or (*p == NUL))
        {
            item_complete = false;
            *b = NUL;
            if(*buf != NUL)
            {
                item.pattern = buf;
                items.push_back(item);
            }
            if(*p == NUL)
                break;

            item = default_item;
            p = search_item_set(item, strskip_wht(p), what);
            b = buf;
        }

    }
    while(*p);
}


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

bool golded_search_manager::search(GMsg* msg, bool quick, bool shortcircuit)
{

    std::vector<search_item>::iterator item = items.begin();
    bool exit = false;
    bool and_cycle = false;

    for(int i=0; i<items.size(); i++, item++)
    {

        if(shortcircuit)
        {
            if(item->logic == search_item::logic_and)
            {
                if(not and_cycle)
                {
                    if(exit)
                        return true;
                }
                else if(not exit)
                    continue;
                and_cycle = true;
            }
            else
            {
                if(and_cycle)
                {
                    and_cycle = (item->logic == search_item::logic_and);
                    if(not exit)
                        continue;
                }
                else
                {
                    and_cycle = (item->logic == search_item::logic_and);
                    if(exit)
                        return true;
                }
            }
        }

        int found = 0;
        if(item->where.from)
        {
            if(item->search(*msg->ifrom ? msg->ifrom : msg->By()))
            {
                msg->foundwhere |= GFIND_FROM;
                found++;
                if(quick)
                    goto quick_found;
            }
        }
        if(item->where.to)
        {
            if(item->search(*msg->ito ? msg->ito : msg->to))
            {
                msg->foundwhere |= GFIND_TO;
                found++;
                if(quick)
                    goto quick_found;
            }
            else if(*msg->icc and item->search(msg->icc))
            {
                msg->foundwhere |= GFIND_TO;
                found++;
                if(quick)
                    goto quick_found;
            }
            else if(*msg->ibcc and item->search(msg->ibcc))
            {
                msg->foundwhere |= GFIND_TO;
                found++;
                if(quick)
                    goto quick_found;
            }
        }
        if(item->where.subject)
        {
            if(item->search(msg->re))
            {
                msg->foundwhere |= GFIND_SUBJECT;
                found++;
                if(quick)
                    goto quick_found;
            }
        }
        if(item->where.body or item->where.tagline or item->where.tearline or item->where.origin or item->where.signature or item->where.kludges)
        {
            Line* line = msg->lin;
            while(line)
            {
                uint type = line->type;
                bool search_this_line = false;
                if(item->where.body and not (type & (GLINE_TAGL|GLINE_TEAR|GLINE_ORIG|GLINE_SIGN|GLINE_KLUDGE)))
                    search_this_line = true;
                if(item->where.tagline and (type & GLINE_TAGL))
                    search_this_line = true;
                if(item->where.tearline and (type & GLINE_TEAR))
                    search_this_line = true;
                if(item->where.origin and (type & GLINE_ORIG))
                    search_this_line = true;
                if(item->where.signature and (type & GLINE_SIGN))
                    search_this_line = true;
                if(item->where.kludges and (type & GLINE_KLUDGE))
                    search_this_line = true;
                if(search_this_line)
                {
                    if(item->search(line->txt.c_str()))
                    {
                        line->type |= GLINE_HIGH;
                        if(type & (GLINE_TAGL|GLINE_TEAR|GLINE_ORIG|GLINE_SIGN|GLINE_KLUDGE))
                        {
                            if(type & GLINE_TAGL)
                                msg->foundwhere |= GFIND_TAGLINE;
                            else if(type & GLINE_TEAR)
                                msg->foundwhere |= GFIND_TEARLINE;
                            else if(type & GLINE_ORIG)
                                msg->foundwhere |= GFIND_ORIGIN;
                            else if(type & GLINE_SIGN)
                                msg->foundwhere |= GFIND_SIGNATURE;
                            else if(type & GLINE_KLUDGE)
                                msg->foundwhere |= GFIND_KLUDGES;
                        }
                        else
                        {
                            msg->foundwhere |= GFIND_BODY;
                        }
                        found++;
                        if(quick)
                            goto quick_found;
                    }
                }
                line = line->next;
            }
        }

quick_found:

        if(item->reverse)
            found = not found;

        // Perform short-circuit logic analysis
        if(shortcircuit)
        {
            exit = make_bool(found);
            if(item == items.end()-1)
                return exit;
        }
        // else save success/failure as score
        else
        {
            item->score = found ? 1 : 0;
        }
    }

    if(not shortcircuit)
    {
        gevalhum logic;
        if(items.size())
        {
            for(int i=0; i<items.size(); i++)
            {
                logic.push_value(items[i].score);
                if((i+1) < items.size())
                {
                    if(items[i].logic == search_item::logic_and)
                        logic.push_operator(geval::logic_and);
                    else
                        logic.push_operator(geval::logic_or);
                }
            }
            return make_bool(logic.evaluate());
        }
    }

    // It should never end up here unless there are no items
    return false;
}


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

void search_mgr_form::before()
{

    gwinput2::before();
}


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

void search_mgr_form::after()
{

    gwinput2::after();
}


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

void search_mgr_form::select(gstrarray& menu, const char* title)
{

    int srow = window.start_row + current->row;
    int scol = window.start_column + current->column;

    if((current->id < id_direction) and ((current->id % 3) == 2))
        scol -= 20;

    GMnu m;
    m.Init();
    m.SetColor(LGREY_|_BLACK, LGREY_|_BLACK, LGREY_|_BLACK, BLACK_|_LGREY, DGREY_|_BLACK);
    m.SetTitle(title, YELLOW_|_BLACK);
    m.SetBorder(0, LGREY_|_BLACK);
    m.SetPos(srow, scol);
    m.SetMask(0);
    m.Begin();
    for(int i=0; i<menu.size(); i++)
        m.Item(i, menu[i].c_str());
    m.End();

    vcurhide();
    m.Start();
    vcurshow();

    if(m.FinalTag() != -1)
    {
        if(not ((current->id < id_direction) and ((current->id % 3) == 2)))
        {
            strbtrim(strcpy(current->buf, menu[m.FinalTag()].c_str()+1));
            current->update();
        }
    }
}


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

bool search_mgr_form::validate()
{

    gstrarray menu;

    if(current->id >= id_direction)
    {
        switch(current->id)
        {
        case id_direction:
            menu.insert(menu.end(), "F Forward");
            menu.insert(menu.end(), "B Backward ");
            select(menu, "Direction");
            break;
        case id_messages:
            menu.insert(menu.end(), "N New");
            menu.insert(menu.end(), "U Unread ");
            menu.insert(menu.end(), "A All");
            select(menu, "Messages");
            break;
        case id_action:
            menu.insert(menu.end(), "R Read");
            menu.insert(menu.end(), "T Tag");
            menu.insert(menu.end(), "D Delete ");
            menu.insert(menu.end(), "W Write");
            menu.insert(menu.end(), "C Copy");
            menu.insert(menu.end(), "M Move");
            select(menu, "Action");
            break;
        case id_areas:
            menu.insert(menu.end(), "C Current ");
            menu.insert(menu.end(), "A All");
            menu.insert(menu.end(), "T Tagged");
            select(menu, "Areas");
            break;
        }
    }
    else if((current->id % 3) == 1)
    {
        menu.insert(menu.end(), "a and ");
        menu.insert(menu.end(), "o or");
        select(menu, "Logic");
    }
    else if((current->id % 3) == 2)
    {
        menu.insert(menu.end(), " Plain ");
        menu.insert(menu.end(), " Regex ");
        menu.insert(menu.end(), " Wildcard ");
        menu.insert(menu.end(), " Fuzzy ");
        menu.insert(menu.end(), " Success if not found ");
        menu.insert(menu.end(), " Case sensitive ");
        menu.insert(menu.end(), " Search \"from\" ");
        menu.insert(menu.end(), " Search \"to\" ");
        menu.insert(menu.end(), " Search \"subject\" ");
        menu.insert(menu.end(), " Search msg body ");
        menu.insert(menu.end(), " Search tagline ");
        menu.insert(menu.end(), " Search tearline ");
        menu.insert(menu.end(), " Search origin ");
        menu.insert(menu.end(), " Search signature ");
        menu.insert(menu.end(), " Search kludges ");
        select(menu, "Options");
    }

    return true;
}


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

void AdvancedSearch(GMsg*, int&, int&)
{

    int   patterns = 9;
    int   width = 77;
    int   height = patterns+11;
    int   widths[3] = { 55, 5, 7 };
    int   field_widths[3] = { 100, 5, 7 };
    int   border_type   = BT_SINGLE;
    vattr title_color   = YELLOW_|_BLUE;
    vattr heading_color = YELLOW_|_BLUE;
    vattr window_color  = LGREY_ |_BLUE;
    vattr border_color  = LBLUE_ |_BLUE;
    vattr edit_color    = YELLOW_|_BLUE;
    vattr idle_color    = LGREY_ |_BLUE;
    vattr active_color  = WHITE_ |_BLUE;
    vattr shadow_color  = DGREY_ |_BLACK;

    widths[0] = width - 3*2 - 2 - 5 - 7 - 2;

    gwindow window;

    window.openxy(1, ((MAXCOL-width)/2), height, width,
                  border_type, border_color, window_color);
    window.shadow(shadow_color);

    window.horizontal_line( 1, 0, width-2, border_type, border_color);
    window.horizontal_line(patterns+2, 0, width-2, border_type, border_color);

    window.vertical_line(0, widths[0]+2,           patterns+3, border_type, border_color);
    window.vertical_line(0, widths[0]+widths[1]+5, patterns+3, border_type, border_color);

    window.title(" Advanced Search Manager ", title_color);

    window.prints(0, 1,                     heading_color, "Pattern");
    window.prints(0, widths[0]+4,           heading_color, "Logic");
    window.prints(0, widths[0]+widths[1]+7, heading_color, "Options");

    window.prints(patterns+3, 1, heading_color, "Direction   : ");
    window.prints(patterns+4, 1, heading_color, "Messages    : ");
    window.prints(patterns+5, 1, heading_color, "Action      : ");
    window.prints(patterns+6, 1, heading_color, "Areas       : ");
    window.prints(patterns+7, 1, heading_color, "Destination : ");
    window.prints(patterns+8, 1, heading_color, "Identifier  : ");

    search_mgr_form iform(window);

    iform.setup(idle_color, active_color, edit_color, _box_table(border_type, 13), true);

    std::string buffers[9*3 + 6];

    int i = 0;
    for(int r=0; r<9; r++)
    {
        int cs = 1;
        for(int c=0; c<3; c++,i++)
        {
            char z[80];
            sprintf(z, "%i,%i", r+1, c+1);
            buffers[i] = z;
            iform.add_field(i, r+2, cs, widths[c], buffers[i], field_widths[c]);
            cs += widths[c] + 3;
        }
    }

    buffers[i+0] = "Forward";
    buffers[i+1] = "New";
    buffers[i+2] = "Read";
    buffers[i+3] = "Current";
    buffers[i+4] = "C:\\TEMP\\DUMPFILE.TXT";
    buffers[i+5] = "Testing";

    for(int y=0; y<6; y++,i++)
        iform.add_field(100+y, patterns+3+y, 15, width-15-3, buffers[i], width-15-3, gwinput::cvt_none, y>=4 ? gwinput::entry_conditional : gwinput::entry_noedit);

    iform.run(0);

    window.close();
}


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

