/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 2002 Ben Parnell
 *
 * 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
 */

#include "common.h"
#include "../../palette.h"
#include "../../fceu.h"

HWND hPPUView;

extern uint8 *VPage[8];
extern uint8 PALRAM[0x20];

int PPUViewPosX,PPUViewPosY;
uint8 palcache[32] = { 0xFF }; //palette cache
uint8 chrcache0[0x1000],chrcache1[0x1000]; //cache CHR, fixes a refresh problem when right-clicking
uint8 *pattern0,*pattern1; //pattern table bitmap arrays
uint8 *ppuv_palette;
static int pindex0=0,pindex1=0;
int PPUViewScanline=0,PPUViewer=0;
int PPUViewSkip;
int PPUViewRefresh = 15;
int mouse_x,mouse_y;

#define PATTERNWIDTH        128
#define PATTERNHEIGHT        128
#define PATTERNBITWIDTH        PATTERNWIDTH*3
#define PATTERNDESTX        10
#define PATTERNDESTY        15
#define ZOOM                        2

#define PALETTEWIDTH        32*4*4
#define PALETTEHEIGHT        32*2
#define PALETTEBITWIDTH        PALETTEWIDTH*3
#define PALETTEDESTX        10
#define PALETTEDESTY        327

#define TBM_SETPOS            (WM_USER+5)
#define TBM_SETRANGE        (WM_USER+6)
#define TBM_GETPOS            (WM_USER)

BITMAPINFO bmInfo;
HDC pDC,TmpDC0,TmpDC1;
HBITMAP TmpBmp0,TmpBmp1;
HGDIOBJ TmpObj0,TmpObj1;

BITMAPINFO bmInfo2;
HDC TmpDC2,TmpDC3;
HBITMAP TmpBmp2,TmpBmp3;
HGDIOBJ TmpObj2,TmpObj3;


void PPUViewDoBlit() {
        if(!hPPUView) return;
        if(PPUViewSkip < PPUViewRefresh) {
                PPUViewSkip++;
                return;
        }
        PPUViewSkip=0;

        StretchBlt(pDC,PATTERNDESTX,PATTERNDESTY,PATTERNWIDTH*ZOOM,PATTERNHEIGHT*ZOOM,TmpDC0,0,PATTERNHEIGHT-1,PATTERNWIDTH,-PATTERNHEIGHT,SRCCOPY);
        StretchBlt(pDC,PATTERNDESTX+(PATTERNWIDTH*ZOOM)+1,PATTERNDESTY,PATTERNWIDTH*ZOOM,PATTERNHEIGHT*ZOOM,TmpDC1,0,PATTERNHEIGHT-1,PATTERNWIDTH,-PATTERNHEIGHT,SRCCOPY);
        StretchBlt(pDC,PALETTEDESTX,PALETTEDESTY,PALETTEWIDTH,PALETTEHEIGHT,TmpDC2,0,PALETTEHEIGHT-1,PALETTEWIDTH,-PALETTEHEIGHT,SRCCOPY);
}

void DrawPatternTable(uint8 *bitmap, uint8 *table, uint8 pal) {
        int i,j,x,y,index=0;
        int p=0,tmp;
        uint8 chr0,chr1;
        uint8 *pbitmap = bitmap;

        pal <<= 2;
        for (i = 0; i < 16; i++)		//Columns
		{				
                for (j = 0; j < 16; j++) //Rows
				{		
                        //-----------------------------------------------
						///8x8 sprite
						for (y = 0; y < 8; y++) 
						{
                                chr0 = table[index];
                                chr1 = table[index+8];
                                tmp=7;
                                for (x = 0; x < 8; x++) 
								{
                                        p  =  (chr0>>tmp)&1;
                                        p |= ((chr1>>tmp)&1)<<1;
                                        p = palcache[p|pal];
                                        tmp--;

                                        *(uint8*)(pbitmap++) = palo[p].b;
                                        *(uint8*)(pbitmap++) = palo[p].g;
                                        *(uint8*)(pbitmap++) = palo[p].r;
                                }
                                index++;
                                pbitmap += ((PALETTEBITWIDTH>>2)-24);
						}
						//------------------------------------------------
                        index+=8;
                        pbitmap -= (((PALETTEBITWIDTH>>2)<<3)-24);
                }
                pbitmap += ((PALETTEBITWIDTH>>2)*7);
        }

}

void FCEUD_UpdatePPUView(int scanline, int refreshchr) {
		if(!PPUViewer) return;
		if(scanline != -1 && scanline != PPUViewScanline) return;

        int x,y,i;
        uint8 *pbitmap = ppuv_palette;

        if(!hPPUView) return;
        if(PPUViewSkip < PPUViewRefresh) return;

        if(refreshchr) {
                for (i = 0, x=0x1000; i < 0x1000; i++, x++) {
                        chrcache0[i] = VPage[i>>10][i];
                        chrcache1[i] = VPage[x>>10][x];
                }
        }

        //update palette only if required
        if(memcmp(palcache,PALRAM,32) != 0) { //bbit note: let para know that this if is useless and
                //cache palette content              will not work because of the lines below that change
                memcpy(palcache,PALRAM,32);        //palcache which will make it not equal next time
                palcache[0x10] = palcache[0x00];
                palcache[0x14] = palcache[0x00];
                palcache[0x18] = palcache[0x00];
                palcache[0x1C] = palcache[0x00];

        //draw palettes
        for (y = 0; y < PALETTEHEIGHT; y++) {
                for (x = 0; x < PALETTEWIDTH; x++) {
                                i = (((y>>5)<<4)+(x>>5));
                                *(uint8*)(pbitmap++) = palo[palcache[i]].b;
                                *(uint8*)(pbitmap++) = palo[palcache[i]].g;
                                *(uint8*)(pbitmap++) = palo[palcache[i]].r;
                        }
                }

                //draw line seperators on palette
                pbitmap = (ppuv_palette+PALETTEBITWIDTH*31);
                for (x = 0; x < PALETTEWIDTH*2; x++) {
                        *(uint8*)(pbitmap++) = 0;
                        *(uint8*)(pbitmap++) = 0;
                        *(uint8*)(pbitmap++) = 0;
                }
                pbitmap = (ppuv_palette-3);
                for (y = 0; y < 64*3; y++) {
                        if(!(y%3)) pbitmap += (32*4*3);
                        for (x = 0; x < 6; x++) {
                                *(uint8*)(pbitmap++) = 0;
                        }
                        pbitmap += ((32*4*3)-6);
                }
                memcpy(palcache,PALRAM,32);        //palcache which will make it not equal next time
        }

        DrawPatternTable(pattern0,chrcache0,pindex0);
        DrawPatternTable(pattern1,chrcache1,pindex1);

        //PPUViewDoBlit();
}

void KillPPUView() {
        //GDI cleanup
        DeleteObject(TmpBmp0);
        SelectObject(TmpDC0,TmpObj0);
        DeleteDC(TmpDC0);
        DeleteObject(TmpBmp1);
        SelectObject(TmpDC1,TmpObj1);
        DeleteDC(TmpDC1);
        DeleteObject(TmpBmp2);
        SelectObject(TmpDC2,TmpObj2);
        DeleteDC(TmpDC2);
        ReleaseDC(hPPUView,pDC);

        DestroyWindow(hPPUView);
        hPPUView=NULL;
        PPUViewer=0;
        PPUViewSkip=0;
}

BOOL CALLBACK PPUViewCallB(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
        RECT wrect;
        char str[20];

        switch(uMsg) {
                case WM_INITDIALOG:
					if (PPUViewPosX==-32000) PPUViewPosX=0; //Just in case
					if (PPUViewPosY==-32000) PPUViewPosY=0;
                        SetWindowPos(hwndDlg,0,PPUViewPosX,PPUViewPosY,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOOWNERZORDER);

                        //prepare the bitmap attributes
                        //pattern tables
                        memset(&bmInfo.bmiHeader,0,sizeof(BITMAPINFOHEADER));
                        bmInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
                        bmInfo.bmiHeader.biWidth = PATTERNWIDTH;
                        bmInfo.bmiHeader.biHeight = PATTERNHEIGHT;
                        bmInfo.bmiHeader.biPlanes = 1;
                        bmInfo.bmiHeader.biBitCount = 24;

                        //palettes
                        memset(&bmInfo2.bmiHeader,0,sizeof(BITMAPINFOHEADER));
                        bmInfo2.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
                        bmInfo2.bmiHeader.biWidth = PALETTEWIDTH;
                        bmInfo2.bmiHeader.biHeight = PALETTEHEIGHT;
                        bmInfo2.bmiHeader.biPlanes = 1;
                        bmInfo2.bmiHeader.biBitCount = 24;

                        //create memory dcs
                        pDC = GetDC(hwndDlg); // GetDC(GetDlgItem(hwndDlg,GRP_PPUVIEW_TABLES));
                        TmpDC0 = CreateCompatibleDC(pDC); //pattern table 0
                        TmpDC1 = CreateCompatibleDC(pDC); //pattern table 1
                        TmpDC2 = CreateCompatibleDC(pDC); //palettes

                        //create bitmaps and select them into the memory dc's
                        TmpBmp0 = CreateDIBSection(pDC,&bmInfo,DIB_RGB_COLORS,(void**)&pattern0,0,0);
                        TmpObj0 = SelectObject(TmpDC0,TmpBmp0);
                        TmpBmp1 = CreateDIBSection(pDC,&bmInfo,DIB_RGB_COLORS,(void**)&pattern1,0,0);
                        TmpObj1 = SelectObject(TmpDC1,TmpBmp1);
                        TmpBmp2 = CreateDIBSection(pDC,&bmInfo2,DIB_RGB_COLORS,(void**)&ppuv_palette,0,0);
                        TmpObj2 = SelectObject(TmpDC2,TmpBmp2);

                        //Refresh Trackbar
                        SendDlgItemMessage(hwndDlg,CTL_PPUVIEW_TRACKBAR,TBM_SETRANGE,0,(LPARAM)MAKELONG(0,25));
                        SendDlgItemMessage(hwndDlg,CTL_PPUVIEW_TRACKBAR,TBM_SETPOS,1,PPUViewRefresh);

                        //Set Text Limit
                        SendDlgItemMessage(hwndDlg,IDC_PPUVIEW_SCANLINE,EM_SETLIMITTEXT,3,0);

                        //force redraw the first time the PPU Viewer is opened
                        PPUViewSkip=100;

                        //clear cache
                        memset(palcache,0,32);
                        memset(chrcache0,0,0x1000);
                        memset(chrcache1,0,0x1000);

                        PPUViewer=1;
                        break;
                case WM_PAINT:
                        PPUViewDoBlit();
                        break;
                case WM_CLOSE:
                case WM_QUIT:
                        KillPPUView();
                        break;
                case WM_MOVING:
                        break;
                case WM_MOVE:
						if (!IsIconic(hwndDlg)) {
                        GetWindowRect(hwndDlg,&wrect);
                        PPUViewPosX = wrect.left;
                        PPUViewPosY = wrect.top;

						#ifdef WIN32
						WindowBoundsCheckNoResize(PPUViewPosX,PPUViewPosY,wrect.right);
						#endif
						}
                        break;
                case WM_RBUTTONDBLCLK:
                case WM_RBUTTONDOWN:
                        mouse_x = GET_X_LPARAM(lParam);
                        mouse_y = GET_Y_LPARAM(lParam);
                        if(((mouse_x >= PATTERNDESTX) && (mouse_x < (PATTERNDESTX+(PATTERNWIDTH*ZOOM)))) && (mouse_y >= PATTERNDESTY) && (mouse_y < (PATTERNDESTY+(PATTERNHEIGHT*ZOOM)))) {
                                if(pindex0 == 7) pindex0 = 0;
                                else pindex0++;
                        }
                        else if(((mouse_x >= PATTERNDESTX+(PATTERNWIDTH*ZOOM)+1) && (mouse_x < (PATTERNDESTX+(PATTERNWIDTH*ZOOM)*2+1))) && (mouse_y >= PATTERNDESTY) && (mouse_y < (PATTERNDESTY+(PATTERNHEIGHT*ZOOM)))) {
                                if(pindex1 == 7) pindex1 = 0;
                                else pindex1++;
                        }
                        FCEUD_UpdatePPUView(-1, 0);
                        PPUViewDoBlit();
                        break;
                case WM_MOUSEMOVE:
                        mouse_x = GET_X_LPARAM(lParam);
                        mouse_y = GET_Y_LPARAM(lParam);
                        if(((mouse_x >= PATTERNDESTX) && (mouse_x < (PATTERNDESTX+(PATTERNWIDTH*ZOOM)))) && (mouse_y >= PATTERNDESTY) && (mouse_y < (PATTERNDESTY+(PATTERNHEIGHT*ZOOM)))) {
                                mouse_x = (mouse_x-PATTERNDESTX)/(8*ZOOM);
                                mouse_y = (mouse_y-PATTERNDESTY)/(8*ZOOM);
                                sprintf(str,"Tile: $%X%X",mouse_y,mouse_x);
                                SetDlgItemText(hwndDlg,LBL_PPUVIEW_TILE1,str);
                                SetDlgItemText(hwndDlg,LBL_PPUVIEW_TILE2,"Tile:");
                                SetDlgItemText(hwndDlg,LBL_PPUVIEW_PALETTES,"Palettes");
                        }
                        else if(((mouse_x >= PATTERNDESTX+(PATTERNWIDTH*ZOOM)+1) && (mouse_x < (PATTERNDESTX+(PATTERNWIDTH*ZOOM)*2+1))) && (mouse_y >= PATTERNDESTY) && (mouse_y < (PATTERNDESTY+(PATTERNHEIGHT*ZOOM)))) {
                                mouse_x = (mouse_x-(PATTERNDESTX+(PATTERNWIDTH*ZOOM)+1))/(8*ZOOM);
                                mouse_y = (mouse_y-PATTERNDESTY)/(8*ZOOM);
                                sprintf(str,"Tile: $%X%X",mouse_y,mouse_x);
                                SetDlgItemText(hwndDlg,LBL_PPUVIEW_TILE2,str);
                                SetDlgItemText(hwndDlg,LBL_PPUVIEW_TILE1,"Tile:");
                                SetDlgItemText(hwndDlg,LBL_PPUVIEW_PALETTES,"Palettes");
                        }
                        else if(((mouse_x >= PALETTEDESTX) && (mouse_x < (PALETTEDESTX+PALETTEWIDTH))) && (mouse_y >= PALETTEDESTY) && (mouse_y < (PALETTEDESTY+PALETTEHEIGHT))) {
                                mouse_x = (mouse_x-PALETTEDESTX)/32;
                                mouse_y = (mouse_y-PALETTEDESTY)/32;
                                sprintf(str,"Palette: $%02X",palcache[(mouse_y<<4)|mouse_x]);
                                SetDlgItemText(hwndDlg,LBL_PPUVIEW_TILE1,"Tile:");
                                SetDlgItemText(hwndDlg,LBL_PPUVIEW_TILE2,"Tile:");
                                SetDlgItemText(hwndDlg,LBL_PPUVIEW_PALETTES,str);
                        }
                        else {
                                SetDlgItemText(hwndDlg,LBL_PPUVIEW_TILE1,"Tile:");
                                SetDlgItemText(hwndDlg,LBL_PPUVIEW_TILE2,"Tile:");
                                SetDlgItemText(hwndDlg,LBL_PPUVIEW_PALETTES,"Palettes");
                        }

                        break;
                case WM_NCACTIVATE:
                        sprintf(str,"%d",PPUViewScanline);
                        SetDlgItemText(hwndDlg,IDC_PPUVIEW_SCANLINE,str);
                        break;
                case WM_COMMAND:
                        switch(HIWORD(wParam)) {
                                case EN_UPDATE:
                                        GetDlgItemText(hwndDlg,IDC_PPUVIEW_SCANLINE,str,4);
                                        sscanf(str,"%d",&PPUViewScanline);
                                        if(PPUViewScanline > 239) PPUViewScanline = 239;
                                        break;
                        }
                        break;
                case WM_HSCROLL:
                        if(lParam) { //refresh trackbar
                                PPUViewRefresh = SendDlgItemMessage(hwndDlg,CTL_PPUVIEW_TRACKBAR,TBM_GETPOS,0,0);
						}
                        break;
        }
        return FALSE;
}

void DoPPUView() {
        if(!GameInfo) {
                FCEUD_PrintError("You must have a game loaded before you can use the PPU Viewer.");
                return;
        }
        if(GameInfo->type==GIT_NSF) {
                FCEUD_PrintError("Sorry, you can't use the PPU Viewer with NSFs.");
                return;
        }

        if(!hPPUView) hPPUView = CreateDialog(fceu_hInstance,"PPUVIEW",NULL,PPUViewCallB);
        if(hPPUView) {
                SetWindowPos(hPPUView,HWND_TOP,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);
                FCEUD_UpdatePPUView(-1,1);
                PPUViewDoBlit();
        }
}
