/* Crossmark processing library 
 * Copyright (C) 2006, Robert Staudinger <robert.staudinger@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/*!
 * \file cm-normalizer-private.hh
 * \brief Crossmark normalizer.
 * \internal
 */

#ifndef CM_NORMALIZER_PRIVATE_HH
#define CM_NORMALIZER_PRIVATE_HH

#include <glib.h>
#include <list>
#include <crossmark/cm-features.hh>
#include <crossmark/cm-document.hh>

namespace crossmark {

/*
 * \internal
 * \brief Extended document interface for validation.
 *
 * The ``editor'' namespace specialises modules so they can be used for
 * automatic input conversion. 
 *
 * E.g. after input of "*foo" it should
 * automatically switch to boldface, but if the next "*" doesn't match
 * word boundaries boldface would have to be cancelled and reverted.
 * Blockquotes are similar, e.g. 3 times <enter> ends blockquote mode.
 * 
 * It will be important to (1) keep the input parser in sync when formatting 
 * is done using the toolbar and (2) resync when the cursor is moved using
 * mouse or keyboard navigation. 
 * For (2) we may just re-parse from the beginning of the block.
 * \see Normalizer::resume()
 * 
 * \todo Figure out a better name. Calling it trackers for now because 
	 it keeps track of keystrokes.
 * \todo This may be exported once we're implementing the input method parser.
 */

class Normalizer;

/*!
 * \internal
 * \brief Document action proxies.
 * \todo Templatise the methods.
 */
namespace normalizer {

/*!
 * \internal 
 * \brief Base interface for buffered calls to a document reader.
 */
class Method
{
public:
	enum Class {
		TEXT,
		PUSH_STYLE, 
		POP_STYLE,
		PUSH_BLOCK,
		POP_BLOCK
	};

	Method (Document &reader)
	  : _reader (reader)
	{}
	virtual ~Method () {}
	virtual void operator () () = 0;
	virtual Method::Class getClass () const = 0;

protected:
	Document &_reader;
};

/*!
 * \internal 
 * \brief Buffered "text" call.
 */
class Text : public Method
{
friend class ::crossmark::Normalizer; // DEBUG
public:
	Text (Document &reader, gchar const *text) 
	  : Method (reader),
	    _text (g_strdup (text))
	{}
	virtual ~Text () 
	{
		g_free (_text);
	}
	virtual void operator () (void) { _reader.text (_text); }
	virtual Method::Class getClass () const { return Method::TEXT; }

	/*!
	 * \todo Actually check before cutting off.
	 */
	void rtrim (document::Block::Type type) 
	{
		if (type == document::Block::HEADING_3) {
			gint tail = strlen (" ===");
			_text[strlen (_text) - tail] = '\0';
		} else if (type == document::Block::HEADING_4) {
			gint tail = strlen (" ====");
			_text[strlen (_text) - tail] = '\0';
		} else {
			g_assert_not_reached ();
		}
	}

	static Text * fallback (Document &reader, document::Style::Type type) 
	{ 
		switch (type) {
		case document::Style::BOLD:
			return new Text (reader, "*");
			break;
		case document::Style::ITALIC:
			return new Text (reader, "/");
			break;
		case document::Style::MONOSPACE:
			return new Text (reader, "`");
			break;
		case document::Style::UNDERLINE:
			return new Text (reader, "_");
			break;
		default:
			g_assert_not_reached ();
		}
	}

private:
	gchar  *_text;
};

/*!
 * \internal 
 * \brief Buffered "push style" call.
 */
class PushStyle : public Method
{
public:
	PushStyle (Document &reader, document::Style::Type type) 
	  : Method (reader),
	    _type (type)
	{}
	virtual ~PushStyle () {}
	virtual void operator () (void) { _reader.pushStyle (_type); }
	virtual Method::Class getClass () const { return Method::PUSH_STYLE; }
	virtual document::Style::Type getType () const { return _type; }
	virtual Text * createFallback () { return Text::fallback (_reader, _type); }

private:
	document::Style::Type _type;
};

/*!
 * \internal 
 * \brief Buffered "pop style" call.
 */
class PopStyle : public Method
{
public:
	PopStyle (Document &reader, document::Style::Type type) 
	  : Method (reader),
	    _type (type)
	{}
	virtual ~PopStyle () {}
	virtual void operator () (void) { _reader.popStyle (_type); }
	virtual Method::Class getClass () const { return Method::POP_STYLE; }
	virtual document::Style::Type getType () const { return _type; }
	virtual Text * createFallback () { return Text::fallback (_reader, _type); }

private:
	document::Style::Type _type;
};

/*!
 * \internal 
 * \brief Buffered "push block" call.
 */
class PushBlock : public Method
{
friend class ::crossmark::Normalizer; // normalizer can modify type
public:
	PushBlock (Document &reader, document::Block::Type type) 
	  : Method (reader), 
	    _type (type)
	{}
	virtual ~PushBlock () {}
	virtual void operator () (void) { _reader.pushBlock (_type); }
	virtual Method::Class getClass () const { return Method::PUSH_BLOCK; }
	virtual document::Block::Type getType () const { return _type; }

protected:
	void setType (document::Block::Type type) { _type = type; }

private:
	document::Block::Type _type;
};

/*!
 * \internal 
 * \brief Buffered "pop block" call.
 */
class PopBlock : public Method
{
public:
	PopBlock (Document &reader, document::Block::Type type) 
	  : Method (reader),
	    _type (type)
	{}
	virtual ~PopBlock () {}
	virtual void operator () (void) { _reader.pushBlock (_type); }
	virtual Method::Class getClass () const { return Method::POP_BLOCK; }

private:
	document::Block::Type _type;
};

}; // namespace normalizer

/*
 * \internal
 * \brief Extended crossmark::Document interface that is used between parser and normalizer.
 *
 * \todo Pull out this classes' public interface for the <i>input method</i>.
 */
class Normalizer : public Document
{
public:
	Normalizer (Document &reader);
	virtual ~Normalizer ();

	/*!
	 * This sets the tracker to initial state. 
	 * \see resume
 	 */
	// virtual void reset ();

	/*!
	 * Resume tracking and making formatting predictions.
	 *
	 * Between reset() and resume() all content between the 
	 * start of the block and the cursor position has to be 
	 * passed to the tracker, so it'll settle in a consistent 
	 * state before editing is resumed.
	 *
	 * While reparsing no predictions will be issued.
	 *
	 * The reset, reparse, resume chain has to be run when
	 * editing is resumed after the caret has been moved.
	 */
	// virtual void resume ();


	virtual void pushDocument ();
	virtual void popDocument ();

	// text interface
	virtual void text (gchar const *text);

	// style interface
	virtual void pushStyle (document::Style::Type type);
	virtual void cancelStyle (document::Style::Type type);
	virtual void popStyle (document::Style::Type type);

	// document structure interface
	virtual void pushBlock (document::Block::Type type);
	virtual void popBlock ();

private:
	std::list<normalizer::Method *> _methods;
	Document &_reader;

	gboolean _isBold;
	gboolean _isItalic;
	gboolean _isMonospace;
	gboolean _isUnderline;

	// DEBUG
	void dump (gchar const * indent = "");
};

};  // namespace crossmark

#endif /* CM_NORMALIZER_PRIVATE_HH */