// Copyright (c) Warwick Allison, 1999.
// Qt4 conversion copyright (c) Ray Chason, 2012-2014.
// NetHack may be freely redistributed.  See license for details.

// qt4menu.cpp -- a menu or text-list widget

extern "C" {
#include "hack.h"
}
#undef Invisible
#undef Warning
#undef index
#undef msleep
#undef rindex
#undef wizard
#undef yn
#undef min
#undef max

#include <QtGui/QtGui>
#if QT_VERSION >= 0x050000
#include <QtWidgets/QtWidgets>
#endif
#include "qt4menu.h"
#include "qt4menu.moc"
#include "qt4glyph.h"
#include "qt4set.h"
#include "qt4streq.h"
#include "qt4str.h"

// temporary
extern "C" int qt_compact_mode;
// end temporary

extern "C" struct menucoloring *menu_colorings;

namespace nethack_qt4 {

// temporary
void centerOnMain( QWidget* w );
// end temporary

QSize NetHackQtTextListBox::sizeHint() const
{
    QScrollBar *hscroll = horizontalScrollBar();
    int hsize = hscroll ? hscroll->height() : 0;
    return QSize(TotalWidth()+hsize, TotalHeight()+hsize);
}

int NetHackQtMenuListBox::TotalWidth() const
{
    int width = 0;

    for (int col = 0; col < columnCount(); ++col) {
	width += columnWidth(col);
    }
    return width;
}

int NetHackQtMenuListBox::TotalHeight() const
{
    int height = 0;

    for (int row = 0; row < rowCount(); ++row) {
	height += rowHeight(row);
    }
    return height;
}

QSize NetHackQtMenuListBox::sizeHint() const
{
    QScrollBar *hscroll = horizontalScrollBar();
    int hsize = hscroll ? hscroll->height() : 0;
    return QSize(TotalWidth()+hsize, TotalHeight()+hsize);
}

// Table view columns:
// 
// [pick-count] [accel] [glyph] [string]
// 
// Maybe accel should be near string.  We'll see.
// pick-count normally blank.
//   double-clicking or click-on-count gives pop-up entry
// string is green when selected
//
NetHackQtMenuWindow::NetHackQtMenuWindow(QWidget *parent) :
    QDialog(parent),
    table(new NetHackQtMenuListBox()),
    prompt(0),
    counting(false)
{
    QGridLayout *grid = new QGridLayout();
    table->setColumnCount(5);
    table->setFrameStyle(QFrame::Panel|QFrame::Sunken);
    table->setLineWidth(2);
    table->setShowGrid(false);
    table->horizontalHeader()->hide();
    table->verticalHeader()->hide();

    ok=new QPushButton("Ok");
    connect(ok,SIGNAL(clicked()),this,SLOT(accept()));

    cancel=new QPushButton("Cancel");
    connect(cancel,SIGNAL(clicked()),this,SLOT(reject()));

    all=new QPushButton("All");
    connect(all,SIGNAL(clicked()),this,SLOT(All()));

    none=new QPushButton("None");
    connect(none,SIGNAL(clicked()),this,SLOT(ChooseNone()));

    invert=new QPushButton("Invert");
    connect(invert,SIGNAL(clicked()),this,SLOT(Invert()));

    search=new QPushButton("Search");
    connect(search,SIGNAL(clicked()),this,SLOT(Search()));

    QPoint pos(0,ok->height());
    move(pos);
    prompt.setParent(this,0);
    prompt.move(pos);

    grid->addWidget(ok, 0, 0);
    grid->addWidget(cancel, 0, 1);
    grid->addWidget(all, 0, 2);
    grid->addWidget(none, 0, 3);
    grid->addWidget(invert, 0, 4);
    grid->addWidget(search, 0, 5);
    grid->addWidget(&prompt, 1, 0, 1, 7);
    grid->addWidget(table, 2, 0, 1, 7);
    grid->setColumnStretch(6, 1);
    grid->setRowStretch(2, 1);
    setFocusPolicy(Qt::StrongFocus);
    table->setFocusPolicy(Qt::NoFocus);
    connect(table, SIGNAL(cellClicked(int,int)), this, SLOT(cellToggleSelect(int,int)));

    setLayout(grid);
}

NetHackQtMenuWindow::~NetHackQtMenuWindow()
{
}

QWidget* NetHackQtMenuWindow::Widget() { return this; }

void NetHackQtMenuWindow::StartMenu()
{
    table->setRowCount((itemcount=0));
    next_accel=0;
    has_glyphs=false;
}

NetHackQtMenuWindow::MenuItem::MenuItem() :
    str("")
{
}

NetHackQtMenuWindow::MenuItem::~MenuItem()
{
}

void NetHackQtMenuWindow::AddMenu(int glyph, const ANY_P* identifier,
	char ch, char gch, int attr, const QString& str, bool presel)
{
    if (!ch && identifier->a_void!=0) {
	// Supply a keyboard accelerator.  Limited supply.
	static char accel[]="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
	if (accel[next_accel]) {
	    ch=accel[next_accel++];
	}
    }

    if ((int)itemlist.size() < itemcount+1) {
	itemlist.resize(itemcount*4+10);
    }
    itemlist[itemcount].glyph=glyph;
    itemlist[itemcount].identifier=*identifier;
    itemlist[itemcount].ch=ch;
    itemlist[itemcount].gch=gch;
    itemlist[itemcount].attr=attr;
    itemlist[itemcount].str=str;
    itemlist[itemcount].selected=presel;
    itemlist[itemcount].count=-1;
    itemlist[itemcount].color = -1;
    // Display the boulder symbol correctly
    if (str.left(8) == "boulder\t") {
	int bracket = str.indexOf('[');
	if (bracket != -1) {
	    itemlist[itemcount].str = str.left(bracket+1)
		+ QChar(cp437(str.at(bracket+1).unicode()))
		+ str.mid(bracket+2);
	}
    }
    int mcolor, mattr;
    if (attr == 0
        && get_menu_coloring(str.toLatin1().constData(), &mcolor, &mattr)) {
	itemlist[itemcount].attr = mattr;
	itemlist[itemcount].color = mcolor;
    }
    ++itemcount;

    if (glyph!=NO_GLYPH) has_glyphs=true;
}

void NetHackQtMenuWindow::EndMenu(const QString& p)
{
    prompt.setText(p);
    promptstr = p;
}

int NetHackQtMenuWindow::SelectMenu(int h, MENU_ITEM_P **menu_list)
{
    QFont tablefont(qt_settings->normalFont());
    table->setFont(tablefont);

    table->setRowCount(itemcount);

    how=h;

    ok->setEnabled(how!=PICK_ONE);ok->setDefault(how!=PICK_ONE);
    cancel->setEnabled(how!=PICK_NONE);
    all->setEnabled(how==PICK_ANY);
    none->setEnabled(how==PICK_ANY);
    invert->setEnabled(how==PICK_ANY);
    search->setEnabled(how!=PICK_NONE);

    setResult(-1);

    // Set contents of table
    QFontMetrics fm(table->font());
    for (int i = 0; i < 5; i++) {
	table->setColumnWidth(i, 0);
    }
    for (int i = 0; i < itemcount; i++) {
	AddRow(i, itemlist[i]);
    }

    // Determine column widths
    std::vector<int> col_widths;
    for (std::size_t i = 0; i < itemlist.size(); ++i) {
	QStringList columns = itemlist[i].str.split("\t");
        if (!itemlist[i].Selectable() && columns.size() == 1)
        {
            // Nonselectable line with no column dividers
            // Assume this is a section header
            continue;
        }
	for (std::size_t j = 0U; j < columns.size(); ++j) {
	    int w = fm.width(columns[j] + "  \t");
	    if (j >= col_widths.size()) {
		col_widths.push_back(w);
	    } else if (col_widths[j] < w) {
		col_widths[j] = w;
	    }
	}
    }

    // Pad each column to its column width
    for (std::size_t i = 0U; i < itemlist.size(); ++i) {
	QTableWidgetItem *twi = table->item(i, 4);
	if (twi == NULL) { continue; }
	QString text = twi->text();
	QStringList columns = text.split("\t");
	for (std::size_t j = 0U; j+1U < columns.size(); ++j) {
	    columns[j] += "\t";
	    int width = col_widths[j];
	    while (fm.width(columns[j]) < width) {
		columns[j] += "\t";
	    }
	}
	text = columns.join("");
	twi->setText(text);
	WidenColumn(4, fm.width(text));
    }

    // FIXME:  size for compact mode
    //resize(this->width(), parent()->height()*7/8);
    move(0, 0);
    adjustSize();
    centerOnMain(this);
    exec();
    int result=this->result();

    *menu_list=0;
    if (result>0 && how!=PICK_NONE) {
	if (how==PICK_ONE) {
	    int i;
	    for (i=0; i<itemcount && !isSelected(i); i++)
		;
	    if (i<itemcount) {
		*menu_list=(MENU_ITEM_P *)alloc(sizeof(MENU_ITEM_P)*1);
		(*menu_list)[0].item=itemlist[i].identifier;
		(*menu_list)[0].count=count(i);
		return 1;
	    } else {
		return 0;
	    }
	} else {
	    int selcount=0;
	    for (int i=0; i<itemcount; i++)
		if (isSelected(i)) selcount++;
	    if (selcount) {
		*menu_list=(MENU_ITEM_P *)alloc(sizeof(MENU_ITEM_P)*selcount);
		int j=0;
		for (int i=0; i<itemcount; i++) {
		    if (isSelected(i)) {
			(*menu_list)[j].item=itemlist[i].identifier;
			(*menu_list)[j].count=count(i);
			j++;
		    }
		}
		return selcount;
	    } else {
		return 0;
	    }
	}
    } else {
	return -1;
    }
}

void NetHackQtMenuWindow::AddRow(int row, const MenuItem& mi)
{
    static const QColor colors[] = {
	QColor(64, 64, 64),
	QColor(Qt::red),
	QColor(0, 191, 0),
	QColor(127, 127, 0),
	QColor(Qt::blue),
	QColor(Qt::magenta),
	QColor(Qt::cyan),
	QColor(Qt::gray),
	QColor(Qt::white),
	QColor(255, 127, 0),
	QColor(127, 255, 127),
	QColor(Qt::yellow),
	QColor(127, 127, 255),
	QColor(255, 127, 255),
	QColor(127, 255, 255),
	QColor(Qt::white)
    };
    QFontMetrics fm(table->font());
    QTableWidgetItem *twi;

    if (mi.Selectable() && how != PICK_NONE) {
	// Count
	twi = new QTableWidgetItem("");
	table->setItem(row, 0, twi);
	twi->setFlags(Qt::ItemIsEnabled);
	WidenColumn(0, fm.width("999999"));
	// Check box, set if selected
	QCheckBox *cb = new QCheckBox();
	cb->setChecked(mi.selected);
	cb->setFocusPolicy(Qt::NoFocus);
	if (how == PICK_ONE)
	    connect(cb, SIGNAL(clicked(bool)), this, SLOT(DoSelection(bool)));
	table->setCellWidget(row, 1, cb);
	WidenColumn(1, cb->width());
    }
    if (mi.glyph != NO_GLYPH) {
	// Icon
	QPixmap pm(qt_settings->glyphs().glyph(mi.glyph));
	twi = new QTableWidgetItem(QIcon(pm), "");
	table->setItem(row, 2, twi);
	twi->setFlags(Qt::ItemIsEnabled);
	WidenColumn(2, pm.width());
    }
    QString letter, text(mi.str);
    if (mi.ch != 0) {
	// Letter specified
	letter = QString(mi.ch) + " - ";
    }
    else {
	// Letter is left blank, except for skills display when # and * are
	// presented
	if (text.startsWith("    ")) {
	    // If mi.str starts with "    ", it's meant to line up with lines
	    // that have a letter; we don't want that here
	    text = text.mid(4);
	} else if (text.startsWith("   #") || text.startsWith("   *")) {
	    // Put the * or # in the letter column
	    letter = text.left(4);
	    text = text.mid(4);
	}
    }
    twi = new QTableWidgetItem(letter);
    table->setItem(row, 3, twi);
    table->item(row, 3)->setFlags(Qt::ItemIsEnabled);
    WidenColumn(3, fm.width(letter));
    twi = new QTableWidgetItem(text);
    table->setItem(row, 4, twi);
    table->item(row, 4)->setFlags(Qt::ItemIsEnabled);
    WidenColumn(4, fm.width(text));

    if (mi.color != -1) {
	twi->setForeground(colors[mi.color]);
    }

    QFont itemfont(table->font());
    switch (mi.attr) {
    case ATR_BOLD:
	itemfont.setWeight(QFont::Bold);
	twi->setFont(itemfont);
	break;

    case ATR_DIM:
	twi->setFlags(Qt::NoItemFlags);
	break;

    case ATR_ULINE:
	itemfont.setUnderline(true);
	twi->setFont(itemfont);
	break;

    case ATR_INVERSE:
	{
	    QBrush fg = twi->foreground();
	    QBrush bg = twi->background();
	    if (fg == bg) {
		// default foreground and background come up the same for
		// some unknown reason
		twi->setForeground(Qt::white);
		twi->setBackground(Qt::black);
	    } else {
		twi->setForeground(bg);
		twi->setBackground(fg);
	    }
	}
	break;
    }
}

void NetHackQtMenuWindow::WidenColumn(int column, int width)
{
    // need to add a bit so the whole column displays
    width += 7;
    if (table->columnWidth(column) < width) {
	table->setColumnWidth(column, width);
    }
}

void NetHackQtMenuWindow::InputCount(char key)
{
    if (key == '\b')
    {
	if (counting)
	{
	    if (countstr.isEmpty())
		ClearCount();
	    else
		countstr = countstr.mid(0, countstr.size() - 1);
	}
    }
    else
    {
	counting = true;
	countstr += QChar(key);
    }
    if (counting)
	prompt.setText("Count: " + countstr);
}

void NetHackQtMenuWindow::ClearCount(void)
{
    counting = false;
    prompt.setText(promptstr);
    countstr = "";
}

void NetHackQtMenuWindow::keyPressEvent(QKeyEvent* event)
{
    QString text = event->text();

    const QChar *uni = text.unicode();
    for (unsigned k = 0; uni[k] != 0; k++) {
	unsigned key = uni[k].unicode();
	if (key=='\033') {
	    if (counting)
		ClearCount();
	    else
		reject();
	} else if (key=='\r' || key=='\n' || key==' ')
	    accept();
	else if (key==MENU_SEARCH)
	    Search();
	else if (key==MENU_SELECT_ALL || key==MENU_SELECT_PAGE)
	    All();
	else if (key==MENU_INVERT_ALL || key==MENU_INVERT_PAGE)
	    Invert();
	else if (key==MENU_UNSELECT_ALL || key==MENU_UNSELECT_PAGE)
	    ChooseNone();
	else if (('0' <= key && key <= '9') || key == '\b')
	    InputCount(key);
	else {
	    for (int i=0; i<itemcount; i++) {
		if (itemlist[i].ch == key || itemlist[i].gch == key)
		    ToggleSelect(i);
	    }
	}
    }
}

void NetHackQtMenuWindow::All()
{
    if (how != PICK_ANY)
        return;

    for (int i=0; i<itemcount; i++) {
	QTableWidgetItem *count = table->item(i, 0);
	if (count != NULL) count->setText("");

	QCheckBox *cb = dynamic_cast<QCheckBox *>(table->cellWidget(i, 1));
	if (cb != NULL) cb->setChecked(true);
    }
}
void NetHackQtMenuWindow::ChooseNone()
{
    if (how != PICK_ANY)
        return;

    for (int i=0; i<itemcount; i++) {
	QTableWidgetItem *count = table->item(i, 0);
	if (count != NULL) count->setText("");

	QCheckBox *cb = dynamic_cast<QCheckBox *>(table->cellWidget(i, 1));
	if (cb != NULL) cb->setChecked(false);
    }
}
void NetHackQtMenuWindow::Invert()
{
    if (how != PICK_ANY)
        return;

    for (int i=0; i<itemcount; i++) {
	QTableWidgetItem *count = table->item(i, 0);
	if (count != NULL) count->setText("");

	QCheckBox *cb = dynamic_cast<QCheckBox *>(table->cellWidget(i, 1));
	if (cb != NULL) cb->setChecked(cb->checkState() == Qt::Unchecked);
    }
}
void NetHackQtMenuWindow::Search()
{
    if (how == PICK_NONE)
        return;

    NetHackQtStringRequestor requestor(this, "Search for:");
    char line[256];
    if (requestor.Get(line)) {
	for (int i=0; i<itemcount; i++) {
	    if (itemlist[i].str.contains(line))
		ToggleSelect(i);
	}
    }
}
void NetHackQtMenuWindow::ToggleSelect(int i)
{
    if (itemlist[i].Selectable()) {
	QCheckBox *cb = dynamic_cast<QCheckBox *>(table->cellWidget(i, 1));
	if (cb == NULL) return;

	cb->setChecked((counting && !countstr.isEmpty())
		    || cb->checkState() == Qt::Unchecked);

	QTableWidgetItem *count = table->item(i, 0);
	if (count != NULL) count->setText(countstr);

	ClearCount();

	if (how==PICK_ONE) {
	    accept();
	}
    }
}

void NetHackQtMenuWindow::cellToggleSelect(int i, int j)
{
    ToggleSelect(i);
}

void NetHackQtMenuWindow::DoSelection(bool)
{
    if (how == PICK_ONE) {
	accept();
    }
}

bool NetHackQtMenuWindow::isSelected(int row)
{
    QCheckBox *cb = dynamic_cast<QCheckBox *>(table->cellWidget(row, 1));
    return cb != NULL && cb->checkState() != Qt::Unchecked;
}

int NetHackQtMenuWindow::count(int row)
{
    QTableWidgetItem *count = table->item(row, 0);
    if (count == NULL) return -1;
    QString cstr = count->text();
    return cstr.isEmpty() ? -1 : cstr.toInt();
}

NetHackQtTextWindow::NetHackQtTextWindow(QWidget *parent) :
    QDialog(parent),
    use_rip(false),
    str_fixed(false),
    ok("Dismiss",this),
    search("Search",this),
    lines(new NetHackQtTextListBox(this)),
    rip(this)
{
    ok.setDefault(true);
    connect(&ok,SIGNAL(clicked()),this,SLOT(accept()));
    connect(&search,SIGNAL(clicked()),this,SLOT(Search()));
    connect(qt_settings,SIGNAL(fontChanged()),this,SLOT(doUpdate()));

    QVBoxLayout* vb = new QVBoxLayout(this);
    vb->addWidget(&rip);
    QHBoxLayout* hb = new QHBoxLayout();
    vb->addLayout(hb);
    hb->addWidget(&ok);
    hb->addWidget(&search);
    vb->addWidget(lines);
}

void NetHackQtTextWindow::doUpdate()
{
    update();
}


NetHackQtTextWindow::~NetHackQtTextWindow()
{

}

QWidget* NetHackQtTextWindow::Widget()
{
    return this;
}

bool NetHackQtTextWindow::Destroy()
{
    return !isVisible();
}

void NetHackQtTextWindow::UseRIP(int how, time_t when)
{
// Code from X11 windowport
#define STONE_LINE_LEN 16    /* # chars that fit on one line */
#define NAME_LINE 0	/* line # for player name */
#define GOLD_LINE 1	/* line # for amount of gold */
#define DEATH_LINE 2	/* line # for death description */
#define YEAR_LINE 6	/* line # for year */

static char** rip_line=0;
    if (!rip_line) {
	rip_line=new char*[YEAR_LINE+1];
	for (int i=0; i<YEAR_LINE+1; i++) {
	    rip_line[i]=new char[STONE_LINE_LEN+1];
	}
    }

    /* Follows same algorithm as genl_outrip() */

    char buf[BUFSZ];
    char *dpx;
    int line;

    /* Put name on stone */
    snprintf(rip_line[NAME_LINE], STONE_LINE_LEN+1, "%s", g.plname);

    /* Put $ on stone */
    snprintf(rip_line[GOLD_LINE], STONE_LINE_LEN+1, "%ld Au", money_cnt(g.invent));

    /* Put together death description */
    formatkiller(buf, sizeof buf, how, FALSE);
    //str_copy(buf, killer, SIZE(buf));

    /* Put death type on stone */
    for (line=DEATH_LINE, dpx = buf; line<YEAR_LINE; line++) {
	int i,i0;
	char tmpchar;

	if ( (i0=strlen(dpx)) > STONE_LINE_LEN) {
	    for(i = STONE_LINE_LEN;
		((i0 > STONE_LINE_LEN) && i); i--)
		if(dpx[i] == ' ') i0 = i;
	    if(!i) i0 = STONE_LINE_LEN;
	}
	tmpchar = dpx[i0];
	dpx[i0] = 0;
	str_copy(rip_line[line], dpx, STONE_LINE_LEN+1);
	if (tmpchar != ' ') {
	    dpx[i0] = tmpchar;
	    dpx= &dpx[i0];
	} else  dpx= &dpx[i0+1];
    }

    /* Put year on stone */
    snprintf(rip_line[YEAR_LINE], STONE_LINE_LEN+1, "%4d", getyear());

    rip.setLines(rip_line,YEAR_LINE+1);

    use_rip=true;
}

void NetHackQtTextWindow::Clear()
{
    lines->clear();
    use_rip=false;
    str_fixed=false;
}

void NetHackQtTextWindow::Display(bool block)
{
    if (str_fixed) {
	lines->setFont(qt_settings->normalFixedFont());
    } else {
	lines->setFont(qt_settings->normalFont());
    }

    int h=0;
    if (use_rip) {
	h+=rip.height();
	ok.hide();
	search.hide();
	rip.show();
    } else {
	h+=ok.height()*2 + 7;
	ok.show();
	search.show();
	rip.hide();
    }
    int mh = QApplication::desktop()->height()*3/5;
    if ( (qt_compact_mode && lines->TotalHeight() > mh) || use_rip ) {
	// big, so make it fill
	showMaximized();
    } else {
	move(0, 0);
	adjustSize();
	centerOnMain(this);
	show();
    }
    exec();
}

void NetHackQtTextWindow::PutStr(int attr, const QString& text)
{
    str_fixed=str_fixed || text.contains("    ");
    lines->addItem(text);
}

void NetHackQtTextWindow::Search()
{
    NetHackQtStringRequestor requestor(this, "Search for:");
    static char line[256]="";
    requestor.SetDefault(line);
    if (requestor.Get(line)) {
	int current=lines->currentRow();
	for (int i=1; i<lines->count(); i++) {
	    int lnum=(i+current)%lines->count();
	    QString str=lines->item(lnum)->text();
	    if (str.contains(line)) {
		lines->setCurrentRow(lnum);
		return;
	    }
	}
	lines->setCurrentItem(NULL);
    }
}

NetHackQtMenuOrTextWindow::NetHackQtMenuOrTextWindow(QWidget *parent_) :
    actual(0),
    parent(parent_)
{
}

QWidget* NetHackQtMenuOrTextWindow::Widget()
{
    if (!actual) impossible("Widget called before we know if Menu or Text");
    return actual->Widget();
}

// Text
void NetHackQtMenuOrTextWindow::Clear()
{
    if (!actual) impossible("Clear called before we know if Menu or Text");
    actual->Clear();
}
void NetHackQtMenuOrTextWindow::Display(bool block)
{
    if (!actual) impossible("Display called before we know if Menu or Text");
    actual->Display(block);
}
bool NetHackQtMenuOrTextWindow::Destroy()
{
    if (!actual) impossible("Destroy called before we know if Menu or Text");
    return actual->Destroy();
}

void NetHackQtMenuOrTextWindow::PutStr(int attr, const QString& text)
{
    if (!actual) actual=new NetHackQtTextWindow(parent);
    actual->PutStr(attr,text);
}

// Menu
void NetHackQtMenuOrTextWindow::StartMenu()
{
    if (!actual) actual=new NetHackQtMenuWindow(parent);
    actual->StartMenu();
}
void NetHackQtMenuOrTextWindow::AddMenu(int glyph, const ANY_P* identifier, char ch, char gch, int attr,
	const QString& str, bool presel)
{
    if (!actual) impossible("AddMenu called before we know if Menu or Text");
    actual->AddMenu(glyph,identifier,ch,gch,attr,str,presel);
}
void NetHackQtMenuOrTextWindow::EndMenu(const QString& prompt)
{
    if (!actual) impossible("EndMenu called before we know if Menu or Text");
    actual->EndMenu(prompt);
}
int NetHackQtMenuOrTextWindow::SelectMenu(int how, MENU_ITEM_P **menu_list)
{
    if (!actual) impossible("SelectMenu called before we know if Menu or Text");
    return actual->SelectMenu(how,menu_list);
}

} // namespace nethack_qt4
