#include <exec/exec.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <utility/hooks.h>

#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/muimaster.h>

#include "WinMaster.h"
#include "/messages.h"

#include <string.h>
#include <stdio.h>

typedef struct WinList {
    struct WinList *wl_Next;
    struct Window  *wl_Win;
    UBYTE           wl_Name[48];
    BOOL            wl_Selected;
} WINLIST;
WINLIST *wl_head;

VOID __saveds PopWin(VOID);
VOID DoIDCMP(VOID);
VOID wTileVert(VOID);
VOID wTileHorz(VOID);
VOID wMaxWin(VOID);
VOID wMinWin(VOID);
VOID wMove(UBYTE where);
VOID BuildWinList(VOID);
int MarkSelected(ULONG flag);
VOID ShowList(VOID);
VOID FreeList(VOID);
BOOL WinExists(struct Window *win);
LONG __saveds __asm DispFunc(register __a1 WINLIST *,
                             register __a2 char **);

struct MsgPort *other_port;
struct Library *MUIMasterBase;
struct ObjApp *ApOb;
extern struct Screen *scn;
extern WIN_INFO smwin;

VOID __saveds PopWin(VOID)
{
    struct Hook dhook = {{NULL,NULL}, (VOID *)DispFunc, NULL, NULL};

    if (!(MUIMasterBase = OpenLibrary("muimaster.library",0))) {
        fprintf(stderr, "Could not open muimaster.library.\n");
        return;
    }
    
    if (NULL == (other_port = CreatePort(0,0))) {
        fprintf(stderr, "Could not create message port\n");
        return;
    }

    BuildWinList();

    if (NULL == (ApOb = CreateApp())) {
        fprintf(stderr, "Could not create mui application.\n");
        CloseLibrary(MUIMasterBase);        
        DeletePort(other_port);
        other_port = NULL;
        return;
    }

    set(ApOb->WinLV, MUIA_List_DisplayHook, &dhook);
    ShowList();

    DoIDCMP();

    DisposeApp(ApOb);
    DeletePort(other_port);
    other_port = NULL;
    FreeList();
    CloseLibrary(MUIMasterBase);
}

VOID DoIDCMP(VOID)
{
    ULONG mui_sigs, port_sigs, sigs, id;
    BOOL done = FALSE;
    struct Message *msg;

    DoMethod(ApOb->App, MUIM_Application_Input, &mui_sigs);
    port_sigs = 1L << other_port->mp_SigBit;

    while (!done) {
        sigs = Wait(mui_sigs | port_sigs);
        if (sigs & mui_sigs) {
            switch (id = DoMethod(ApOb->App, MUIM_Application_Input, &sigs)) {
                case ID_VERT :
                    wTileVert();
                    done = TRUE;
                    break;
                case ID_HORZ :
                    wTileHorz();
                    done = TRUE;
                    break;
                
                case ID_MAX:
                    wMaxWin();
                    done = TRUE;
                    break;
                case ID_MIN:
                    wMinWin();
                    done = TRUE;
                    break;

                case ID_FRONT:
                    wMove(ID_FRONT);
                    done = TRUE;
                    break;
                case ID_BACK:
                    wMove(ID_BACK);
                    done = TRUE;
                    break;

                case MUIV_Application_ReturnID_Quit :
                    done = TRUE;
                    break;
            }
        }
        else {
            msg = GetMsg(other_port);
            ReplyMsg(msg);
            done = TRUE;
        }
    }
}

VOID wTileVert(VOID)
{
    int num, new_width, new_height, extra_space, w, x, y, nw, nh;
    WINLIST *work = wl_head;
    struct Window *last = NULL;

    if (0 == (num = MarkSelected(WFLG_SIZEGADGET))) return;

    if ((smwin.wi_Window->TopEdge + smwin.wi_Window->Height) == scn->Height)
        new_height = smwin.wi_Window->TopEdge;
    else
        new_height = scn->Height;
    new_height -= scn->BarHeight + 1;

    x = 0;
    y = scn->BarHeight + 1;

    new_width = scn->Width / num;
    extra_space = scn->Width % num;

    Forbid();
    while (work) {
        if (work->wl_Selected) {
            if (WinExists(work->wl_Win)) {
                last = work->wl_Win;
                WindowToFront(last);
                w = extra_space ? new_width + 1 : new_width;
                nw = (last->MaxWidth < w) ? last->MaxWidth : w;
                nh = (last->MaxHeight < new_height) ? last->MaxHeight : new_height;
                ChangeWindowBox(last, x, y, nw, nh);
                if (extra_space) extra_space--;
                x += w;
            }
        }
        work = work->wl_Next;
    }
    Permit();

    if (last) ActivateWindow(last);
}

VOID wTileHorz(VOID)
{
    int num, new_height, extra_space, h, y, nw, nh;
    WINLIST *work = wl_head;
    struct Window *last = NULL;

    if (0 == (num = MarkSelected(WFLG_SIZEGADGET))) return;

    if ((smwin.wi_Window->TopEdge + smwin.wi_Window->Height) == scn->Height)
        new_height = smwin.wi_Window->TopEdge;
    else
        new_height = scn->Height;
    new_height -= scn->BarHeight + 1;

    new_height /= num;
    extra_space = new_height % num;

    y = scn->BarHeight + 1;

    Forbid();
    while (work) {
        if (work->wl_Selected) {
            if (WinExists(work->wl_Win)) {
                last = work->wl_Win;
                WindowToFront(last);
                h = extra_space ? new_height + 1 : new_height;
                nw = (last->MaxWidth < scn->Width) ? last->MaxWidth : scn->Width;
                nh = (last->MaxHeight < h) ? last->MaxHeight : h;
                ChangeWindowBox(last, 0, y, nw, nh);
                if (extra_space) extra_space--;
                y += h;
            }
        }
        work = work->wl_Next;
    }
    Permit();

    if (last) ActivateWindow(last);
}

VOID wMaxWin(VOID)
{
    int new_height, y, nw, nh;
    WINLIST *work = wl_head;
    struct Window *last = NULL;

    if (0 == MarkSelected(WFLG_SIZEGADGET)) return;

    if ((smwin.wi_Window->TopEdge + smwin.wi_Window->Height) == scn->Height)
        new_height = smwin.wi_Window->TopEdge;
    else
        new_height = scn->Height;
    new_height -= scn->BarHeight + 1;

    y = scn->BarHeight + 1;

    Forbid();
    while (work) {
        if (work->wl_Selected) {
            if (WinExists(work->wl_Win)) {
                last = work->wl_Win;
                WindowToFront(last);
                nw = (last->MaxWidth < scn->Width) ? last->MaxWidth : scn->Width;
                nh = (last->MaxHeight < new_height) ? last->MaxHeight : new_height;
                ChangeWindowBox(last, 0, y, nw, nh);
            }
        }
        work = work->wl_Next;
    }
    Permit();

    if (last) ActivateWindow(last);
}

VOID wMinWin(VOID)
{
    WINLIST *work = wl_head;

    if (0 == MarkSelected(WFLG_SIZEGADGET)) return;

    Forbid();
    while (work) {
        if (work->wl_Selected) {
            if (WinExists(work->wl_Win)) {
                WindowToBack(work->wl_Win);
                ChangeWindowBox(work->wl_Win, work->wl_Win->LeftEdge, work->wl_Win->TopEdge,
                                work->wl_Win->MinWidth, work->wl_Win->MinHeight);
            }
        }
        work = work->wl_Next;
    }
    Permit();
}

VOID wMove(UBYTE where)
{
    WINLIST *work = wl_head;
    struct Window *last = NULL;

    MarkSelected(~0);
    
    Forbid();
    while (work) {
        if (work->wl_Selected) {
            if (WinExists(work->wl_Win)) {
                if (ID_BACK == where)
                    WindowToBack(work->wl_Win);
                else {
                    last = work->wl_Win;
                    WindowToFront(last);
                }
            }
        }
        work = work->wl_Next;
    }
    Permit();

    if (last) ActivateWindow(last);
}

VOID BuildWinList(VOID)
{
    WINLIST *work, *prev = NULL;
    struct Window *win;
    LONG ibase;
    
    ibase = LockIBase(0);
    
    win = scn->FirstWindow;
    while (win) {
        if (!(win->Flags & WFLG_BORDERLESS) && !(win->Flags & WFLG_BACKDROP)) {
            if (work = AllocMem(sizeof(WINLIST), MEMF_CLEAR)) {
                work->wl_Win = win;
                if (win->Title)
                    strncpy(work->wl_Name, win->Title, 47);
                else
                    strcpy(work->wl_Name, "<<UNKNOWN>>");
                if (NULL == wl_head)
                    wl_head = work;
                else
                    prev->wl_Next = work;
                prev = work;
            }
        }
        win = win->NextWindow;
    }
    UnlockIBase(ibase);
}

int MarkSelected(ULONG flag)
{
    WINLIST *work;
    ULONG id = MUIV_List_NextSelected_Start, total = 0;
    
    Forbid();
    while (1) {
        DoMethod(ApOb->WinLV, MUIM_List_NextSelected, &id);
        if (MUIV_List_NextSelected_End == id) break;
        
        DoMethod(ApOb->WinLV, MUIM_List_GetEntry, id, &work);

        if (WinExists(work->wl_Win)) {
            if (work->wl_Win->Flags & flag) {
                work->wl_Selected = TRUE;
                total++;
            }
        }
    }
    Permit();

    return((int)total);
}

VOID ShowList(VOID)
{
    WINLIST *work = wl_head;
    
    while (work) {
        DoMethod(ApOb->WinLV, MUIM_List_InsertSingle, work, MUIV_List_Insert_Bottom);
        work = work->wl_Next;
    }
}

VOID FreeList(VOID)
{
    WINLIST *work = wl_head, *next;
    
    while (work) {
        next = work->wl_Next;
        FreeMem(work, sizeof(WINLIST));
        work = next;
    }
    wl_head = NULL;
}

BOOL WinExists(struct Window *win)
{
    struct Window *temp = scn->FirstWindow;
    
    while (temp) {
        if (temp == win) return(TRUE);
        temp = temp->NextWindow;
    }
    return(FALSE);
}


LONG __saveds __asm DispFunc(register __a1 WINLIST *wl,
                             register __a2 char **ar)
{
    *ar = wl->wl_Name;
    return(0);
}