/*********************************************************************
 * This file is part of the cpplibs suite.
 *
 * Copyright (C) 2001 Topi Mäenpää
 * All rights reserved.
 *
 * This program is free software. You can redistribute and/or modify
 * it under the terms of the free software licence found in the
 * accompanying file "COPYING". The licence terms must always be
 * redistributed with this source file. The above copyright notice
 * must be reproduced in all modified and unmodified copies of this
 * source file.
 *
 * $Revision: 1.8 $
 *********************************************************************/

#ifndef _STREAMTOKENIZER_H
#define _STREAMTOKENIZER_H

#include <iostream>
#include <string>
#include <Pair.h>
#include <strings.h>
#include "io/IO.h"

#define _LIST_INCLUDED_FROM_STREAMTOKENIZER_H
#include "List.h"
#undef _LIST_INCLUDED_FROM_STREAMTOKENIZER_H

/**
 * Bit mask for a normal token.
 **/
#define TOKEN_NORMAL   1
/**
 * Bit mask for a word token.
 **/
#define TOKEN_WORD     2
/**
 * Bit mask for a numerical token.
 **/
#define TOKEN_NUMBER   4
/**
 * Bit mask for a space.
 **/
#define TOKEN_SPACE    8
/**
 * Bit mask for a section token.
 **/
#define TOKEN_SECTION  16

namespace util
{
	class Section;
	
	/**
	 * StreamTokenizer reads an input stream and splits it into separate
	 * tokens. It may work in unlimited number of user-defined modes,
	 * and there are two predefined ones: C and xml.
	 **/
	class StreamTokenizer : virtual public Object
	{
	public:
		/**
		 * Possible syntaxes for the tokenizer.
		 * <ul>
		 * <li>SYNTAX_C - C syntax</li>
		 * <li>SYNTAX_XML - XML syntax</li>
		 * </ul>
		 **/
		enum Syntax { SYNTAX_C, SYNTAX_XML };

		class Token;
		class NormalToken;
		class WordToken;
		class NumberToken;
		class SectionToken;
		
		/**
		 * Initialize a StreamTokenizer with the given syntax. ("C" by
		 * default)
		 **/
		StreamTokenizer(Syntax syntax = SYNTAX_C);
		~StreamTokenizer();

		/**
		 * Set the attribute for a character. A character attribute is a
		 * logical or operation of the predefined constants TOKEN_{NORMAL,
		 * WORD, NUMBER, SPACE, SECTION}. When StreamTokenizer encounters
		 * a character with an attribute other than TOKEN_NORMAL, it takes
		 * special actions. Normal characters are considered separate
		 * tokens, adjacent word characters form one token, spaces
		 * separate tokens and are ignored, numbers may be digits or other
		 * parts of a number, and sections start a user-defined section
		 * that is treated as one token. The number and section attributes
		 * are not intended for manual adjustment.
		 *
		 * @param character the character whose attribute is to be altered
		 * @param attr the new attribute.
		 **/
		void setAttribute(unsigned char character, int attr);
		/**
		 * Get the attribute for a character.
		 *
		 * @param character the character whose attribute you want to check
		 * @param a bit mask of set attributes
		 **/
		int getAttribute(unsigned char character) const { return _iaAttributeMap[character]; }
		/**
		 * Get the attribute for a character.
		 *
		 * @param character the character whose attribute you want to check
		 * @param a bit mask of set attributes
		 **/
		int& attribute(unsigned char character) { return _iaAttributeMap[character]; }

		/**
		 * Get the next token from an input stream. The returned token is
		 * a newly allocated object that must be destroyed with the delete
		 * operator.
		 *
		 * @param input the stream to read from
		 * @return a token
		 **/
		Token* getNextToken(std::istream& input) throw (io::IOException&);

		/**
		 * Add a new section.
		 **/
		void addSection(Section* sec);
		/**
		 * Remove a section.
		 **/
		void removeSection(Section* sec);

		/**
		 * Set the decimal separator character.
		 **/
		void setDecimalSeparator(unsigned char separator);
		/**
		 * Get the decimal separator character.
		 **/
		char getDecimalSeparator(void) { return _cDecimalSeparator; }

		/**
		 * A method for setting the reader's syntax to C style. In C mode,
		 * StreamTokenizer recognizes "slashlash" and "slashstar" style
		 * comments, and string constants delimited by double quotes. It
		 * also reads numbers as numerical tokens.<p>
		 *
		 * In C mode, string literals without double quotes are of type
		 * TOKEN_WORD. Strings with double quotes are of type
		 * TOKEN_SECTION with the section name "string". Comments are of
		 * type TOKEN_SECTION with the section name "comment".
		 **/
		void setCSyntax(void);

		/**
		 * A method for setting the reader's syntax to XML style. In XML
		 * mode, StreamTokenizer recognizes XML tags (including comments),
		 * and automatically parses entities defined in XML 1.0
		 * specification (like &amp;amp;).<p>
		 *
		 * In XML mode, string literals without quotes (single or double)
		 * are of type TOKEN_WORD. Strings with quotes are of type
		 * TOKEN_SECTION with the section name "string". String sections
		 * are normalized according to the attribute value normalization
		 * scheme defined in XML 1.0 recommendation. In addition to
		 * strings, the following section types are recognized:
		 *
		 * <ul>
		 * <li>&lt;![CDATA[ ]]&gt; - cdata</li>
		 * <li>&lt;!-- --&gt; - comment</li>
		 * <li>&lt;! &gt; - declaration</li>
		 * <li>&lt;? ?&gt; - instruction</li>
		 * <li>&lt; &gt; - tag</li>
		 * </ul>
		 **/
		void setXMLSyntax(void);

		/**
		 * Get the current line number.
		 **/
		unsigned int getLineNumber(void) { return _iLineNumber; }
		/**
		 * Set the current line number.
		 **/
		void setLineNumber(unsigned int number) { _iLineNumber = number; }

	private:
		void clearGeneratedSections(void);
		
		List<Section*> _lstGeneratedSections;
		List<Section*> _lstSections;
		int _iaAttributeMap[256];
		unsigned char _cDecimalSeparator;
		unsigned int _iMaxStartLength, _iLineNumber;
	};

	/**
	 * Token is the common base class for different token types.
	 **/
	class StreamTokenizer::Token : virtual public Object
	{
	public:
		/**
		 * Get the type of this token.
		 * @return one of the TOKEN_* constants
		 **/
		int getType() const { return _iType; }
		
	protected:
		/**
		 * A protected constructor for subclasses.
		 **/
		Token(int type) : _iType(type) {}

	private:
		int _iType;
	};

	/**
	 * Word token represents a sequence of characters labeled as
	 * TOKEN_WORD.
	 **/
	class StreamTokenizer::WordToken : public StreamTokenizer::Token
	{
	public:
		/**
		 * Create a word token that stores the given word.
		 **/
		WordToken(std::string word) : Token(TOKEN_WORD), _strWord(word) {}

		/**
		 * Get the contents of this word token.
		 **/
		std::string getWord() const { return _strWord; }

	private:
		std::string _strWord;
	};

	/**
	 * Number token represents a numerical value, i.e. a sequence of
	 * characters labeled as TOKEN_NUMBER.
	 **/
	class StreamTokenizer::NumberToken : public StreamTokenizer::Token
	{
	public:
		/**
		 * Create a new number token with the given contents.
		 * @param str the number exactly as it was read
		 * @param number the numerical value of the number
		 **/
		NumberToken(std::string str, double number) :
			Token(TOKEN_NUMBER), _strNumber(str), _dNumber(number) {}

		/**
		 * Get the number this token represents.
		 **/
		double getNumber() const { return _dNumber; }
		/**
		 * Get the number as it was read.
		 **/
		std::string getNumberString() const { return _strNumber; }

	private:
		std::string _strNumber;
		double _dNumber;
	};

	/**
	 * Normal token represents a character that has no special label.
	 **/
	class StreamTokenizer::NormalToken : public StreamTokenizer::Token
	{
	public:
		/**
		 * Create a new normal token with the given content character.
		 **/
		NormalToken(char character) : Token(TOKEN_NORMAL), _cCharacter(character) {}

		/**
		 * Get the character.
		 **/
		char getCharacter() const { return _cCharacter; }

	private:
		char _cCharacter;
	};


	/**
	 * Section represents a part of input that is treated as a single
	 * token in StreamTokenizer.
	 **/
	class Section : virtual public Object
	{
	public:
		friend class StreamTokenizer;

		/**
		 * Create a section with the given name and start string.
		 *
		 * @param name the name of the section. When this section is
		 * encountered, StreamTokenizer will place its name to the
		 * sectionName variable.
		 * @param start the start string of this section (i.e. &sol;*)
		 **/
		Section(const std::string& name,
						const std::string& start);

		virtual ~Section() {}

		void setCaseSensitive(bool bSensitive);
		bool isCaseSensitive(void) { return _bCaseSensitive; }

		/**
		 * Replace all occurrences of <i>from</i> with <i>to</i> when
		 * reading this section.
		 **/
		void addReplacement(const std::string& from, const std::string& to);

		/**
		 * Set the name for this section, e.g. "string" or "comment".
		 **/
		void setName(const std::string& name) { _strName = name; }
		/**
		 * Get the name of this section.
		 **/
		std::string getName(void) const { return _strName; }
		/**
		 * Get the name of this section.
		 **/
		std::string& name(void) { return _strName; }
		/**
		 * Get the name of this section.
		 **/
		const std::string& name(void) const { return _strName; }

		/**
		 * Set the string that starts this section.
		 **/
		//void setStart(const string& start) { _strStart = start; }
		/**
		 * Get the start string.
		 **/
		std::string getStart(void) const { return _strStart; }
		/**
		 * Get the start string.
		 **/
		const std::string& start(void) const { return _strStart; }
		/**
		 * Get the start string.
		 **/
		//string& start(void) { return _strStart; }

		/**
		 * Read the section's contents from a stream.
		 **/
		virtual std::string readSection(std::istream& input) throw (io::IOException&) = 0;
		/**
		 * Compare two strings. StreamTokenizer calls this method when it
		 * thinks it has found a section start. If the section start is
		 * case insensitive or it can otherwise take various forms, you
		 * can override this method to return 0 for all allowable section
		 * start strings. The default implementation returns
		 * strncmp(ptr1,ptr2,len).
		 *
		 * @param ptr1 a character array representing the section start string
		 * @param ptr2 a pointer to the data that StreamTokenizer thinks is a section start
		 * @param len the length of the section start string
		 **/
		virtual int compare(const char* ptr1, const char* ptr2, size_t len) { return strncmp(ptr1,ptr2,len); }
		
	protected:
		bool checkReplace(unsigned char&, std::istream&, std::string&);
		//int (*_cmpFunc)(const char*, const char*, size_t);
		bool _bCaseSensitive;
		bool _baReplace[256];
		std::string _strName,_strStart;
		unsigned int _iMaxReplacementLength;
		List<Pair<std::string,std::string> > _lstReplacements;
	};

	/**
	 * Section token represents a section of any characters delimited by
	 * some special characters.
	 **/
	class StreamTokenizer::SectionToken : public StreamTokenizer::Token
	{
	public:
		/**
		 * Create a new section token with the given name and contents.
		 **/
		SectionToken(Section* section,
								 std::string sectionContents) :
			Token(TOKEN_SECTION), _section(section), _strContents(sectionContents) {}

		/**
		 * Get the name of the section, i.e. "comment", "string" etc.
		 **/
		std::string getName() const { return _section->getName(); }

		/**
		 * Get a pointer to the Section object that was responsible for
		 * reading this section.
		 **/
		Section* getSection() { return _section; }

		/**
		 * Get the contents of the section, i.e. the data between start
		 * and end delimiters.
		 **/
		std::string getContents() const { return _strContents; }

	private:
		Section* _section;
		std::string _strContents;
	};
	
	/**
	 * A section that terminates at end of line.
	 **/
	class EOLSection : public Section
	{
	public:
		/**
		 * Create an end-of-line section with the given name and start
		 * string.
		 **/
		EOLSection(const std::string& name,
							 const std::string& start) : Section(name,start) {}

		std::string readSection(std::istream& input) throw (io::IOException&);
	};

	/**
	 * A section that terminates at a given section end string.
	 **/
	class EndDelimitedSection : public Section
	{
	public:
		/**
		 * Create an end-delimited section with the given name plus start
		 * and end strings.
		 **/
		EndDelimitedSection(const std::string& name,
												const std::string& start,
												const std::string& end);

		std::string readSection(std::istream& input) throw (io::IOException&);

	private:
		std::string _strEnd;
	};

}

#endif
