/***************************************************************************
			  MenuNode.cpp  -  generic menu node type
			     -------------------
    begin                : Mon Jan 10 2000
    copyright            : (C) 2000 by Thomas Eschenbacher
    email                : Thomas.Eschenbacher@gmx.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "config.h"
#include <stdio.h>
#include <qdict.h>
#include <kapp.h>

#include "libkwave/Parser.h"

#include "MenuNode.h"
#include "MenuGroup.h"

/** unique id for menus */
static int unique_menu_id = 0;

//static int menu_node_count = 0;

MenuNode::MenuNode(MenuNode *parent, const QString &name,
	const QString &command, int key, const QString &uid)
    :QObject(),
    m_groups(),
    m_icon(0)
{
//    menu_node_count++;
//    qDebug("MenuNode::MenuNode(): node count=%d", menu_node_count);

    m_parentNode = parent;
    m_name = name;
    m_command = command;
    m_key = key;
    m_uid = uid;
    m_enabled = true;
    m_last_enabled = true;
    m_checked = false;
    m_id = -1;

    m_groups.clear();
    m_children.setAutoDelete(false);
}

//*****************************************************************************
MenuNode::~MenuNode()
{
    // leave all groups
    QString group = m_groups.first();
    while (!group.isNull()) {
	leaveGroup(group);
	group = m_groups.first();
    }

    clear();

    // deregister from our parent
    if (m_parentNode) m_parentNode->removeChild(this);
}

//*****************************************************************************
void MenuNode::emitCommand(const QString &command)
{
    Q_ASSERT(command.length());
    if (!command.length()) return ;

    if (!getParentNode()) {
	// no parent -> we are the root node -> we have to emit
	emit sigCommand(command);
    } else {
	// tell the root node to emit
	MenuNode *root = getRootNode();
	Q_ASSERT(root);
	if (root) root->emitCommand(command);
    }
}

//*****************************************************************************
void MenuNode::actionSelected()
{
    if (m_command.length()) emitCommand(m_command);
}

//*****************************************************************************
void MenuNode::actionChildEnableChanged(int /*id*/, bool /*enable*/)
{
}

//*****************************************************************************
/*
void MenuNode::slotHilighted(int id)
{
  // (this is useful for debugging menu ids)
  qDebug("MenuNode::hilight(%d)", id);
  MenuNode *parent = getParent();
  setCheckable(true);
  if (parent) {
      for (int i=0; i < 300; i++) {
	  parent->setItemChecked(i, false);
      }
      parent->setItemChecked(id, true);
  }
}
*/

//*****************************************************************************
void MenuNode::clear()
{
    // clear all children
    MenuNode *child = m_children.first();
    while (child) {
	removeChild(child);
	delete child;
	child = m_children.first();
    }
}

//*****************************************************************************
int MenuNode::getChildIndex(int /*id*/)
{
    return -1;
}

//*****************************************************************************
MenuNode *MenuNode::getParentNode()
{
    return m_parentNode;
}

//*****************************************************************************
MenuNode *MenuNode::getRootNode()
{
    return (m_parentNode) ? m_parentNode->getRootNode() : this;
}

//*****************************************************************************
const QPixmap &MenuNode::getIcon()
{
    return m_icon;
}

//*****************************************************************************
void MenuNode::setIcon(const QPixmap icon)
{
    m_icon = icon;
    if (m_parentNode) m_parentNode->setItemIcon(m_id, icon);
}

//*****************************************************************************
void MenuNode::setItemIcon(int id, const QPixmap &icon)
{
    qDebug("MenuNode(%s)::setItemIcon(%d, %p)",
           getName().local8Bit().data(), id,
           (void*)&icon);
}

//*****************************************************************************
bool MenuNode::isEnabled()
{
    // evaluate our own (individual) enable and our parent's enable state
    if (!m_enabled) return false;
    if ((m_parentNode != 0) && !m_parentNode->isEnabled()) return false;

    // find  out if all our groups are anabled
    MenuNode *root = getRootNode();
    if (root) {
	QStringList::Iterator it = m_groups.begin();
	for ( ; it != m_groups.end(); ++it) {
	    Q_ASSERT(it != 0);
	    QString group_name = *it;
	    MenuNode *group = root->findUID(group_name);
	    if (group && group->inherits("MenuGroup")) {
		if (!((MenuGroup*)group)->isEnabled()) {
		    qDebug("MenuNode(%s).isEnabled(): group %s is disabled",
			   getName().local8Bit().data(),
			   group_name.local8Bit().data());
		    return false;
		}
	    }
	}
    }

    // if we get here, everything is enabled
    return true;
}

//*****************************************************************************
void MenuNode::setEnabled(bool enable)
{
    bool new_enable;

    // store our own individual enable flag
    m_enabled = enable;

    new_enable = isEnabled();    // get the current effective state

    if (new_enable != m_last_enabled) { // on changes:
	m_last_enabled = new_enable;

	// notify our parent that our enabled state has changed
	emit sigChildEnableChanged(m_id, new_enable);

	// notify all child nodes that our enable has changed
	emit sigParentEnableChanged();
    }
}

//*****************************************************************************
void MenuNode::slotChildEnableChanged(int id, bool enable)
{
    actionChildEnableChanged(id, enable);
}

//*****************************************************************************
void MenuNode::slotParentEnableChanged()
{
    setEnabled(m_enabled);
}

//*****************************************************************************
bool MenuNode::isChecked()
{
    return m_checked;
}

//*****************************************************************************
void MenuNode::setItemChecked(int /*id*/, bool /*check*/)
{
}

//*****************************************************************************
void MenuNode::setChecked(bool check)
{
    m_checked = check;
}

//*****************************************************************************
void MenuNode::setText(const QString &)
{
    // not used here, only gives sense in MenuItem
}

//*****************************************************************************
int MenuNode::getNeededIDs()
{
    return 1;
}

//*****************************************************************************
int MenuNode::registerChild(MenuNode *node)
{
    int new_id;
    Q_ASSERT(node);
    if (!node) return -1;

    new_id = unique_menu_id;
    unique_menu_id += node->getNeededIDs();

    m_children.append(node);
    node->setId(new_id);

    // notification for the childs that our enable state changed
    QObject::connect(
	this, SIGNAL(sigParentEnableChanged()),
	node, SLOT(slotParentEnableChanged())
    );

    // notification for us that a child's enable state changed
    QObject::connect(
	node, SIGNAL(sigChildEnableChanged(int, bool)),
	this, SLOT(slotChildEnableChanged(int, bool))
    );

    return new_id;
}

//*****************************************************************************
void MenuNode::setUID(const QString &uid)
{
    m_uid = uid;
}

//*****************************************************************************
MenuNode *MenuNode::findUID(const QString &uid)
{
    if (m_uid == uid) return this;    // found ourself

    MenuNode *child = m_children.first();
    while (child) {
	int pos = m_children.at();
	MenuNode *node = child->findUID(uid);
	if (node) return node;    // found in child
	m_children.at(pos);
	child = m_children.next();
    }

    return 0;    // nothing found :-(
}

//*****************************************************************************
MenuNode *MenuNode::findChild(const QString &name)
{
    Q_ASSERT(name.length());
    MenuNode *child = m_children.first();
    while (child) {
	int pos = m_children.at();
	if (name == child->getName())
	    return child;
	m_children.at(pos);
	child = m_children.next();
    }
    return 0;
}

//*****************************************************************************
MenuNode *MenuNode::findChild(int id)
{
    MenuNode *child = m_children.first();
    while (child) {
	int pos = m_children.at();
	if (child->getId() == id)
	    return child;
	m_children.at(pos);
	child = m_children.next();
    }
    return 0;
}

//*****************************************************************************
void MenuNode::removeChild(MenuNode *child)
{
    Q_ASSERT(child);
    if (!child) return ;

    // notification for the childs that our enable state changed
    QObject::disconnect(
	this, SIGNAL(sigParentEnableChanged()),
	child, SLOT(slotParentEnableChanged())
    );

    // notification for us that a child's enable state changed
    QObject::disconnect(
	child, SIGNAL(sigChildEnableChanged(int, bool)),
	this, SLOT(slotChildEnableChanged(int, bool))
    );

    m_children.setAutoDelete(false);
    m_children.remove(child);
}

//*****************************************************************************
MenuNode *MenuNode::insertBranch(const QString &name,
	const QString &/*command*/, int /*key*/, const QString &/*uid*/,
	int /*index*/)
{
    qDebug("!!! MenuNode(%s): insertBranch(%s) !!!",
          m_name.local8Bit().data(),
          name.local8Bit().data());
    return 0;
}

//*****************************************************************************
MenuNode *MenuNode::insertLeaf(const QString &name,
	const QString &/*command*/, int /*key*/, const QString &/*uid*/,
	int /*index*/)
{
    qDebug("!!! MenuNode(%s): insertLeaf(%s) !!!",
          m_name.local8Bit().data(),
          name.local8Bit().data());
    return 0;
}

//*****************************************************************************
int MenuNode::insertNode(const QString &name, const QString &position,
                         const QString &command, int key, const QString &uid)
{
    int result = -1;
    int pos = 0;

    if (!position.length()) {
	qWarning("MenuNode::parseCommand: no position!");
	return result;
    }

    // make working copies of name and position
    QString n(name);
    QString p(position);

    // at start of the parsing process ?
    if (!n.length()) {
	// split off the first token, separated by a slash
	pos = p.find('/');
	if (pos < 0) pos = p.length();
    }
    n = position.left(pos);
    p.remove(0, pos+1);
    if ((n.length()) && (specialCommand(n))) {
	// no new branch, only a special command
	return 0;
    }

    if ((!p.length()) || (p[0] == '#')) {
	// end of the tree
	MenuNode *sub = findChild(n);
	if (sub) {
	    // a leaf with this name already exists
	    // -> maybe we want to set new properties
	    if (key) sub->setKey(key);

	    if (uid.length()) sub->setUID(uid);

	    if (p[0] == '#') sub->specialCommand(p);
	    return sub->getId();
	} else {
	    // insert a new leaf
	    MenuNode *leaf = insertLeaf(n, command, key, uid);
	    if (!leaf) return -1;

	    if (p[0] == '#') leaf->specialCommand(p);
	    return leaf->getId();
	}
    } else {
	// somewhere in the tree
	MenuNode *sub = findChild(n);
	if (!sub) {
	    sub = insertBranch(n, command, key, uid);
	} else if ( !sub->isBranch() && (p[0] != '#')) {
	    // remove the "leaf" and insert a branch with
	    // the same properties
	    sub = leafToBranch(sub);
	} else if ( (p[0] == '#') || (p[0] == 0) ) {
	    // branch already exists and we are at the end of parsing
	    // -> maybe we want to set new properties
	    if (key) sub->setKey(key);
	    if (uid.length()) sub->setUID(uid);
	}

	if (sub) {
	    result = sub->insertNode(0, p, command, key, uid);
	} else {
	    qDebug("MenuNode::insertNode: branch failed!");
	}
    }

    return result;
}

//*****************************************************************************
MenuNode *MenuNode::leafToBranch(MenuNode *node)
{
    Q_ASSERT(node);
    Q_ASSERT(node != this);

    if (!node || (node==this)) return 0;
    MenuNode *sub = node;

    // get the old properties
    int index = sub->getIndex();
    int old_key = sub->getKey();
    QString old_uid = sub->getUID();
    const QPixmap &old_icon = sub->getIcon();
    QString name = node->getName();
    QString command = node->getCommand();
    QStringList old_groups = sub->m_groups;

    // remove the old child node
    removeChild(sub);

    // insert the new branch
    sub = insertBranch(name, command, old_key, old_uid, index);
    if (sub) {
	// join it to the same groups
	QStringList::Iterator it = old_groups.begin();
	for (; it != old_groups.end(); ++it) {
	    sub->joinGroup(*it);
	}

	// set the old icon
	if (!old_icon.isNull()) sub->setIcon(old_icon);
    }

    // free the old node later.
    // IMPORTANT: we must not call "delete node" now, because we need
    //            to call leafToBranch(this) !
    node->deleteLater();

    return sub;
}

//*****************************************************************************
QDict<MenuNode> *MenuNode::getGroupList()
{
    Q_ASSERT(m_parentNode);
    return (m_parentNode) ? m_parentNode->getGroupList() : 0;
}

//*****************************************************************************
void MenuNode::joinGroup(const QString &group)
{
    Q_ASSERT(m_parentNode);
    QDict<MenuNode> *group_list = getGroupList();
    if (m_groups.contains(group)) return ;    // already joined

    MenuGroup *grp = (group_list) ? (MenuGroup *)group_list->find(group) : 0;
    if (!grp) {
	// group does not already exist, create a new one
	grp = new MenuGroup(getRootNode(), group);
	if (grp) group_list->insert(group, grp);
    }

    // remind that we belong to the given group
    m_groups.append(group);

    // register this node as a child of the group
    if (grp) grp->registerChild(this);
}

//*****************************************************************************
void MenuNode::leaveGroup(const QString &group)
{
    QDict<MenuNode> *group_list = getGroupList();

    MenuGroup *grp = (group_list) ? (MenuGroup *)group_list->find(group) : 0;

    // remove the group from our list
    m_groups.remove(group);

    // remove ourself from the group
    if (grp) grp->removeChild(this);
}

//*****************************************************************************
bool MenuNode::specialCommand(const QString &command)
{
    if (command.startsWith("#group(")) {
	Parser parser(command);

	QString group = parser.firstParam();
	while (group.length()) {
	    joinGroup(group);
	    group = parser.nextParam();
	}
	return true;
    } else if (command.startsWith("#disable")) {
	// disable the node
	setEnabled(false);
	return true;
    } else if (command.startsWith("#enable")) {
	// disable the node
	setEnabled(true);
	return true;
    }
    return false;
}

//***************************************************************************
#include "MenuNode.moc"
//***************************************************************************
//***************************************************************************
