/*
 * Copyright (c) 1996-2002 Nicolas HADACEK (hadacek@kde.org)
 *
 * 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 "field.h"
#include "field.moc"

#include <math.h>

#include <qlayout.h>
#include <qtimer.h>
#include <qpainter.h>

#include <klocale.h>
#include <knotifyclient.h>

#include "settings.h"
#include "solver/solver.h"
#include "dialogs.h"


using namespace KGrid2D;

const Field::ActionData Field::ACTION_DATA[Nb_Actions] = {
  { "Reveal",         "reveal",          I18N_NOOP("Case revealed")       },
  { "AutoReveal",     "autoreveal",      I18N_NOOP("Case autorevealed")   },
  { "SetFlag",        "mark",            I18N_NOOP("Flag set")            },
  { "UnsetFlag",      "unmark",          I18N_NOOP("Flag unset")          },
  { "SetUncertain",   "set_uncertain",   I18N_NOOP("Question mark set")   },
  { "UnsetUncertain", "unset_uncertain", I18N_NOOP("Question mark unset") }
};

Field::Field(QWidget *parent)
    : FieldFrame(parent), _state(Init), _solvingState(Regular), _level(Level::Easy)
{}

void Field::readSettings()
{
    if ( inside(_cursor) ) {
        QPainter p(this);
        drawCase(p, _cursor);
    }
    if ( Settings::magicReveal() ) emit setCheating();
}

QSize Field::sizeHint() const
{
  return QSize(2*frameWidth() + _level.width()*Settings::caseSize(),
               2*frameWidth() + _level.height()*Settings::caseSize());
}

void Field::setLevel(const Level &level)
{
    _level = level;
    reset(false);
    adjustSize();
}

void Field::setReplayField(const QString &field)
{
    setState(Replaying);
    initReplay(field);
}

void Field::setState(GameState state)
{
    Q_ASSERT( state!=GameOver );
    emit gameStateChanged(state);
    _state = state;
}

void Field::reset(bool init)
{
    BaseField::reset(_level.width(), _level.height(), _level.nbMines());
    if ( init || _state==Init ) setState(Init);
    else setState(Stopped);
    if (Settings::magicReveal()) emit setCheating();
    _currentAction = Settings::EnumMouseAction::None;
    _reveal = false;
    _cursor.first = _level.width()/2;
    _cursor.second = _level.height()/2;
    _advisedCoord = Coord(-1, -1);
    update();
}

void Field::paintEvent(QPaintEvent *e)
{
	QPainter painter(this);
	drawFrame(&painter);
	if ( _state==Paused ) return;

    Coord min = fromPoint(e->rect().topLeft());
    bound(min);
    Coord max = fromPoint(e->rect().bottomRight());
    bound(max);
	for (short i=min.first; i<=max.first; i++)
	    for (short j=min.second; j<=max.second; j++)
            drawCase(painter, Coord(i,j));
}

void Field::changeCase(const Coord &p, CaseState newState)
{
    BaseField::changeCase(p, newState);
    QPainter painter(this);
	drawCase(painter, p);
	if ( isActive() ) emit updateStatus( hasMine(p) );
}

QPoint Field::toPoint(const Coord &p) const
{
    QPoint qp;
    qp.setX( p.first*Settings::caseSize() + frameWidth() );
    qp.setY( p.second*Settings::caseSize() + frameWidth() );
    return qp;
}

Coord Field::fromPoint(const QPoint &qp) const
{
	double i = (double)(qp.x() - frameWidth()) / Settings::caseSize();
    double j = (double)(qp.y() - frameWidth()) / Settings::caseSize();
    return Coord((int)floor(i), (int)floor(j));
}

int Field::mapMouseButton(QMouseEvent *e) const
{
	switch (e->button()) {
	case Qt::LeftButton:  return Settings::mouseAction(Settings::EnumButton::left);
	case Qt::MidButton:   return Settings::mouseAction(Settings::EnumButton::mid);
	case Qt::RightButton: return Settings::mouseAction(Settings::EnumButton::right);
	default:              return Settings::EnumMouseAction::ToggleFlag;
	}
}

void Field::revealActions(bool press)
{
    if ( _reveal==press ) return; // avoid flicker
    _reveal = press;

	switch (_currentAction) {
	case Reveal:
		pressCase(_cursor, press);
		break;
	case AutoReveal:
		pressClearFunction(_cursor, press);
		break;
	default:
		break;
	}
}

void Field::mousePressEvent(QMouseEvent *e)
{
    if ( !isActive() || (_currentAction!=Settings::EnumMouseAction::None) ) return;

    emit setMood(Stressed);
    _currentAction = mapMouseButton(e);

    Coord p = fromPoint(e->pos());
    if ( !inside(p) ) return;
    placeCursor(p);
    revealActions(true);
}

void Field::mouseReleaseEvent(QMouseEvent *e)
{
    if ( !isActive() ) return;

    int tmp = _currentAction;
    emit setMood(Normal);
    revealActions(false);
    int ma = mapMouseButton(e);
    _currentAction = Settings::EnumMouseAction::None;
    if ( ma!=tmp ) return;

    Coord p = fromPoint(e->pos());
    if ( !inside(p) ) return;
    placeCursor(p);

    switch (ma) {
    case Settings::EnumMouseAction::ToggleFlag:          doMark(p); break;
    case Settings::EnumMouseAction::ToggleUncertainFlag: doUmark(p); break;
    case Settings::EnumMouseAction::Reveal:              doReveal(p); break;
    case Settings::EnumMouseAction::AutoReveal:          doAutoReveal(p); break;
    default: break;
    }
}

void Field::mouseMoveEvent(QMouseEvent *e)
{
	if ( !isActive() ) return;

    Coord p = fromPoint(e->pos());
    if ( p==_cursor ) return; // avoid flicker

	revealActions(false);
    if ( !inside(p) ) return;
    placeCursor(p);
	revealActions(true);
}

void Field::pressCase(const Coord &c, bool pressed)
{
	if ( state(c)==Covered ) {
        QPainter painter(this);
        drawCase(painter, c, pressed);
    }
}

void Field::pressClearFunction(const Coord &p, bool pressed)
{
    pressCase(p, pressed);
    CoordList n = coveredNeighbours(p);
    QPainter painter(this);
    for (CoordList::const_iterator it=n.begin(); it!=n.end(); ++it)
        drawCase(painter, *it, pressed);
}

void Field::keyboardAutoReveal()
{
	pressClearFunction(_cursor, true);
	QTimer::singleShot(50, this, SLOT(keyboardAutoRevealSlot()));
}

void Field::keyboardAutoRevealSlot()
{
	pressClearFunction(_cursor, false);
	doAutoReveal(_cursor);
}

void Field::doAutoReveal(const Coord &c)
{
	if ( !isActive() ) return;
    if ( state(c)!=Uncovered ) return;
    emit addAction(c, AutoReveal);
    resetAdvised();
    doAction(AutoReveal, c, Settings::magicReveal());
}

void Field::pause()
{
    switch (_state) {
    case Paused:  setState(Playing); break;
    case Playing: setState(Paused); break;
    default: return;
    }
    update();
}

void Field::moveCursor(Neighbour n)
{
    Coord c = neighbour(_cursor, n);
    if ( inside(c) ) placeCursor(c);
}

void Field::moveToEdge(Neighbour n)
{
    Coord c = toEdge(_cursor, n);
    if ( inside(c) ) placeCursor(c);
}

bool Field::doReveal(const Coord &c, CoordList *autorevealed,
                     bool *caseUncovered)
{
	if ( !isActive() ) return true;
    if ( state(c)!=Covered ) return true;
    if ( firstReveal() ) setState(Playing);
    CaseState state =
        doAction(Reveal, c, Settings::magicReveal(), autorevealed, caseUncovered);
    emit addAction(c, Reveal);
    return ( state!=Error );
}

void Field::doMark(const Coord &c)
{
	if ( !isActive() ) return;
    ActionType action;
    CaseState oldState = state(c);
    switch (oldState) {
	case Covered:   action = SetFlag; break;
	case Marked:    action = (Settings::uncertainMark() ? SetUncertain : UnsetFlag); break;
	case Uncertain:	action = UnsetUncertain; break;
	default:        return;
	}
    CaseState newState = doAction(action, c, Settings::magicReveal());
    addMarkAction(c, newState, oldState);
}

void Field::doUmark(const Coord &c)
{
	if ( !isActive() ) return;
    ActionType action;
    CaseState oldState = state(c);
    switch (oldState) {
	case Covered:
	case Marked:    action = SetUncertain; break;
	case Uncertain: action = UnsetUncertain; break;
	default:        return;
	}
    CaseState newState = doAction(action, c, Settings::magicReveal());
    addMarkAction(c, newState, oldState);
}


KMines::CaseState Field::doAction(ActionType type, const Coord &c,
                                  bool complete, CoordList *autorevealed,
                                  bool *caseUncovered)
{
    resetAdvised();
    CaseState state = Error;
    if ( _solvingState==Solved ) complete = false;

    KNotifyClient::event(winId(), ACTION_DATA[type].event,
                         i18n(ACTION_DATA[type].eventMessage));
    switch (type) {
    case Reveal:
        if ( !reveal(c, autorevealed, caseUncovered) )
            emit gameStateChanged(GameOver);
        else {
            state = Uncovered;
            if (complete) completeReveal();
        }
        break;
    case AutoReveal:
        if ( !autoReveal(c, caseUncovered) )
            emit gameStateChanged(GameOver);
        else {
            state = Uncovered;
            if (complete) completeReveal();
        }
        break;
    case SetFlag:
        state = Marked;
        if (complete) completeReveal();
        break;
    case UnsetFlag:
    case UnsetUncertain:
        state = Covered;
        break;
    case SetUncertain:
        state = Uncertain;
        break;
    case Nb_Actions:
        Q_ASSERT(false);
        break;
    }

    if ( state!=Error ) changeCase(c, state);
    return state;
}

void Field::addMarkAction(const Coord &c, CaseState newS, CaseState oldS)
{
    switch (newS) {
    case Marked:    emit addAction(c, SetFlag); return;
    case Uncertain: emit addAction(c, SetUncertain); return;
    default: break;
    }
    switch (oldS) {
    case Marked:    emit addAction(c, UnsetFlag); return;
    case Uncertain: emit addAction(c, UnsetUncertain); return;
    default: break;
    }
}

void Field::placeCursor(const Coord &p)
{
    if ( !isActive() ) return;

    Q_ASSERT( inside(p) );
    Coord old = _cursor;
    _cursor = p;
    if ( Settings::keyboardGame() ) {
        QPainter painter(this);
        drawCase(painter, old);
        drawCase(painter, _cursor);
    }
}

void Field::resetAdvised()
{
    if ( !inside(_advisedCoord) ) return;
    QPainter p(this);
    Coord tmp = _advisedCoord;
    _advisedCoord = Coord(-1, -1);
    drawCase(p, tmp);
}

void Field::setAdvised(const Coord &c, double proba)
{
    resetAdvised();
	_solvingState = Advised;
	_advisedCoord = c;
    _advisedProba = proba;
    if ( inside(c) ) {
        QPainter p(this);
        drawCase(p, c);
    }
}

void Field::drawCase(QPainter &painter, const Coord &c, bool pressed) const
{
	Q_ASSERT( inside(c) );

    QString text;
    uint nbMines = 0;
    PixmapType type = NoPixmap;

	switch ( state(c) ) {
    case Covered:
        break;
	case Marked:
        type = FlagPixmap;
        pressed = false;
        break;
	case Error:
        type = ErrorPixmap;
        pressed = true;
        break;
	case Uncertain:
        text = '?';
        pressed = false;
        break;
	case Exploded:
        type = ExplodedPixmap;
        pressed = true;
        break;
	case Uncovered:
        pressed = true;
        if ( hasMine(c) ) type = MinePixmap;
        else {
            nbMines = nbMinesAround(c);
            if (nbMines) text.setNum(nbMines);
        }
	}

    int i = -1;
    if ( c==_advisedCoord ) {
        if ( _advisedProba==1 ) i = 0;
        else if ( _advisedProba>0.75 ) i = 1;
        else if ( _advisedProba>0.5  ) i = 2;
        else if ( _advisedProba>0.25 ) i = 3;
        else i = 4;
    }

    bool hasFocus = ( Settings::keyboardGame() && (c==_cursor) );
    drawBox(painter, toPoint(c), pressed, type, text, nbMines, i, hasFocus);
}
