/* 
CCIDE
Copyright 2001-2011 David Lindauer.

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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

You may contact the author at:
	mailto::camille@bluegrass.net
 */
#include <windows.h>
#include <commctrl.h>
#include <commdlg.h>
#include <richedit.h>
#include <stdio.h>

#include "header.h"
#include "winconst.h"
extern HANDLE children[MAX_CHILDREN];
extern int numberofdrawwindows;
extern HWND hwndFrame;
extern HINSTANCE hInstance;
extern enum DebugState uState;

HWND hwndBookmark;

static char bpModule[256];
static int bpLine;

static LOGFONT fontdata = 
{
    15, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE, ANSI_CHARSET,
        OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE, 
        "Arial"
};

DWINFO currentBM;

struct tagfile *tagFileList,  *last_module;

int oldline(struct tagfile *, int, int);

int IsTagged(char *module, int line)
{
    struct tagfile *l = tagFileList;
    if (last_module && !stricmp(module, last_module->name))
    {
        l = last_module;
        goto join;
    } 
    while (l)
    {
        if (!stricmp(l->name, module))
        {
            int i;
            last_module = l;
            join: for (i = 0; i < TAG_MAX; i++)
            {
                struct tag *t = l->tagArray[i];
                while (t)
                {
					int tl = t->drawnLineno;
					if (uState != notDebugging)
						tl = GetBreakpointNearestLine(module, tl, TRUE);
                    if (line == tl)
                        if (t->enabled)
                            if (t->enabled &TAGF_GRAYED)
                                return TAG_BPGRAYED;
                            else
                                return i;
                            else
                                break;
                    t = t->next;
                } 
            }
            return  - 1;
        }
        l = l->next;
    }
    return  - 1;
}

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

int Tag(int type, char *name, int drawnLineno, int charpos, void *extra, int
    freeextra, int alwaysadd)
{
    struct tagfile **l = &tagFileList;
    struct tag *t,  **oldt;
    while (*l)
    {
        if (!stricmp((*l)->name, name))
            break;
        l = &(*l)->next;
    } if (! *l)
    {
        *l = calloc(sizeof(struct tagfile), 1);
        if (! *l)
            return FALSE ;
        strcpy((*l)->name, name);
    }
    t = (*l)->tagArray[type];
    oldt = &(*l)->tagArray[type];
    while (t)
    {
		int tl = t->drawnLineno;
		if (type == TAG_BP)
			tl = GetBreakpointNearestLine(name, tl, TRUE);
        if (drawnLineno == tl)
        {
            if (alwaysadd)
            {
                t->enabled = TAGF_ENABLED;
                if (!t->enabled && type == TAG_BP)
				{
					int tl = GetBreakpointNearestLine((*l)->name, t->debugLineno, TRUE);
                    if (!dbgSetBreakPoint(name, tl, extra))
                        t->enabled |= TAGF_GRAYED;
				}
                InvalidateByName((*l)->name);
                return TRUE;
            } 

            if (t->enabled && type == TAG_BP && (!t->extra || freeextra) ||
                type != TAG_BP)
            {
                *oldt = t->next;
                if (type == TAG_BP)
				{
					int tl = GetBreakpointNearestLine((*l)->name, t->debugLineno, TRUE);
                    dbgClearBreakPoint((*l)->name, tl);
				}
                free(t->extra);
                free(t);
                InvalidateByName((*l)->name);
            }
            else
            {
                t->enabled = !t->enabled;
                if (t->enabled)
                {
					int tl = GetBreakpointNearestLine((*l)->name, t->debugLineno, TRUE);
                    if (!dbgSetBreakPoint((*l)->name, tl, t->extra))
                        t->enabled |= TAGF_GRAYED;
                }
                else
				{
					int tl = GetBreakpointNearestLine((*l)->name, t->debugLineno, TRUE);
                    dbgClearBreakPoint((*l)->name, tl);
				}
                InvalidateByName((*l)->name);
                return t->enabled;
            }
            return FALSE;

        }
        if (drawnLineno < t->drawnLineno)
            break;
        oldt = &t->next;
        t = t->next;
    }
    t = calloc(sizeof(struct tag), 1);
    if (!t)
        return FALSE ;
    t->editingLineno = t->drawnLineno = drawnLineno;
    t->debugLineno = oldline(l, drawnLineno, FALSE);
    t->charpos = charpos;
    t->extra = extra;
    t->next =  *oldt;
    t->enabled = TAGF_ENABLED;
    *oldt = t;
    if (type == TAG_BP)
	{
		int tl = GetBreakpointNearestLine(name, t->debugLineno, TRUE);
        if (!dbgSetBreakPoint(name, tl, extra))
            t->enabled |= TAGF_GRAYED;
	}
    InvalidateByName((*l)->name);
    return TRUE;
}

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

void TagRemoveAll(int type)
{
    struct tagfile *l = &tagFileList;
    struct tag *t;
    while (l)
    {
        t = l->tagArray[type];
        l->tagArray[type] = NULL;
        while (t)
        {
            struct tag *nt = t->next;
            if (type == TAG_BP)
			{
				int tl = GetBreakpointNearestLine(l->name, t->debugLineno, TRUE);
                dbgClearBreakPoint(l->name, tl);
			}
            free(t->extra);
            free(t);
            t = nt;
        } InvalidateByName(l->name);
        l = l->next;
    }
}
int TagAnyBreakpoints(void)
{
    struct tagfile *l = tagFileList;
	while (l)
	{
		if (l->tagArray[TAG_BP])
			return TRUE;
		l = l->next;
	}
	return FALSE;
}
// used after BP are restored from data file
void TagRegenBreakPoints(void)
{
    struct tagfile *l = tagFileList;
    struct tag *t;
    int flag = FALSE;
    while (l)
    {
        t = l->tagArray[TAG_BP];
        while (t)
        {
            if (t->enabled == TAGF_ENABLED)
			{
				int tl;
				tl = GetBreakpointNearestLine(l->name, t->debugLineno, TRUE);
	            if (!dbgSetBreakPoint(l->name,tl, t->extra))
	            {
	                flag = TRUE;
	                t->enabled |= TAGF_GRAYED;
	                InvalidateByName(l->name);
	            } 
			}
			t = t->next;
        }
        l = l->next;
    }
    if (flag)
        ExtendedMessageBox("Breakpoints", 0, 
            "One or more breakpoints were not positioned on valid lines and were disabled");
}

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

static int oldline(struct tagfile *l, int lineno, int allowmiddle)
{
    struct tagchangeln *c = l->changedLines;
    while (c && lineno >= c->lineno)
    {
        if (lineno < c->lineno + c->delta)
        {
            if (allowmiddle)
                return c->lineno;
            else
                return 0;
        } 
		lineno -= c->delta;
        c = c->next;
    }
    return lineno;
}

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

int TagOldLine(char *name, int lineno)
{
    struct tagfile *l = tagFileList;
    int rv = lineno;
    while (l)
    {
        if (!stricmp(l->name, name))
        {
            return oldline(l, lineno, FALSE);
        } l = l->next;
    }
    return lineno;
}

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

static int newline(struct tagfile *l, int line)
{
    int delta = 0;
    struct tagchangeln *c = l->changedLines;
    while (c && line >= c->lineno)
    {
        delta += c->delta;
        c = c->next;
    } return delta + line;
}

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

int TagNewLine(char *name, int lineno)
{
    struct tagfile *l = tagFileList;
    int rv = lineno;
    while (l)
    {
        if (!stricmp(l->name, name))
        {
            return newline(l, lineno);
        } l = l->next;
    }
    return lineno;
}

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

void TagRemoveOld(struct tagfile *l)
{
    struct tagchangeln *c = l->changedLines;
    while (c)
    {
        struct tagchangeln *x = c->next;
        free(c);
        c = x;
    } l->changedLines = 0;
}

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

static void TagInsertChange(struct tagfile *l, int lineno, int delta)
{
    struct tagchangeln **c = &l->changedLines,  *newone,  **insert = 0;
    int found = FALSE;
    lineno = oldline(l, lineno, TRUE);
    while (*c)
    {
        if ((*c)->lineno >= lineno)
        {
            if ((*c)->lineno == lineno)
                found = TRUE;
            else
                if (!insert)
                    insert = c;
            (*c)->delta += delta;
        } c = &(*c)->next;
    }
    if (!found)
    {
        if (!insert)
            insert = c;
        newone = calloc(1, sizeof(struct tagchangeln));
        if (!newone)
            return ;
        newone->next =  *insert;
        newone->lineno = lineno;
        newone->delta = delta;
        (*insert) = newone; // relies on link being first field
    }
}

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

void TagLineChange(char *name, int drawnLineno, int delta)
{
    struct tagfile *l = tagFileList;

    int i;
    while (l)
    {
        if (!stricmp(l->name, name))
        {
            TagInsertChange(l, drawnLineno, delta);
            for (i = 0; i < TAG_MAX; i++)
            {
                struct tag *t = l->tagArray[i];
                while (t)
                {
                    if (t->drawnLineno >= drawnLineno)
                        t->drawnLineno += delta;
                    t = t->next;
                } 
            }
            break;
        }
        l = l->next;
    }
    if (!l)
    {
        l = calloc(sizeof(struct tagfile), 1);
        if (l)
        {
            struct tagfile **ls = &tagFileList;
            while (*ls)
                ls = &(*ls)->next;
            *ls = l;
            strcpy(l->name, name);
            TagInsertChange(l, drawnLineno, delta);
        } 
    }
	ccLineChange(name, drawnLineno, delta);
}

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

void TagLinesAdjust(char *name, int mode)
{
    struct tagfile *l = tagFileList;
    int i;
    while (l)
    {
        if (!name || !stricmp(l->name, name))
        {
            if (mode == TAGM_DISCARDFILE || mode == TAGM_UPDATEDEBUG)
                TagRemoveOld(l);
            for (i = 0; i < TAG_MAX; i++)
            {
                struct tag *t = l->tagArray[i];
                while (t)
                {
                    switch (mode)
                    {
                        case TAGM_SAVEFILE:
                            t->editingLineno = t->drawnLineno;
                            break;
                        case TAGM_DISCARDFILE:
                            t->drawnLineno = t->editingLineno;
                            break;
                        case TAGM_UPDATEDEBUG:
                            t->debugLineno = t->drawnLineno;
                            break;
                    }
                    t = t->next;
                }
            }
        }
        l = l->next;
    }
}

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

void TagBreakpoint(char *module, int line)
{
    if (module)
    {
        strcpy(bpModule, module);
    }
    else
    {
        bpModule[0] = 0;
    }
    bpLine = line;
}

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

int BPLine(char *module)
{
    if (bpModule[0] && !stricmp(bpModule, module))
        return TagNewLine(module, bpLine);
    return 0;
}

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

void TagEraseAllEntries(void)
{
    int i;
    struct tagfile *l = tagFileList;
    for (i = 0; i < TAG_MAX; i++)
        TagRemoveAll(i);
    while (l)
    {
        struct tagfile *s = l->next;
        TagRemoveOld(l);
        free(l);
        l = s;
    } 
	tagFileList = 0;
	last_module = 0;
}

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

void ToggleBookMark(int linenum)
{
    HWND hWnd = GetFocus();
    int i;
    for (i = 0; i < numberofdrawwindows; i++)
    {
        DWINFO *ptr = (DWINFO*)GetWindowLong(children[i], 0);
        if (ptr->dwHandle == hWnd)
        {
            char buf[256],  *ch;
            int addr;
			if (linenum == -1)
	            SendMessage(ptr->dwHandle, EM_GETSEL, (WPARAM) &linenum, 0);

            linenum = SendMessage(ptr->dwHandle, EM_LINEFROMCHAR, linenum, 0) +
                1;
            memset(buf, 0, 256);
            *(short*)buf = 256;

            SendMessage(ptr->dwHandle, EM_GETLINE, (WPARAM)linenum - 1, (LPARAM)
                buf);
            if (Tag(TAG_BOOKMARK, ptr->dwName, linenum, 0, ch = strdup(buf), 0,
                0))
            {
                strcpy(currentBM.dwName, ptr->dwName);
                currentBM.dwLineNo = linenum;
            }
            else
                free(ch);
            break;
        }
    }

}

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

int findbmpos(struct tagfile **l, struct tag **t)
{
    struct tagfile *il;
    (*l) = tagFileList;
    if (currentBM.dwName[0])
    {
        while ((*l))
        {
            if (!stricmp(currentBM.dwName, (*l)->name))
                break;
            (*l) = (*l)->next;
        } if (!(*l))
            (*l) = tagFileList;
    }
    if (!(*l))
        return 0;
    il = (*l);

    do
    {
        (*t) = (*l)->tagArray[TAG_BOOKMARK];
        while ((*t))
        {
            if ((*t)->drawnLineno >= currentBM.dwLineNo || (*l) != il)return 1;
            (*t)
                 = (*t)->next;
        }
        (*l) = (*l)->next;
        if (!(*l))
            (*l) = tagFileList;
        (*t) = (*l)->tagArray[TAG_BOOKMARK];
    }
    while ((*l) != il)
        ;
    return 0;
}

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

void NextBookMark(void)
{
    struct tagfile *l;
    struct tag *t,  *st;
    int sln;
    if (!findbmpos(&l, &t))
        return ;
    st = t;
    t = t->next;
    do
    {
        if (t)
        {
            strcpy(currentBM.dwName, l->name);
            currentBM.dwLineNo = t->drawnLineno;
            currentBM.logMRU = FALSE;
			currentBM.newFile = FALSE;
            CreateDrawWindow(&currentBM, TRUE);
            return ;
        } 
        else
        {
            l = l->next;
            if (!l)
                l = tagFileList;
            t = l->tagArray[TAG_BOOKMARK];
        }
    }
    while (t != st);

}

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

void PreviousBookMark(void)
{
    DWINFO temp;
    struct tagfile *l,  *fl;
    struct tag *t,  *ft;
    int sln, gotone = FALSE;
    if (!findbmpos(&l, &t))
        return ;
    fl = tagFileList;
    while (fl)
    {
        ft = fl->tagArray[TAG_BOOKMARK];
        strcpy(temp.dwName, fl->name);
        temp.logMRU = FALSE;
		temp.newFile = FALSE;
        while (ft)
        {
            if (fl == l)
            {
                if (ft->drawnLineno == t->drawnLineno && gotone)
                {
                    currentBM = temp;
                    CreateDrawWindow(&currentBM, TRUE);
                    return ;
                } 
                else if (ft->drawnLineno >= t->drawnLineno)
                {
                    while (fl)
                    {
                        strcpy(temp.dwName, fl->name);
                        while (ft)
                        {
                            temp.dwLineNo = ft->drawnLineno;
                            ft = ft->next;
                        }
                        fl = fl->next;
                        if (fl)
                            ft = fl->tagArray[TAG_BOOKMARK];
                    }
                    currentBM = temp;
                    CreateDrawWindow(&currentBM, TRUE);
                    return ;
                }
            }
            temp.dwLineNo = ft->drawnLineno;
            gotone = TRUE;
            ft = ft->next;
        }
        fl = fl->next;
    }
}

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

LRESULT CALLBACK _export BMProc(HWND hwnd, UINT iMessage, WPARAM wParam, LPARAM
    lParam)
{
    static HFONT font;
    LPMEASUREITEMSTRUCT mi;
    LPDRAWITEMSTRUCT di;
    LV_COLUMN lvC;
    LV_ITEM item;
    RECT r;
    struct tagfile *l = &tagFileList;
    struct tag *t,  *t1;
    HWND hwndlb;
    int i;
    switch (iMessage)
    {
        case WM_NOTIFY:
            if (wParam == IDC_BMLISTBOX)
            {
                if (((LPNMHDR)lParam)->code == NM_DBLCLK)
                {
                    SendMessage(hwnd, WM_COMMAND, IDC_BMGOTO, 0);
                }
            }
            break;
        case WM_COMMAND:
            if (wParam == IDCANCEL)
            {
                DeleteObject(font);
                DestroyWindow(hwnd);
                hwndBookmark = 0;
            }
            else if (wParam == IDC_BMGOTO)
            {
                int sel = ListView_GetSelectionMark(GetDlgItem(hwnd,
                    IDC_BMLISTBOX));
                if (sel ==  - 1)
                {
                    DeleteObject(font);
                    EndDialog(hwnd, 1);
                    break;
                }
                item.iItem = sel;
                item.iSubItem = 0;
                item.mask = LVIF_PARAM;
                ListView_GetItem(GetDlgItem(hwnd, IDC_BMLISTBOX), &item);
                t1 = (struct tag*)item.lParam;
                while (l)
                {
                    t = l->tagArray[TAG_BOOKMARK];
                    while (t && t != t1)
                        t = t->next;
                    if (t)
                    {
                        strcpy(currentBM.dwName, l->name);
                        currentBM.dwLineNo = t->drawnLineno;
                        currentBM.logMRU = FALSE;
						currentBM.newFile = FALSE;
                        CreateDrawWindow(&currentBM, TRUE);
                        break;
                    }
                    l = l->next;
                }
                DeleteObject(font);
                EndDialog(hwnd, 1);
            }
            break;
        case WM_MEASUREITEM:
            mi = (LPMEASUREITEMSTRUCT)lParam;
            hwndlb = GetDlgItem(hwnd, IDC_BMLISTBOX);
            GetClientRect(hwndlb, &r);
            mi->itemWidth = r.right;
            mi->itemHeight = 14;
            break;
        case WM_INITDIALOG:
            CenterWindow(hwnd);
            font = CreateFontIndirect(&fontdata);
            hwndlb = GetDlgItem(hwnd, IDC_BMLISTBOX);
            SendMessage(hwndlb, WM_SETFONT, (WPARAM)font, 0);
            GetWindowRect(hwndlb, &r);
            lvC.mask = LVCF_WIDTH | LVCF_SUBITEM;
            lvC.cx = r.right - r.left;
            lvC.iSubItem = 0;
            ListView_InsertColumn(hwndlb, 0, &lvC);
            i = 0;
            while (l)
            {
                t = l->tagArray[TAG_BOOKMARK];
                while (t)
                {
                    char buf[256];
                    sprintf(buf, "%s(%d): %s", l->name, t->drawnLineno, t
                        ->extra);
                    item.iItem = i++;
                    item.iSubItem = 0;
                    item.mask = LVIF_PARAM | LVIF_TEXT;
                    item.lParam = (LPARAM)t;
                    item.pszText = buf;
                    ListView_InsertItem(hwndlb, &item);
                    t = t->next;
                }
                l = l->next;
            }
            break;
    }
    return 0;
}

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

void ShowBookMarks(void)
{
    hwndBookmark = CreateDialog(hInstance, "BOOKMARKDLG", hwndFrame, (DLGPROC)
        BMProc);
}
