#define WINVER 0x0501 

#include <windows.h>

#include <windowsx.h>
#include <strings.h>
#include <stdio.h>
#include <commctrl.h>
#include <winuser.h>

#include "main.h"

#define ID_STATUSBAR       4997
#define ID_TOOLBAR         4998

#define ID_MDI_CLIENT      4999
#define ID_MDI_FIRSTCHILD  50000

#define IDC_CHILD_EDIT      2000
#define MAX_UNDO            10

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK MDIChildWndProc(HWND hwnd, UINT Message, WPARAM wParam,
    LPARAM lParam);
LRESULT CALLBACK MapProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK StatProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam);
void intToString(int num);
BOOL LoadNewTiles(void);
BOOL DoFileOpenSave(BOOL bSave);
BOOL SaveFile(LPSTR pszFileName);
BOOL DoBitmapLoad(char* szFileName);
void CreateSymbol(void);
void PutSymbol(unsigned short);
void do_compress(void);

//unsigned char map_data[256];                  //the map data
int map_index = 0;
unsigned char comp_map_data[320];  //the compressed map data (maximum size)

int comp_index = 0;                     //where we start
int comp_iter = 6;                          //signal to compress iteration
int dComp_counter = 0;
int dict[1024];                         //two ints per dictionary entry
int dict_index = 0;
unsigned short this_code,next_code,temp_code;
typedef struct {
    BOOLEAN changed;
    char tileset[MAX_PATH];
    char filepath[MAX_PATH];
} MAPSTRUCT;

typedef struct {
    int tilenumber;
    unsigned char newtile;
    unsigned char oldtile;
} UNDOSTRUCT;
    

const char g_szAppName[]      = "MyMDIWindow";
const char g_szChild[]        = "MyMDIChild";
const char g_szMap[]          = "MyMap";
const char g_szTileStats[]    = "MyStats";
char ans[5];

HINSTANCE g_hInst;
HWND g_hMDIClient,g_hTiles;
HWND g_hBG;
HWND g_hMainWindow,g_hStatusBar;
HWND statwin;
BOOL tile_pending = FALSE;
char tilesPath[MAX_PATH];
MAPSTRUCT current_map;
unsigned char map_data[256];
UNDOSTRUCT undo_array[MAX_UNDO];
int undoPos = 0;
BOOL menu_state = FALSE;
BITMAP** bmpTiles;
HDC tiles;
int sourcePos = 0xFFFFFFFF;
float comp_ratio = 0;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
   LPSTR lpszCmdParam, int nCmdShow)
{
    MSG  Msg;
    WNDCLASSEX WndClassEx;
    HACCEL haccel;

    InitCommonControls();
    
    g_hInst = hInstance;
    ZeroMemory(map_data,256);
    ZeroMemory(undo_array,sizeof(UNDOSTRUCT)*MAX_UNDO);

    WndClassEx.cbSize          = sizeof(WNDCLASSEX);
    WndClassEx.style           = 0;
    WndClassEx.lpfnWndProc     = WndProc;
    WndClassEx.cbClsExtra      = 0;
    WndClassEx.cbWndExtra      = 0;
    WndClassEx.hInstance       = hInstance;
    WndClassEx.hIcon           = LoadIcon(NULL, IDI_APPLICATION);
    WndClassEx.hCursor         = LoadCursor(NULL, IDC_ARROW);
    WndClassEx.hbrBackground   = (HBRUSH)(COLOR_3DSHADOW+1);
    WndClassEx.lpszMenuName    = "MAIN";
    WndClassEx.lpszClassName   = g_szAppName;
    WndClassEx.hIconSm           = LoadIcon(NULL, IDI_APPLICATION);

    RegisterClassEx(&WndClassEx);

    WndClassEx.lpfnWndProc     = MDIChildWndProc;
    WndClassEx.lpszMenuName       = NULL;
    WndClassEx.lpszClassName   = g_szChild;
    WndClassEx.hbrBackground   = (HBRUSH)(COLOR_3DFACE+1);

    RegisterClassEx(&WndClassEx);

    WndClassEx.lpfnWndProc  = MapProc;
    WndClassEx.lpszClassName    = g_szMap;
    
    RegisterClassEx(&WndClassEx);
    
    WndClassEx.hbrBackground    = (HBRUSH) (COLOR_MENU+1); 
    WndClassEx.lpfnWndProc      = StatProc;
    WndClassEx.lpszClassName    = g_szTileStats;
    
    RegisterClassEx(&WndClassEx);

    g_hMainWindow = CreateWindowEx(WS_EX_APPWINDOW, g_szAppName,
        "Zelda Map Editor", WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
        CW_USEDEFAULT, CW_USEDEFAULT, 800, 650,
        0, 0, hInstance, NULL);

    ShowWindow(g_hMainWindow, nCmdShow);
    UpdateWindow(g_hMainWindow);

    ZeroMemory(&current_map,sizeof(MAPSTRUCT));
    current_map.changed = FALSE;
    ZeroMemory(tilesPath,MAX_PATH);
    haccel = LoadAccelerators(hInstance,"FileAccel");
    
    while(GetMessage(&Msg, NULL, 0, 0)) {
        if(!TranslateAccelerator( g_hMainWindow,haccel,&Msg)) {
            TranslateMessage(&Msg);
            DispatchMessage(&Msg);
            if (current_map.changed && !menu_state) {
                EnableMenuItem(GetMenu(g_hMainWindow),CM_FILE_SAVE,MF_BYCOMMAND | MF_ENABLED);
                menu_state=TRUE;
            }
        }       
    }
    return Msg.wParam;
}


LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch(Message)
    {
        case WM_CREATE:
        {
            CLIENTCREATESTRUCT ccs;
            MDICREATESTRUCT mcs;
            HDC hdc;
            PAINTSTRUCT ps;
            BITMAP tmpBMP;
            int x,y,i=0,iWidth = -1;
            int iStatusWidths[] = {200, 300, -1};
            
            // Find window menu where children will be listed
            ccs.hWindowMenu  = NULL; //GetSubMenu(GetMenu(hwnd), 0);
            ccs.idFirstChild = ID_MDI_FIRSTCHILD;

            g_hMDIClient = CreateWindowEx(WS_EX_CLIENTEDGE, "mdiclient", NULL,
                WS_CHILD | WS_CLIPCHILDREN,
                CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
                hwnd, (HMENU)ID_MDI_CLIENT, g_hInst, (LPVOID)&ccs);
            ShowWindow(g_hMDIClient, SW_SHOW);       
            g_hBG = CreateWindowEx(WS_EX_CLIENTEDGE, g_szMap, NULL, 
                WS_CHILD | WS_CLIPSIBLINGS,
                -2,-2,512+24,512+24,
                g_hMDIClient,NULL,NULL,NULL); 
            ShowWindow(g_hBG, SW_SHOW);
                 
            SetWindowPos(g_hBG,HWND_BOTTOM,-2,-2,536,536,SWP_NOMOVE);

            g_hStatusBar = CreateWindowEx(0, STATUSCLASSNAME, NULL,
                WS_CHILD | WS_VISIBLE | SBARS_SIZEGRIP, 0, 0, 0, 0,
                hwnd, (HMENU)ID_STATUSBAR, g_hInst, NULL);
            SendMessage(g_hStatusBar, SB_SETPARTS, 1, (LPARAM)&iWidth );
            
            bmpTiles = (BITMAP**) LoadImage(g_hInst, "tiles.bmp", IMAGE_BITMAP,
                0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION | LR_MONOCHROME);
            
            tiles = CreateCompatibleDC(GetDC(g_hTiles));
            SelectObject(tiles,bmpTiles);
            break;
        }
        case WM_COMMAND:
        {
            switch(LOWORD(wParam))
            {
                case CM_FILE_NEW:
                {
                    int result = IDYES;
                    BITMAP tmpBMP;
                    RECT rs;

                    if (current_map.changed) {
                        result = MessageBox(NULL,"Save current map before creating a blank map in a new tileset?","Notice",
                            MB_YESNOCANCEL | MB_ICONQUESTION);
                    }
                    if (result==IDCANCEL)
                           return 0;
                    
                    ZeroMemory(map_data,256);
                    ZeroMemory(&current_map,sizeof(MAPSTRUCT));
                    ZeroMemory(tilesPath,MAX_PATH);
                    EnableMenuItem(GetMenu(g_hMainWindow),CM_FILE_SAVEAS,MF_BYCOMMAND | MF_ENABLED);
                    DrawMenuBar(g_hMainWindow);
                    if (LoadNewTiles()) {           //True signifies success or error beginning loading
                        if (tilesPath[0]) {         //if tilespath address exists
                            strcpy(current_map.tileset,tilesPath);
                            InvalidateRect(g_hBG,NULL,FALSE);
                            if (!g_hTiles)
                                SendMessage(hwnd,WM_COMMAND,CM_TILES_NEW,CM_TILES_NEW);
                        }
                    } else {
                        if (!g_hTiles)
                            SendMessage(hwnd,WM_COMMAND,CM_TILES_NEW,CM_TILES_NEW);
                    }
                    InvalidateRect(g_hTiles,NULL,TRUE);
                    break;
                }
                case CM_FILE_SAVE:
                {
                    //MessageBox(NULL,current_map.filepath,"Info",MB_OK);
                    if (current_map.filepath[0]) {
                        SaveFile(current_map.filepath);
                    } else {
                        if (current_map.tileset[0])
                            DoFileOpenSave(TRUE);
                    }
                    current_map.changed = FALSE;
                    break;
                }
                case CM_FILE_SAVEAS:
                {
                    if (DoFileOpenSave(TRUE)) current_map.changed = FALSE;
                    break;
                }
                case CM_FILE_OPEN:
                {
                    int result;
                    char first_char_backup;
                    if (current_map.changed) {
                        result = MessageBox(NULL,"Save current map before loading a new one?","Query",
                            MB_YESNOCANCEL | MB_ICONQUESTION);
                        if (result==IDYES) {
                            DoFileOpenSave(TRUE);
                        } else if (result==IDCANCEL) {
                            return 0;
                        }
                    }
                    first_char_backup = current_map.filepath[0];
                    current_map.filepath[0] = 0; 
                    if (DoFileOpenSave(FALSE)) {
                        if (!current_map.filepath[0]) {
                            MessageBox(NULL, "Not a valid Zelda map file.","Error",MB_OK);
                            return 0;
                        }
                    } else {
                        current_map.filepath[0] = first_char_backup;
                        return 0;
                    }
          
                    if (strcmp(tilesPath,current_map.tileset)) {
                        strcpy(tilesPath,current_map.tileset);
                        current_map.changed = FALSE;
                        if (!DoBitmapLoad(current_map.tileset)) {
                            if (MessageBox(NULL,"Error loading tileset -- try map in a different tileset? (The map may not be compatible)",
                                "Notice",MB_YESNO | MB_ICONERROR)==IDYES) {
                                while (!LoadNewTiles());
                                strcpy(current_map.tileset,tilesPath);
                            }
                        }
                    }
                    EnableMenuItem(GetMenu(g_hMainWindow),CM_FILE_SAVEAS,MF_BYCOMMAND | MF_ENABLED);
                    EnableMenuItem(GetMenu(g_hMainWindow),CM_FILE_SAVE,MF_BYCOMMAND | MF_GRAYED);
                    menu_state = FALSE;
                    DrawMenuBar(g_hMainWindow);
                    InvalidateRect(g_hBG,NULL,TRUE);
                    if (g_hTiles){
                         InvalidateRect(g_hTiles,NULL,TRUE);
                         return 0;
                    }
                    current_map.changed = FALSE;
                    SendMessage(hwnd,WM_COMMAND,CM_TILES_NEW,CM_TILES_NEW);
                    return 0;
                }
                case CM_MAP_FILL:
                {
                    if (!current_map.tileset[0]) {
                        MessageBox(NULL,"No map is loaded","Error",MB_OK | MB_ICONERROR);
                        return 0;
                    }
                    if (MessageBox(NULL,"The entire map will filled\nwith the selected tile.  Proceed?",
                        "Notice",MB_YESNO | MB_ICONWARNING)==IDYES) {
                        int i;
                        for (i=0;i<256;i++) map_data[i] = (unsigned short)sourcePos/16;
                        ZeroMemory(undo_array,sizeof(UNDOSTRUCT)*5);
                        current_map.changed = TRUE;
                        InvalidateRect(g_hBG,NULL,FALSE);
                    }
                    do_compress();
                    break;
                }
                case CM_MAP_CLEAR:
                {
                    if (!current_map.tileset[0]) {
                        MessageBox(NULL,"No map is loaded","Error",MB_OK | MB_ICONERROR);
                        return 0;
                    }
                    if (MessageBox(NULL,"The entire map will be cleared.  Proceed?","Notice",
                        MB_YESNO | MB_ICONWARNING)==IDYES) {
                        ZeroMemory(map_data,256);
                        ZeroMemory(undo_array,sizeof(UNDOSTRUCT)*5);
                        current_map.changed = TRUE;
                        InvalidateRect(g_hBG,NULL,FALSE);
                    }
                    do_compress();
                    break;
                }
                case CM_MAP_REPLACE:
                {
                    if (!current_map.tileset[0]) {
                        MessageBox(NULL,"No map is loaded","Error",MB_OK | MB_ICONERROR);
                        return 0;
                    }
                }                 
                case CM_FILE_EXIT:
                {
                   PostMessage(hwnd, WM_CLOSE, 0, 0);
                   break;
                }
                case CM_TILES_LOAD:
                {
                    int result = 0;
                    if (current_map.changed) {
                        result = MessageBox(NULL,"Save current map before changing tileset?",
                        "Query",MB_YESNOCANCEL | MB_ICONQUESTION);
                    }
                    if (result==IDCANCEL)
                        return 0;
                    if (result==IDYES)
                        DoFileOpenSave(TRUE);
                        
                    if (!LoadNewTiles()) {
                        MessageBox(NULL,"Couldn't load tiles.","Error",MB_OK | MB_ICONERROR);
                        return 0;
                    }
                    
                    if (strcmp(tilesPath,current_map.tileset)) {
                        if (MessageBox(NULL,"Current map does not belong in this tileset, should it be converted?",
                            "Query",MB_YESNO | MB_ICONQUESTION)==IDNO) {
                            if (!LoadFile(current_map.filepath)) {
                                MessageBox( NULL, "Could not load file","Error", MB_OK | MB_ICONERROR);
                                return 0;
                            }
                            DoBitmapLoad(current_map.tileset);
                            strcpy(tilesPath,current_map.tileset);
                        } else {
                            strcpy(current_map.tileset,tilesPath);
                            current_map.changed = TRUE;
                        }
                    }
                    if (!g_hTiles) { 
                        SendMessage(hwnd,WM_COMMAND,CM_TILES_NEW,CM_TILES_NEW);
                        return 0;
                    }
                    
                    InvalidateRect(g_hTiles,NULL,TRUE);
                    InvalidateRect(g_hBG,NULL,FALSE);
                    sourcePos = 0xFFFFFFFF;
                    
                    break;
                }
                case CM_TILES_NEW:
                {  
                    MDICREATESTRUCT mcs;
                    BITMAP tmpBMP;
                    int i;
                    
                    GetObject(bmpTiles,sizeof(BITMAP),&tmpBMP);
                    i = (32*(tmpBMP.bmHeight/192));
                    if (tmpBMP.bmHeight>i) i+=32;
                    mcs.x = 538;
                    if (i>(6*32)+10) {
                        mcs.cx = i+14;
                    } else {
                        mcs.cx  = (6*32)+10;
                    }
                    mcs.y = 0;      mcs.cy = 16*32;
                    mcs.style   = WS_CHILD | WS_OVERLAPPEDWINDOW;                   
                    
                    mcs.szTitle     = "Tiles";
                    mcs.szClass     = g_szChild;
                    mcs.hOwner      = g_hInst;

                    EnableMenuItem(GetMenu(g_hMainWindow),CM_TILES_NEW,
                        MF_BYCOMMAND | MF_GRAYED);
                    DrawMenuBar(g_hMainWindow);
                    g_hTiles = (HWND) (HWND)SendMessage(g_hMDIClient, 
                        WM_MDICREATE, 0, (LONG)&mcs);
                    break;
                }
                case CM_UNDO:
                {        
                    if (undo_array[undoPos].newtile || undo_array[undoPos].oldtile
                         || undo_array[undoPos].tilenumber) {
                        map_data[undo_array[undoPos].tilenumber]=undo_array[undoPos].oldtile;
                        if (undoPos<MAX_UNDO-1) {    
                            undoPos++;
                        } else {
                            EnableMenuItem(GetMenu(g_hMainWindow),CM_UNDO,MF_BYCOMMAND | MF_GRAYED);
                        }  
                        EnableMenuItem(GetMenu(g_hMainWindow),CM_REDO,MF_BYCOMMAND | MF_ENABLED);
                    }
                    if (!undo_array[undoPos].tilenumber) {
                        EnableMenuItem(GetMenu(g_hMainWindow),CM_UNDO,MF_BYCOMMAND | MF_GRAYED);
                    }
                    DrawMenuBar(g_hMainWindow);
                    InvalidateRect(g_hBG,NULL,FALSE);
                    do_compress();
                    break;
                }
                case CM_REDO:
                {
                    if (undo_array[undoPos-1].tilenumber) {
                        if (undoPos>0) {
                            undoPos--;
                        } else {
                            EnableMenuItem(GetMenu(g_hMainWindow),CM_REDO,MF_BYCOMMAND | MF_GRAYED);
                            DrawMenuBar(g_hMainWindow);
                            return 0;
                        }
                        map_data[undo_array[undoPos].tilenumber]=undo_array[undoPos].newtile;
                        EnableMenuItem(GetMenu(g_hMainWindow),CM_UNDO,MF_BYCOMMAND | MF_ENABLED);
                        InvalidateRect(g_hBG,NULL,FALSE);
                    }
                    if (!undo_array[undoPos-1].tilenumber) {
                        EnableMenuItem(GetMenu(g_hMainWindow),CM_REDO,MF_BYCOMMAND | MF_GRAYED);
                        DrawMenuBar(g_hMainWindow);
                    }
                    InvalidateRect(g_hBG,NULL,FALSE);
                    do_compress();
                    break;
                }
            }
            break;
        }
        case WM_SIZE:
        {
            RECT rectClient, rectStatus;
            UINT uStatusHeight, uClientAlreaHeight;

            SendMessage(g_hStatusBar, WM_SIZE, 0, 0);
            GetClientRect(hwnd, &rectClient);
            GetWindowRect(g_hStatusBar, &rectStatus);

            uStatusHeight = rectStatus.bottom - rectStatus.top;
            uClientAlreaHeight = rectClient.bottom;
            
            MoveWindow(g_hMDIClient, 0, 0, rectClient.right, uClientAlreaHeight - uStatusHeight, TRUE);
            return 0;
        }
        case WM_CLOSE:
        {
            int result = IDNO;
            if (current_map.changed) {
                result = MessageBox(NULL,"Save current map before exiting?","Notice",
                    MB_YESNOCANCEL | MB_ICONQUESTION);
            }
            if (result == IDCANCEL)
                return 0;
            if (result == IDYES) {
                if (current_map.filepath[0]) {
                    if (!SaveFile(current_map.filepath)) return 0;
                } else {
                    if (!DoFileOpenSave(TRUE)) return 0;
                }
            }        
            DestroyWindow(hwnd);
            break;
        }
        case WM_DESTROY:
         PostQuitMessage(0);
        break;
        default:
            return DefFrameProc(hwnd, g_hMDIClient, Message, wParam, lParam);
    }
    return 0;
}

LRESULT CALLBACK MDIChildWndProc(HWND hwnd, UINT Message, WPARAM wParam,
   LPARAM lParam)
{

    switch (Message) {
        case WM_CREATE:
        {
            statwin = CreateWindow(g_szTileStats,"Tile Stats",WS_CHILD | WS_DLGFRAME,
                64,192*2+12-4,128,84,
                hwnd,NULL,NULL,NULL);    
            ShowWindow(statwin,SW_SHOW);
            break;
        }
        case WM_LBUTTONDOWN:
        {
            int m_y = GET_Y_LPARAM(lParam);
            if (m_y<32*12) {
                sourcePos = ((m_y & 0xFFE0)/2)
                    + ((GET_X_LPARAM(lParam) & 0xFFE0)*6);
                InvalidateRect(hwnd,NULL,FALSE);
                SetWindowPos(hwnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_SHOWWINDOW);
            }
            break;
        }
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            RECT rs;
            HDC hdc = BeginPaint(hwnd,&ps);
            int i,x=0;
            BITMAP tempBMP;
            if (tilesPath[0]) {
                GetObject(bmpTiles,sizeof(BITMAP),&tempBMP);
                for (i=0;i<tempBMP.bmHeight;i+=192,x+=33) {
                    StretchBlt(hdc, x,0,32,192*2,tiles,0,i,16,192,SRCCOPY);
                }
            } else {
                if (LoadNewTiles()) {
                    if (!tilesPath[0])  SendMessage(hwnd,WM_CLOSE,0,0);
                }
                sourcePos = 0xFFFFFFFF;
                if (!current_map.tileset[0]) {
                    strcpy(current_map.tileset,tilesPath);
                    InvalidateRect(g_hBG,NULL,FALSE);
                }
                InvalidateRect(hwnd,NULL,FALSE);
            }
            rs.left = 20;rs.right=20+4+32+4;
            rs.top = 192*2+12-4;rs.bottom=rs.top+4+32+4;
            FillRect(hdc,&rs,(HBRUSH) COLOR_ACTIVEBORDER+1);
            if (sourcePos!=0xFFFFFFFF)
                StretchBlt(hdc, 24,192*2+12,32,32,tiles,0,sourcePos,16,16,SRCCOPY);           
            EndPaint (hwnd, &ps);
            break;
        }
        case WM_CLOSE:
        {
            EnableMenuItem(GetMenu(g_hMainWindow),CM_TILES_NEW,MF_BYCOMMAND | MF_ENABLED);
            DrawMenuBar(g_hMainWindow);
            DefMDIChildProc(hwnd, Message, wParam, lParam);
            g_hTiles = 0;
        }

        default:
            return DefMDIChildProc(hwnd, Message, wParam, lParam);
    }
}

LRESULT CALLBACK MapProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch (Message) {
        case WM_LBUTTONDOWN:
        case WM_RBUTTONDOWN:
        {
            char statusStr[64];
            SetWindowPos(hwnd,HWND_BOTTOM,-2,-2,536,536,0);
            tile_pending = TRUE;
            int m_y = (GET_Y_LPARAM(lParam)-20) & 0xFFE0;
            int m_x = (GET_X_LPARAM(lParam)-20) & 0xFFE0;
            if (m_x<512 && m_y<512 && m_x>=0 && m_y>=0) {
                int i,j,temp;

                sprintf(statusStr,"X: %.3d, Y: %.3d, OFFSET: $%.4X",
                    m_x/2, m_y/2, (m_y<<7) + (m_x/2));
                
                SendMessage(g_hStatusBar, SB_SETTEXT, 0, (LPARAM)statusStr);
                InvalidateRect(g_hStatusBar,NULL,TRUE);
                
                j = (m_y/2)+(m_x/32);
                temp = map_data[j];
                
                if (Message==WM_LBUTTONDOWN) {
                    if (GetKeyState(VK_CONTROL) & 0x8000) {
                        sourcePos = (temp & 0x7F)<<4;
                        InvalidateRect(g_hTiles,NULL,FALSE);
                        return 0;
                    }
                    
                    if (sourcePos == 0xFFFFFFFF) return 0;

                    map_data[j] = sourcePos>>4;
                } else {
                    map_data[j] ^= 0x80;
                }
                
                current_map.changed = TRUE;
                
                do_compress();
                
                for (i=MAX_UNDO-1;i>0;i--) {
                    undo_array[i].newtile = undo_array[i-1].newtile;
                    undo_array[i].tilenumber = undo_array[i-1].tilenumber;
                    undo_array[i].oldtile = undo_array[i-1].oldtile;
                }
                undoPos = 0;
                undo_array[0].tilenumber = j;
                undo_array[0].newtile = map_data[j];
                undo_array[0].oldtile = temp;
                // adjust menu accordingly
                EnableMenuItem(GetMenu(g_hMainWindow),CM_UNDO,MF_BYCOMMAND | MF_ENABLED);
                EnableMenuItem(GetMenu(g_hMainWindow),CM_REDO,MF_BYCOMMAND | MF_GRAYED);
                DrawMenuBar(g_hMainWindow);
                InvalidateRect(hwnd,NULL,FALSE);
            }
            return 0;
        }       
        case (WM_PAINT):
        {
            HDC hdc;
            PAINTSTRUCT ps;
            RECT rs;
            int y,x,i=0;
            char nums[16]="0123456789ABCDEF";
            char* temp;
            
            if (!current_map.tileset[0])
                return DefWindowProc (hwnd, Message, wParam, lParam);
            
            hdc = BeginPaint(g_hBG, &ps);
            SetDCBrushColor(hdc,0x000000FF);

            temp = nums;
            for (x=27;x<532;x+=32) {
                TextOut(hdc,x,0,temp,1);
                TextOut(hdc,4,x,temp++,1);
            }
            if (current_map.tileset[0]) {
                for (y=20;y<512;y+=32) {
                    for (x=20;x<512;x+=32) {
                        if (map_data[i] & 0x80) {
                            rs.left=x;rs.top=y;
                            rs.bottom=y+32;rs.right=x+32;
                            FillRect(hdc,&rs,GetStockObject(DC_BRUSH));                           
                            StretchBlt(hdc,x,y,32,32,tiles,0,(int) (map_data[i++] & 0x7F)*16,16,16,SRCPAINT);
                        } else {
                            StretchBlt(hdc,x,y,32,32,tiles,0,(int) (map_data[i++] & 0x7F)*16,16,16,SRCCOPY);
                        }
                    }
                }
            }
            EndPaint (g_hBG, &ps);
            SendMessage(g_hStatusBar,WM_PAINT,0,0);
            break;
        }
        case WM_ERASEBKGND:
        {
            if (tile_pending) {
                RECT rs;
                tile_pending = FALSE;
                rs.left=0;rs.top=0;
                rs.right=532;rs.bottom=20;
                FillRect(GetDC(g_hBG),&rs,(HBRUSH) (COLOR_3DFACE+1));
                InvalidateRect(hwnd,&rs,FALSE);
                rs.right=20;
                rs.bottom=532;
                FillRect(GetDC(g_hBG),&rs,(HBRUSH) (COLOR_3DFACE+1));
                InvalidateRect(hwnd,&rs,FALSE);
                return 0;
            }
            return DefWindowProc(hwnd,Message,wParam,lParam);
        }
        default:
            return DefWindowProc (hwnd, Message, wParam, lParam);
    }
}

LRESULT CALLBACK StatProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
    switch(Message) {
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            RECT rs;
            HDC hdc = BeginPaint(hwnd,&ps);
            int i,k;
            char *puts = tilesPath;
            
            TextOut(hdc,4,1,"Current Tileset",15);
            if (tilesPath[0]) {
                for (i=MAX_PATH;tilesPath[i]!='\\';i--);
                puts+=i++;
                for (k=1;tilesPath[i++]!='.';k++)
                TextOut(hdc,10,20,puts+1,k);
            }
            TextOut(hdc,4,40,"Tile #",6);
            intToString((int)sourcePos/16);
            for (i=0;ans[i]!=0;i++);
            TextOut(hdc,50,40,ans,i);
            TextOut(hdc,4,60,"Ratio: ",6);
            sprintf(ans,"%f",comp_ratio);
            TextOut(hdc,50,60,ans,5);
            EndPaint(hwnd,&ps);
            break;
        }
    default:
        return DefWindowProc(hwnd,Message,wParam,lParam);
    }
}

void intToString(int num) {
    int i=0,j,k=0,ones=0;
    char temp[5];
    ZeroMemory(ans,5);    
    while (num) {
        ones = num%10;
        temp[i++]=(char)(ones+48);
        num=num/10;
    }
    if (!i) temp[i++]=48;
    for (j=i-1;j>=0;j--) ans[k++]=temp[j];
    ans[k]=0;
}

BOOL LoadNewTiles(void) {
/*  The load new tiles function will return a boolean value which
is not necessarily indicative of failure or success.
FALSE indicates an error in tilemap compatibility.
TRUE indicates either success or failure to begin loading tilemap. */
    OPENFILENAME ofn;
    char szFileName[MAX_PATH];
    int i,w;
    BITMAP tmpBMP;
    RECT rs,rs1;
    
    ZeroMemory(&ofn,sizeof(ofn));
    szFileName[0] = 0;
    
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = g_hMainWindow;
    ofn.lpstrFilter = "Bitmap Files (*.bmp)\0*.bmp\0All Files (*.*)\0*.*\0\0";
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrDefExt = "bmp";
    
    ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    GetOpenFileName(&ofn);

    if (!szFileName[0])
        return TRUE;        //supposed to be "TRUE"

    if (!(DoBitmapLoad(szFileName))) return FALSE;  
    strcpy(tilesPath,szFileName);

    // if the tile window is open
    if (g_hTiles) {
        GetObject(bmpTiles,sizeof(BITMAP),&tmpBMP);
        i = (32*(tmpBMP.bmHeight/192));
        if (tmpBMP.bmHeight & 0x0000003F) i+=32;
        w = 538;
        if (i>(6*32)+10) {
            w = i+14;
        } else {
            w  = (6*32)+10;
        }
        GetWindowRect(g_hMDIClient,&rs1);
        GetWindowRect(g_hTiles,&rs);  
        MoveWindow(g_hTiles,rs.left-rs1.left-1,rs.top-rs1.top-1,w,16*32,TRUE);    
    }
    return TRUE;
}

BOOL DoBitmapLoad(char* szFileName) {
    BITMAP tmpBMP;
    
    bmpTiles = (BITMAP**) LoadImage(g_hInst, szFileName, IMAGE_BITMAP,
            0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION | LR_MONOCHROME);
    if (!bmpTiles) {
        MessageBox(NULL,"Error loading bitmap.","Error",MB_OK | MB_ICONERROR);
        return FALSE;
    }
    GetObject(bmpTiles,sizeof(BITMAP),&tmpBMP);
    if (tmpBMP.bmWidth>16 || tmpBMP.bmHeight<16 || tmpBMP.bmHeight>128*16) {
        MessageBox(NULL,"Not a valid tilemap","Error",MB_OK | MB_ICONERROR);
        return FALSE;
    }
    SelectObject(tiles,bmpTiles);
    return TRUE;
}    

BOOL DoFileOpenSave(BOOL bSave) {
    OPENFILENAME ofn;
    char szFileName[MAX_PATH];
    
    ZeroMemory(&ofn, sizeof(ofn));
    ZeroMemory(szFileName, MAX_PATH);

    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = g_hMainWindow;
    ofn.lpstrFilter = "Asm Files (*.asm)\0*.asm\0All Files (*.*)\0*.*\0\0";
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrDefExt = "asm";

    if (bSave) {
        ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY |
            OFN_OVERWRITEPROMPT;
        if(GetSaveFileName(&ofn)) {
            strcpy(current_map.filepath,szFileName);
            if (!SaveFile(szFileName)) return TRUE;
        } else {
            return FALSE;
        }
    } else {
        ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
        if(GetOpenFileName(&ofn)) {
            if (!LoadFile(szFileName)) return TRUE;
            strcpy(current_map.filepath,szFileName);
        } else {
            return FALSE;
        }
    }
    // get rid of all of the pending undos.
    ZeroMemory(undo_array,sizeof(UNDOSTRUCT)*MAX_UNDO);
    EnableMenuItem(GetMenu(g_hMainWindow),CM_UNDO,MF_BYCOMMAND | MF_GRAYED);
    EnableMenuItem(GetMenu(g_hMainWindow),CM_REDO,MF_BYCOMMAND | MF_GRAYED);
    DrawMenuBar(g_hMainWindow);
    do_compress();
    return TRUE;
}

BOOL SaveFile(LPSTR pszFileName) {
    FILE *outFile;
    int lineCounter=1,w,i=0;
    char *p,*imageData;
    char tempName[25],temp_title[MAX_PATH];
    BITMAP tmpBMP;
    
    // if the map hasn't been saved, then let's do a save as.
    if (!current_map.filepath[0]) {
        return WndProc(g_hMainWindow, WM_COMMAND, CM_FILE_SAVEAS, 0);
    }
    //update the title string
    strcpy(temp_title,"Zelda Map Editor : ");
    SetWindowText(g_hMainWindow, strcat(temp_title, pszFileName));

    if (!(outFile = fopen(pszFileName,"w"))) return FALSE;
    memset(temp_title,0,MAX_PATH);
    strcpy(temp_title,tilesPath);
    for (i=0;temp_title[i]!='.';i++);
    //set the dot to null to end the string
    temp_title[i] = 0;
    for (w=MAX_PATH;temp_title[w]!='\\';w--);
    //temp name gets the core file name "dungeon.asm -> dungeon"
    strcpy(tempName,temp_title+w+1);
    strcpy(temp_title+i,".zmp");
    //write the tile set path.
    fprintf(outFile,";%s!",tilesPath);
    for (lineCounter=0,i=0;lineCounter<16;lineCounter++) {
        fputs("\n .db ",outFile);
        for (w=0;w<16;w++,i++) {
            fprintf(outFile,"$%.2X",map_data[i]);
            if (w!=15) fputc(',',outFile);
        }
    }
    fclose(outFile);
    // begin writing bitmap data
    outFile = fopen(temp_title,"w");
    GetObject(bmpTiles,sizeof(BITMAP),&tmpBMP);
    fprintf(outFile,"%s_data:",tempName);
    fprintf(outFile,"\n;Height: %d",tmpBMP.bmHeight);
    fprintf(outFile,"\n;Width: %d",tmpBMP.bmWidth);
    fprintf(outFile,"\n;Scan Width: %d",tmpBMP.bmWidthBytes);
    fprintf(outFile,"\n;Bits per Pixel: %d",tmpBMP.bmBitsPixel);
    fprintf(outFile,"\n;From File: %s\n",current_map.tileset);
    // there are 4 bytes per scan line
    imageData = (char*) tmpBMP.bmBits + (tmpBMP.bmHeight*4)-1;
    for (lineCounter = tmpBMP.bmHeight;lineCounter>0;lineCounter--,imageData-=2) {
        imageData-=2;
        fprintf(outFile," .db $%.2X,$%.2X\n",(unsigned char) ~(*(imageData-1)),
                                             (unsigned char) ~(*imageData));
    }
    fprintf(outFile,"%s_data_end:",tempName);
    fclose(outFile);
    menu_state = FALSE;
    EnableMenuItem(GetMenu(g_hMainWindow),CM_FILE_SAVE,MF_BYCOMMAND | MF_GRAYED);
    return TRUE;     
}

BOOL LoadFile(LPSTR pszFileName)
{
    char hexDecode[] = "0123456789ABCDEF";
    char temp_title[MAX_PATH];
    FILE* inFile;
    int i,i2,inChar,x,y=0,ind=0,temp;

    if (!(inFile = fopen(pszFileName,"r"))) return FALSE;
    
    strcpy(temp_title,"Zelda Map Editor : ");
    SetWindowText(g_hMainWindow, strcat(temp_title, pszFileName));
    
    inChar = fgetc(inFile);         //reads in the ';'
    if ((char) inChar != ';') return FALSE;  
    while ((char)inChar!='!') {     //:12_.db_
        inChar = fgetc(inFile);
        // do a little check to see if it follows the format
        if ((char) inChar == '\n') return FALSE;
        current_map.tileset[y++]=(char)inChar;
    }
    current_map.tileset[y-1] = 0;
    fseek(inFile,7L,SEEK_CUR);
    for (y=0;y<16;y++) {
        for (x=0;x<16;x++) {
            fgetc(inFile);          //$
            inChar = fgetc(inFile); //left number
            for (i=0;i<16 && hexDecode[i]!=(char)inChar;i++);
            temp = fgetc(inFile);
            for (i2=0;i2<16 && hexDecode[i2]!=(char)temp;i2++);
            map_data[ind++] = (unsigned char) i2+(i<<4);
            fgetc(inFile);          //,
        }
        fseek(inFile,5L,SEEK_CUR);
    }
    fclose(inFile);
    return TRUE;
}   

void do_compress(void) {
    int temp_dict_index,temp_map_index,i;
    comp_index = 0;                     //where we start
    comp_iter = 6;                          //signal to compress iteration
    dComp_counter = 0;
    memset(dict,0,1024*sizeof(int));
    dict_index = 0;

    memset(comp_map_data,0,320);
    this_code = map_data[0];
    map_index = 1;  

    for (;;) {
        PutSymbol(this_code); 
        next_code = map_data[map_index++];
        //if (map_index-1>=256) break; which is the equivalent of saying
        if (map_index>=257) break;
        CreateSymbol();
        //the next dictionary entry is in, now we have to see if our current
        //two bytes match a pattern file in the dictionary
        
        for (temp_dict_index = 0;temp_dict_index < dict_index;
            temp_dict_index+=2) {
                
            if (dict[temp_dict_index] == next_code 
             && dict[temp_dict_index+1] == map_data[map_index]) {
                next_code = (unsigned short) (temp_dict_index/2)+256;
                map_index++;
            }
        }
        //regardless of whether or not a match was found, next_code holds the
        //value to be written
        this_code = next_code;
    }
    PutSymbol(0x03FF);              //a -1 terminator
    if (comp_iter!=6) {
        comp_index++;
    }
    comp_ratio = (float)comp_index/256;
    InvalidateRect(statwin,NULL,TRUE);
}

void CreateSymbol(void) {
    dict[dict_index] = this_code;
    dict[dict_index+1] = next_code; dict_index+=2;
}
        
void PutSymbol(unsigned short tCode) {
    unsigned short temp = tCode;
    unsigned char small_temp;
    
    dComp_counter++;
    if (comp_iter!=8) tCode <<= comp_iter;  //leave 6 slots to the right
    small_temp = (tCode & 0xFF00)>>8;
    comp_map_data[comp_index] = comp_map_data[comp_index] | small_temp;
    comp_index++;
    small_temp = tCode & 0x00FF;
    comp_map_data[comp_index] = comp_map_data[comp_index] | small_temp;
    if (comp_iter==8) comp_index++;
    comp_iter-=2;
    if (!comp_iter) comp_iter=8;
}

