/***************************************************************************
 * css.c
 * Character-based parser for Css definitions
 * Author - Iago Rubio <iagorubio(at)users.sourceforge.net>
 **************************************************************************/
#include "general.h"

#include <string.h>
#include <ctype.h>

#include "parse.h"
#include "read.h"


typedef enum eCssKinds {
    K_NONE = -1, K_CLASS, K_SELECTOR, K_ID
} cssKind;

static kindOption CssKinds [] = {
    { TRUE, 'c', "class", "classes" },
    { TRUE, 's', "selector",  "selectors"  },
    { TRUE, 'v', "variable",  "identities"  }
};

typedef enum _CssParserState {	// state of parsing
	P_STATE_NONE,  				// default state
	P_STATE_IN_COMMENT,			// into a comment, only multi line in CSS
	P_STATE_IN_SINGLE_STRING,	// into a single quoted string
	P_STATE_IN_DOUBLE_STRING,	// into a double quoted string
	P_STATE_IN_DEFINITION,		// on the body of the style definition, nothing for us
	P_STATE_IN_MEDIA,			// on a @media declaration, can be multi-line
	P_STATE_IN_IMPORT,			// on a @import declaration, can be multi-line
	P_STATE_IN_NAMESPACE,		// on a @namespace declaration
	P_STATE_IN_PAGE,			// on a @page declaration
	P_STATE_IN_FONTFACE,		// on a @font-face declaration
	P_STATE_AT_END				// end of parsing
} CssParserState;

static void makeCssSimpleTag( vString *name, cssKind kind, boolean delete )
{
	vStringTerminate (name);
	makeSimpleTag (name, CssKinds, kind);
	vStringClear (name);
	if( delete )
		vStringDelete (name);
}

static boolean isCssDeclarationAllowedChar( const unsigned char *cp )
{
	return  isalnum ((int) *cp) ||
			isspace ((int) *cp) ||
			*cp == '_' ||	// allowed char
			*cp == '-' ||	// allowed char
			*cp == '+' ||   // allow all sibling in a single tag
			*cp == '>' ||   // allow all child in a single tag
			*cp == '{' || 	// allow the start of the declaration
			*cp == '.' || 	// allow classes and selectors
			*cp == ',' || 	// allow multiple declarations
			*cp == ':' ||   // allow pseudo classes
			*cp == '*' || 	// allow globs as P + *
			*cp == '#';		// allow ids
}

static CssParserState parseCssDeclaration( const unsigned char **position, cssKind kind )
{
	vString *name = vStringNew ();
	const unsigned char *cp = *position;

	// pick to the end of line including children and sibling
	// if declaration is multiline go for the next line
	while ( isCssDeclarationAllowedChar(cp) ||
			*cp == '\0' ) 	// track the end of line into the loop
	{
		if( (int) *cp == '\0' )
		{
			cp = fileReadLine ();
			if( cp == NULL ){
				makeCssSimpleTag(name, kind, TRUE);
				*position = cp;
				return P_STATE_AT_END;
			}
		}
		else if( *cp == ',' )
		{
			makeCssSimpleTag(name, kind, TRUE);
			*position = ++cp;
			return P_STATE_NONE;
		}
		else if( *cp == '{' )
		{
			makeCssSimpleTag(name, kind, TRUE);
			*position = ++cp;
			return P_STATE_IN_DEFINITION;
		}

		vStringPut (name, (int) *cp);
		++cp;
	}

	makeCssSimpleTag(name, kind, TRUE);
	*position = cp;

	return P_STATE_NONE;
}

static CssParserState parseCssLine( const unsigned char *line, CssParserState state )
{
	vString *aux;

	while( *line != '\0' ) // fileReadLine returns NULL terminated strings
	{
		while (isspace ((int) *line))
			++line;
		switch( state )
		{
			case P_STATE_NONE:
				// pick first char if alphanumeric is a selector
				if( isalnum ((int) *line) )
					state = parseCssDeclaration( &line, K_SELECTOR );
				else if( *line == '.' ) // a class
					state = parseCssDeclaration( &line, K_CLASS );
				else if( *line == '#' ) // an id
					state = parseCssDeclaration( &line, K_ID );
				else if( *line == '@' ) // at-rules, we'll ignore them
				{
					++line;
					aux = vStringNew();
					while( !isspace((int) *line) )
					{
						vStringPut (aux, (int) *line);
						++line;
					}
					vStringTerminate (aux);
					if( strcmp( aux->buffer, "media" ) == 0 )
						state = P_STATE_IN_MEDIA;
					else if ( strcmp( aux->buffer, "import" ) == 0 )
						state = P_STATE_IN_IMPORT;
					else if ( strcmp( aux->buffer, "namespace" ) == 0 )
						state = P_STATE_IN_NAMESPACE;
					else if ( strcmp( aux->buffer, "page" ) == 0 )
						state = P_STATE_IN_PAGE;
					else if ( strcmp( aux->buffer, "font-face" ) == 0 )
						state = P_STATE_IN_FONTFACE;
					vStringDelete (aux);
				}
				else if( *line == '*' && *(line-1) == '/' ) // multi-line comment
					state = P_STATE_IN_COMMENT;
			break;
			case P_STATE_IN_COMMENT:
				if( *line == '/' && *(line-1) == '*')
					state = P_STATE_NONE;
			break;
			case  P_STATE_IN_SINGLE_STRING:
				if( *line == '\'' && *(line-1) != '\\' )
					state = P_STATE_IN_DEFINITION; // PAGE, FONTFACE and DEFINITION are treated the same way
			break;
			case  P_STATE_IN_DOUBLE_STRING:
				if( *line=='"' && *(line-1) != '\\' )
					state = P_STATE_IN_DEFINITION; // PAGE, FONTFACE and DEFINITION are treated the same way
			break;
			case  P_STATE_IN_MEDIA:
				// skip to start of media body or line end
				while( *line != '{' )
				{
					if( *line == '\0' )
						break;
					++line;
				}
				if( *line == '{' )
						state = P_STATE_NONE;
			break;
			case  P_STATE_IN_IMPORT:
			case  P_STATE_IN_NAMESPACE:
				// skip to end of declaration or line end
				while( *line != ';' )
				{
					if( *line == '\0' )
						break;
					++line;
				}
				if( *line == ';' )
					state = P_STATE_NONE;
			break;
			case P_STATE_IN_PAGE:
			case P_STATE_IN_FONTFACE:
			case P_STATE_IN_DEFINITION:
				if( *line == '}' )
					state = P_STATE_NONE;
				else if( *line == '\'' )
					state = P_STATE_IN_SINGLE_STRING;
				else if( *line == '"' )
					state = P_STATE_IN_DOUBLE_STRING;
			break;
			case P_STATE_AT_END:
				return state;
			break;
		}
		if (line == NULL) return P_STATE_AT_END;
		line++;
	}
	return state;
}

static void findCssTags (void)
{
    const unsigned char *line;
	CssParserState state = P_STATE_NONE;

    while ( (line = fileReadLine ()) != NULL )
    {
		state = parseCssLine( line, state );
		if( state==P_STATE_AT_END ) return;
    }
}

/* parser definition */
extern parserDefinition* CssParser (void)
{
    static const char *const extensions [] = { "css", NULL };
    parserDefinition* def = parserNew ("CSS");
    def->kinds      = CssKinds;
    def->kindCount  = KIND_COUNT (CssKinds);
    def->extensions = extensions;
    def->parser     = findCssTags;
    return def;
}