
#include <stdlib.h>
#include <kapplication.h>
#include <qlayout.h>
#include <qpainter.h>

#include "Editor.h"
#include "prefs.h"

#include <kmessagebox.h>
#include <klocale.h>     // Needed to use KLocale
#include <kiconloader.h> //
#include <kstandarddirs.h>
#include <ktoolbarradiogroup.h>

#define ID_TOOL_NEW  100
#define ID_TOOL_LOAD 101
#define ID_TOOL_SAVE 102
#define ID_TOOL_ADD 103
#define ID_TOOL_DEL 104
#define ID_TOOL_MOVE 105
#define ID_TOOL_SELECT 106
#define ID_TOOL_CUT 107
#define ID_TOOL_COPY 108
#define ID_TOOL_PASTE 109
#define ID_TOOL_LEFT 110
#define ID_TOOL_RIGHT 111
#define ID_TOOL_UP 112
#define ID_TOOL_DOWN 113

#define ID_TOOL_STATUS 199

#define ID_META_EXIT 201



// When we assign a tile to draw in a slot we do it in order from te following
// table, wrapping on the tile number. It makes the tile layout look more
// random.


Editor::Editor
(
	QWidget* parent,
	const char* name
)
    :
    QDialog( parent, name, TRUE, 0 ), tiles(false)
{

    clean= true;
    numTiles=0;
    mode = insert;

    int sWidth = (BoardLayout::width+2)*(tiles.qWidth());
    int sHeight =( BoardLayout::height+2)*tiles.qHeight();

    sWidth += 4*tiles.shadowSize();

    drawFrame = new FrameImage( this, "drawFrame" );
    drawFrame->setGeometry( 10, 40 ,sWidth ,sHeight);
    drawFrame->setMinimumSize( 0, 0 );
    drawFrame->setMaximumSize( 32767, 32767 );
    drawFrame->setFocusPolicy( QWidget::NoFocus );
    drawFrame->setBackgroundMode( QWidget::PaletteBackground );
    drawFrame->setFrameStyle( 49 );
    drawFrame->setMouseTracking(true);

   // setup the tool bar
   setupToolbar();

   QVBoxLayout *layout = new QVBoxLayout(this);
   layout->addWidget(topToolbar,0);
   layout->addWidget(drawFrame,1);
   layout->activate();

    resize( sWidth+60, sHeight+60);
    setMinimumSize( sWidth+60, sHeight+60);
    setMaximumSize( sWidth+60, sHeight+60);

   QString tile = "pics/" + Prefs::tileSet();
   tile = locate("appdata", tile);
   tiles.loadTileset(tile);

   // tell the user what we do
   setCaption(kapp->makeStdCaption(i18n("Edit Board Layout")));


   connect( drawFrame, SIGNAL(mousePressed(QMouseEvent *) ),
		SLOT(drawFrameMousePressEvent(QMouseEvent *)));
   connect( drawFrame, SIGNAL(mouseMoved(QMouseEvent *) ),
		SLOT(drawFrameMouseMovedEvent(QMouseEvent *)));

   statusChanged();

   update();
}



Editor::~Editor()
{
}

// ---------------------------------------------------------
void Editor::setupToolbar()
{

    KIconLoader *loader = KGlobal::iconLoader();
    topToolbar = new KToolBar( this, "editToolBar" );
    KToolBarRadioGroup *radio = new KToolBarRadioGroup(topToolbar);

    // new game
    topToolbar->insertButton(loader->loadIcon("filenew", KIcon::Toolbar),
            ID_TOOL_NEW, TRUE, i18n("New board"));
    // open game
    topToolbar->insertButton(loader->loadIcon("fileopen", KIcon::Toolbar),
            ID_TOOL_LOAD, TRUE, i18n("Open board"));
    // save game
    topToolbar->insertButton(loader->loadIcon("filesave", KIcon::Toolbar),
            ID_TOOL_SAVE, TRUE, i18n("Save board"));

#ifdef FUTURE_OPTIONS
    // Select
    topToolbar->insertSeparator();
    topToolbar->insertButton(loader->loadIcon("rectangle_select", KIcon::Toolbar),
            ID_TOOL_SELECT, TRUE, i18n("Select"));
    topToolbar->insertButton(loader->loadIcon("editcut", KIcon::Toolbar),
            ID_TOOL_CUT, TRUE, i18n("Cut"));
    topToolbar->insertButton(loader->loadIcon("editcopy", KIcon::Toolbar),
            ID_TOOL_COPY, TRUE, i18n("Copy"));
    topToolbar->insertButton(loader->loadIcon("editpaste", KIcon::Toolbar),
            ID_TOOL_PASTE, TRUE, i18n("Paste"));

    topToolbar->insertSeparator();
    topToolbar->insertButton(loader->loadIcon("move", KIcon::Toolbar),
            ID_TOOL_MOVE, TRUE, i18n("Move tiles"));
#endif
    topToolbar->insertButton(loader->loadIcon("pencil", KIcon::Toolbar),
            ID_TOOL_ADD, TRUE, i18n("Add tiles"));
    topToolbar->insertButton(loader->loadIcon("editdelete", KIcon::Toolbar),
            ID_TOOL_DEL, TRUE, i18n("Remove tiles"));

    topToolbar->setToggle(ID_TOOL_ADD);
    topToolbar->setToggle(ID_TOOL_MOVE);
    topToolbar->setToggle(ID_TOOL_DEL);
    topToolbar->toggleButton(ID_TOOL_ADD);
    radio->addButton(ID_TOOL_ADD);
#ifdef FUTURE_OPTIONS
    radio->addButton(ID_TOOL_MOVE);
#endif
    radio->addButton(ID_TOOL_DEL);

    // board shift

    topToolbar->insertSeparator();
    topToolbar->insertButton(loader->loadIcon("back", KIcon::Toolbar),
            ID_TOOL_LEFT, TRUE, i18n("Shift left"));
    topToolbar->insertButton(loader->loadIcon("up", KIcon::Toolbar),
            ID_TOOL_UP, TRUE, i18n("Shift up"));
    topToolbar->insertButton(loader->loadIcon("down", KIcon::Toolbar),
            ID_TOOL_DOWN, TRUE, i18n("Shift down"));
    topToolbar->insertButton(loader->loadIcon("forward", KIcon::Toolbar),
            ID_TOOL_RIGHT, TRUE, i18n("Shift right"));

    topToolbar->insertSeparator();
    topToolbar->insertButton(loader->loadIcon("exit", KIcon::Toolbar),
            ID_META_EXIT, TRUE, i18n("Exit"));

    // status in the toolbar for now (ick)

    theLabel = new QLabel(statusText(), topToolbar);
    int lWidth = theLabel->sizeHint().width();

    topToolbar->insertWidget(ID_TOOL_STATUS,lWidth, theLabel );
     topToolbar->alignItemRight( ID_TOOL_STATUS, true );

    //addToolBar(topToolbar);
   connect( topToolbar,  SIGNAL(clicked(int) ), SLOT( topToolbarOption(int) ) );

    topToolbar->updateRects(0);
     topToolbar->setFullSize(true);
    topToolbar->setBarPos(KToolBar::Top);
//    topToolbar->enableMoving(false);
    topToolbar->adjustSize();
    setMinimumWidth(topToolbar->width());


}

void Editor::statusChanged() {
	bool canSave = ((numTiles !=0) && ((numTiles & 1) == 0));
	theLabel->setText(statusText());
 	topToolbar->setItemEnabled( ID_TOOL_SAVE, canSave);
}


void Editor::topToolbarOption(int option) {

    switch(option) {
	case ID_TOOL_NEW:
		newBoard();
		break;
	case ID_TOOL_LOAD:
		loadBoard();
		break;
	case ID_TOOL_SAVE:
		saveBoard();
		break;
	case ID_TOOL_LEFT:
		theBoard.shiftLeft();
		repaint(false);
	        break;
	case ID_TOOL_RIGHT:
		theBoard.shiftRight();
		repaint(false);
	        break;
	case ID_TOOL_UP:
		theBoard.shiftUp();
		repaint(false);
	        break;
	case ID_TOOL_DOWN:
		theBoard.shiftDown();
		repaint(false);
	        break;
	case ID_TOOL_DEL:
			mode=remove;
		break;

	case ID_TOOL_MOVE:
			mode=move;
		break;

	case ID_TOOL_ADD:
			mode = insert;
		break;
	case ID_META_EXIT:
			close();
		break;

	default:

	break;
    }

}

QString Editor::statusText() {
	QString buf;

	int x=currPos.x;
	int y=currPos.y;
	int z= currPos.e;

	if (z == 100)
		z = 0;
	else
		z=z+1;

	if (x >=BoardLayout::width || x <0 || y >=BoardLayout::height || y <0)
		x = y = z = 0;

	buf = i18n("Tiles: %1 Pos: %2,%3,%4").arg(numTiles).arg(x).arg(y).arg(z);
	return buf;
}


void Editor::loadBoard() {

    if ( !testSave() )
	return;

    KURL url = KFileDialog::getOpenURL(
				NULL,
				i18n("*.layout|Board Layout (*.layout)\n"
				"*|All Files"),
				this,
				i18n("Open Board Layout" ));

   if ( url.isEmpty() )
        return;


    theBoard.loadBoardLayout( url.path() );

    repaint(false);
}


// Clear out the contents of the board. Repaint the screen
// set values to their defaults.
void Editor::newBoard() {
    if (!testSave())
	return;



    theBoard.clearBoardLayout();



    clean=true;
    numTiles=0;
    statusChanged();
    repaint(false);
}

bool Editor::saveBoard() {
    // get a save file name
    KURL url = KFileDialog::getSaveURL(
				NULL,
				i18n("*.layout|Board Layout (*.layout)\n"
				"*|All Files"),
				this,
				i18n("Save Board Layout" ));
   if( !url.isLocalFile() )
   {
      KMessageBox::sorry( this, i18n( "Only saving to local files currently supported." ) );
      return false;
   }

   if ( url.isEmpty() )
       return false;

    QFileInfo f( url.path() );
    if ( f.exists() ) {
	// if it already exists, querie the user for replacement
	int res=KMessageBox::warningYesNo(this,
			i18n("A file with that name "
					   "already exists. Do you "
					   "wish to overwrite it?"),
					i18n("Save Board Layout" ));
	if (res != KMessageBox::Yes)
		return false;
    }

    bool result = theBoard.saveBoardLayout( url.path() );
    if (result==true){
        clean = true;
        return true;
    } else {
	return false;
    }
}


// test if a save is required and return true if the app is to continue
// false if cancel is selected. (if ok then call out to save the board
bool Editor::testSave()
{

    if (clean)
	return(true);

    int res;
    res=KMessageBox::warningYesNoCancel(this,
	i18n("The board has been modified. Would you "
		"like to save the changes?"));

    if (res == KMessageBox::Yes) {
	// yes to save
	if (saveBoard()) {
  	    return true;
	} else {
	    KMessageBox::sorry(this, i18n("Save failed. Aborting operation."));
	}
    } else {
	return (res != KMessageBox::Cancel);
    }
    return(true);
}


// The main paint event, draw in the grid and blit in
// the tiles as specified by the layout.

void Editor::paintEvent( QPaintEvent*  ) {


    // first we layer on a background grid
    QPixmap buff;
    QPixmap *dest=drawFrame->getPreviewPixmap();
    buff.resize(dest->width(), dest->height());
    drawBackground(&buff);
    drawTiles(&buff);
    bitBlt(dest, 0,0,&buff, 0,0,buff.width(), buff.height(), CopyROP);

    drawFrame->repaint(false);
}

void Editor::drawBackground(QPixmap *pixmap) {

    QPainter p(pixmap);

    // blast in a white background
    p.fillRect(0,0,pixmap->width(), pixmap->height(), QColor(white));


    // now put in a grid of tile quater width squares
    int sy = (tiles.height()/2)+tiles.shadowSize();
    int sx = (tiles.width()/2);

    for (int y=0; y<=BoardLayout::height; y++) {
	int nextY=sy+(y*tiles.qHeight());
	p.drawLine(sx, nextY,sx+(BoardLayout::width*tiles.qWidth()), nextY);
    }

    for (int x=0; x<=BoardLayout::width; x++) {
	int nextX=sx+(x*tiles.qWidth());
	p.drawLine(nextX, sy, nextX, sy+BoardLayout::height*tiles.qHeight());
    }
}

void Editor::drawTiles(QPixmap *dest) {

    QPainter p(dest);

    QString tile1 = "pics/" + Prefs::tileSet();
    tile1 = locate("appdata", tile1);
    tiles.loadTileset(tile1);


    int xOffset = tiles.width()/2;
    int yOffset = tiles.height()/2;
    short tile = 0;

    // we iterate over the depth stacking order. Each successive level is
    // drawn one indent up and to the right. The indent is the width
    // of the 3d relief on the tile left (tile shadow width)
    for (int z=0; z<BoardLayout::depth; z++) {
        // we draw down the board so the tile below over rights our border
        for (int y = 0; y < BoardLayout::height; y++) {
            // drawing right to left to prevent border overwrite
            for (int x=BoardLayout::width-1; x>=0; x--) {
                int sx = x*(tiles.qWidth()  )+xOffset;
                int sy = y*(tiles.qHeight()  )+yOffset;
                if (theBoard.getBoardData(z, y, x) != '1') {
                    continue;
                }
		QPixmap *t;
		tile=(z*BoardLayout::depth)+
			(y*BoardLayout::height)+
				(x*BoardLayout::width);
//		if (mode==remove && currPos.x==x && currPos.y==y && currPos.e==z) {
//                   t = tiles.selectedPixmaps(44));
//		} else {
                   t = tiles.unselectedPixmaps(43);
//		}

                // Only one compilcation. Since we render top to bottom , left
                // to right situations arise where...:
                // there exists a tile one q height above and to the left
                // in this situation we would draw our top left border over it
                // we simply split the tile draw so the top half is drawn
                // minus border
                if ((x>1) && (y>0) && theBoard.getBoardData(z,y-1,x-2)=='1'){

                    bitBlt( dest,
                            sx+tiles.shadowSize(), sy,
                            t, tiles.shadowSize() ,0,
                            t->width()-tiles.shadowSize(),
                            t->height()/2, CopyROP );


                    bitBlt( dest, sx, sy+t->height()/2,
                        t, 0,t->height()/2,t->width(),t->height()/2,CopyROP);
                } else {

                bitBlt( dest, sx, sy,
                    t, 0,0, t->width(), t->height(), CopyROP );
                }


                tile++;
                tile = tile % 143;
            }
        }
        xOffset +=tiles.shadowSize();
        yOffset -=tiles.shadowSize();
    }
}


// convert mouse position on screen to a tile z y x coord
// different to the one in kmahjongg.cpp since if we hit ground
// we return a result too.

void Editor::transformPointToPosition(
        const QPoint& point,
        POSITION& MouseClickPos,
	bool align)
{

    short z = 0; // shut the compiler up about maybe uninitialised errors
    short y = 0;
    short x = 0;
    MouseClickPos.e = 100;

    // iterate over z coordinate from top to bottom
    for( z=BoardLayout::depth-1; z>=0; z-- )
    {
        // calculate mouse coordiantes --> position in game board
	// the factor -theTiles.width()/2 must keep track with the
	// offset for blitting in the print zvent (FIX ME)
        x = ((point.x()-tiles.width()/2)-(z+1)*tiles.shadowSize())/ tiles.qWidth();
        y = ((point.y()-tiles.height()/2)+ z*tiles.shadowSize()) / tiles.qHeight();


        // skip when position is illegal
        if (x<0 || x>=BoardLayout::width || y<0 || y>=BoardLayout::height)
		continue;

        //
        switch( theBoard.getBoardData(z,y,x) )
        {
	case (UCHAR)'3':    if (align) {
				    x--;
                                    y--;
	                        }
                                break;

            case (UCHAR)'2':    if (align)
				    x--;
                                break;

            case (UCHAR)'4':    if (align)
				    y--;
                                break;

            case (UCHAR)'1':    break;

            default :           continue;
        }
        // if gameboard is empty, skip
        if ( ! theBoard.getBoardData(z,y,x) )
		continue;

        // here, position is legal
        MouseClickPos.e = z;
        MouseClickPos.y = y;
        MouseClickPos.x = x;
        MouseClickPos.f = theBoard.getBoardData(z,y,x);
	break;
    }
    if (MouseClickPos.e == 100) {
    	MouseClickPos.x = x;
    	MouseClickPos.y = y;
    	MouseClickPos.f=0;
    }
}


// we swallow the draw frames mouse clicks and process here
void Editor::drawFrameMousePressEvent( QMouseEvent* e )
{

	POSITION mPos;
	transformPointToPosition(e->pos(), mPos, (mode == remove));

	switch (mode) {
		case remove:
		    if (!theBoard.tileAbove(mPos) && mPos.e < BoardLayout::depth && theBoard.isTileAt(mPos) ) {
			theBoard.deleteTile(mPos);
			numTiles--;
			statusChanged();
			drawFrameMouseMovedEvent(e);
			repaint(false);
		    }
		break;
		case insert: {
		    POSITION n = mPos;
		    if (n.e == 100)
			n.e = 0;
		    else
			n.e += 1;
		    if (canInsert(n)) {
			theBoard.insertTile(n);
			numTiles++;
			statusChanged();
			repaint(false);
		    }
		  }
		break;
		default:
		break;
	}

}


void Editor::drawCursor(POSITION &p, bool visible)
{
    int x = (tiles.width()/2)+(p.e*tiles.shadowSize())+(p.x * tiles.qWidth());
    int y = (tiles.height()/2)-(p.e*tiles.shadowSize())+(p.y * tiles.qHeight());
    int w = tiles.width();
    int h = tiles.height();


    if (p.e==100 || !visible)
	x = -1;
    drawFrame->setRect(x,y,w,h, tiles.shadowSize(), mode-remove);
    drawFrame->repaint(false);



}



// we swallow the draw frames mouse moves and process here
void Editor::drawFrameMouseMovedEvent( QMouseEvent* e ){


    POSITION mPos;
    transformPointToPosition(e->pos(), mPos, (mode == remove));

    if ((mPos.x==currPos.x) && (mPos.y==currPos.y) && (mPos.e==currPos.e))
	return;
    currPos = mPos;

    statusChanged();

    switch(mode) {
	case insert: {
		POSITION next;
		next = currPos;
		if (next.e == 100)
			next.e = 0;
		else
			next.e += 1;

		drawCursor(next, canInsert(next));
	break;
	}
	case remove:
    		drawCursor(currPos, 1);
	break;

	case move:

	break;

    }

}

// can we inser a tile here. We can iff
//	there are tiles in all positions below us (or we are a ground level)
//	there are no tiles intersecting with us on this level

bool Editor::canInsert(POSITION &p) {


    if (p.e >= BoardLayout::depth)
	return (false);
    if (p.y >BoardLayout::height-2)
	return false;
    if (p.x >BoardLayout::width-2)
	return false;

    POSITION n = p;
    if (p.e != 0) {
	n.e -= 1;
	if (!theBoard.allFilled(n)) {
	    return(false);
	}
    }
    int any = theBoard.anyFilled(p);
    return(!any);

}



#include "Editor.moc"
