/*
 * Copyright 2006-2018 Adrian Thurston <thurston@colm.net>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "redbuild.h"

#include <assert.h>
#include <string.h>
#include <stdbool.h>

#include <iostream>

#include "fsmcodegen.h"

using namespace std;

RedFsmBuild::RedFsmBuild( Compiler *pd, FsmGraph *fsm )
:
	pd(pd),
	fsm(fsm),
	nextActionTableId(0),
	startState(-1),
	errState(-1)
{
}

void RedFsmBuild::initActionList( unsigned long length )
{ 
	redFsm->allActions = new GenAction[length];
	memset( redFsm->allActions, 0, sizeof(GenAction) * length );
	for ( unsigned long a = 0; a < length; a++ )
		redFsm->genActionList.append( redFsm->allActions+a );
}


void RedFsmBuild::makeActionList()
{
	/* Determine which actions to write. */
	int nextActionId = 0;
	for ( ActionList::Iter act = pd->actionList; act.lte(); act++ ) {
		if ( act->numRefs() > 0 || act->numCondRefs > 0 )
			act->actionId = nextActionId++;
	}

	initActionList( nextActionId );
	curAction = 0;

	for ( ActionList::Iter act = pd->actionList; act.lte(); act++ ) {
		if ( act->actionId >= 0 )
			makeAction( act );
	}
}

void RedFsmBuild::initActionTableList( unsigned long length )
{ 
	redFsm->allActionTables = new RedAction[length];
}

void RedFsmBuild::initStateList( unsigned long length )
{
	redFsm->allStates = new RedState[length];
	for ( unsigned long s = 0; s < length; s++ )
		redFsm->stateList.append( redFsm->allStates+s );

	/* We get the start state as an offset, set the pointer now. */
	assert( startState >= 0 );
	redFsm->startState = redFsm->allStates + startState;
	if ( errState >= 0 )
		redFsm->errState = redFsm->allStates + errState;
	for ( EntryIdVect::Iter en = redFsm->entryPointIds; en.lte(); en++ )
		redFsm->entryPoints.insert( redFsm->allStates + *en );

	/* The nextStateId is no longer used to assign state ids (they come in set
	 * from the frontend now), however generation code still depends on it.
	 * Should eventually remove this variable. */
	redFsm->nextStateId = redFsm->stateList.length();
}

void RedFsmBuild::addEntryPoint( int entryId, unsigned long entryState )
{
	redFsm->entryPointIds.append( entryState );
	redFsm->redEntryMap.insert( entryId, entryState );
}

void RedFsmBuild::addRegionToEntry( int regionId, int entryId )
{
	assert( regionId == redFsm->regionToEntry.length() );
	redFsm->regionToEntry.append( entryId );
}

void RedFsmBuild::initTransList( int snum, unsigned long length )
{
	/* Could preallocate the out range to save time growing it. For now do
	 * nothing. */
}

void RedFsmBuild::newTrans( int snum, int tnum, Key lowKey, 
		Key highKey, long targ, long action )
{
	/* Get the current state and range. */
	RedState *curState = redFsm->allStates + snum;
	RedTransList &destRange = curState->outRange;

	if ( curState == redFsm->errState )
		return;

	/* Make the new transitions. */
	RedState *targState = targ >= 0 ? (redFsm->allStates + targ) : 
			redFsm->wantComplete ? redFsm->getErrorState() : 0;
	RedAction *actionTable = action >= 0 ? (redFsm->allActionTables + action) : 0;
	RedTrans *trans = redFsm->allocateTrans( targState, actionTable );
	RedTransEl transEl( lowKey, highKey, trans );

	if ( redFsm->wantComplete ) {
		/* If the machine is to be complete then we need to fill any gaps with
		 * the error transitions. */
		if ( destRange.length() == 0 ) {
			/* Range is currently empty. */
			if ( keyOps->minKey < lowKey ) {
				/* The first range doesn't start at the low end. */
				Key fillHighKey = lowKey;
				fillHighKey.decrement();

				/* Create the filler with the state's error transition. */
				RedTransEl newTel( keyOps->minKey, fillHighKey, redFsm->getErrorTrans() );
				destRange.append( newTel );
			}
		}
		else {
			/* The range list is not empty, get the the last range. */
			RedTransEl *last = &destRange[destRange.length()-1];
			Key nextKey = last->highKey;
			nextKey.increment();
			if ( nextKey < lowKey ) {
				/* There is a gap to fill. Make the high key. */
				Key fillHighKey = lowKey;
				fillHighKey.decrement();

				/* Create the filler with the state's error transtion. */
				RedTransEl newTel( nextKey, fillHighKey, redFsm->getErrorTrans() );
				destRange.append( newTel );
			}
		}
	}

	/* Filler taken care of. Append the range. */
	destRange.append( RedTransEl( lowKey, highKey, trans ) );
}

void RedFsmBuild::finishTransList( int snum )
{
	/* Get the current state and range. */
	RedState *curState = redFsm->allStates + snum;
	RedTransList &destRange = curState->outRange;

	if ( curState == redFsm->errState )
		return;

	/* If building a complete machine we may need filler on the end. */
	if ( redFsm->wantComplete ) {
		/* Check if there are any ranges already. */
		if ( destRange.length() == 0 ) {
			/* Fill with the whole alphabet. */
			/* Add the range on the lower and upper bound. */
			RedTransEl newTel( keyOps->minKey, keyOps->maxKey, redFsm->getErrorTrans() );
			destRange.append( newTel );
		}
		else {
			/* Get the last and check for a gap on the end. */
			RedTransEl *last = &destRange[destRange.length()-1];
			if ( last->highKey < keyOps->maxKey ) {
				/* Make the high key. */
				Key fillLowKey = last->highKey;
				fillLowKey.increment();

				/* Create the new range with the error trans and append it. */
				RedTransEl newTel( fillLowKey, keyOps->maxKey, redFsm->getErrorTrans() );
				destRange.append( newTel );
			}
		}
	}
}

void RedFsmBuild::setId( int snum, int id )
{
	RedState *curState = redFsm->allStates + snum;
	curState->id = id;
}

void RedFsmBuild::setEofTrans( int snum, int eofTarget, int actId )
{
	RedState *curState = redFsm->allStates + snum;
	RedState *targState = redFsm->allStates + eofTarget;
	RedAction *eofAct = redFsm->allActionTables + actId;
	curState->eofTrans = redFsm->allocateTrans( targState, eofAct );
}

void RedFsmBuild::setFinal( int snum )
{
	RedState *curState = redFsm->allStates + snum;
	curState->isFinal = true;
}


void RedFsmBuild::setStateActions( int snum, long toStateAction, 
			long fromStateAction, long eofAction )
{
	RedState *curState = redFsm->allStates + snum;
	if ( toStateAction >= 0 )
		curState->toStateAction = redFsm->allActionTables + toStateAction;
	if ( fromStateAction >= 0 )
		curState->fromStateAction = redFsm->allActionTables + fromStateAction;
	if ( eofAction >= 0 )
		curState->eofAction = redFsm->allActionTables + eofAction;
}

void RedFsmBuild::closeMachine()
{
}


void RedFsmBuild::initStateCondList( int snum, ulong length )
{
	/* Could preallocate these, as we could with transitions. */
}

void RedFsmBuild::setForcedErrorState()
{
	redFsm->forcedErrorState = true;
}

Key RedFsmBuild::findMaxKey()
{
	Key maxKey = keyOps->maxKey;
	for ( RedStateList::Iter st = redFsm->stateList; st.lte(); st++ ) {
		assert( st->outSingle.length() == 0 );
		assert( st->defTrans == 0 );

		long rangeLen = st->outRange.length();
		if ( rangeLen > 0 ) {
			Key highKey = st->outRange[rangeLen-1].highKey;
			if ( highKey > maxKey )
				maxKey = highKey;
		}
	}
	return maxKey;
}


void RedFsmBuild::makeActionTableList()
{
	/* Must first order the action tables based on their id. */
	int numTables = nextActionTableId;
	RedActionTable **tables = new RedActionTable*[numTables];
	for ( ActionTableMap::Iter at = actionTableMap; at.lte(); at++ )
		tables[at->id] = at;

	initActionTableList( numTables );
	curActionTable = 0;

	for ( int t = 0; t < numTables; t++ ) {
		long length = tables[t]->key.length();

		/* Collect the action table. */
		RedAction *redAct = redFsm->allActionTables + curActionTable;
		redAct->actListId = curActionTable;
		redAct->key.setAsNew( length );

		int pos = 0;
		for ( ActionTable::Iter atel = tables[t]->key; atel.lte(); atel++ ) {
			int actionId = atel->value->actionId;
			redAct->key[pos].key = 0;
			redAct->key[pos].value = redFsm->allActions+actionId;
			pos += 1;
		}

		/* Insert into the action table map. */
		redFsm->actionMap.insert( redAct );

		curActionTable += 1;

	}

	delete[] tables;
}

void RedFsmBuild::reduceActionTables()
{
	/* Reduce the actions tables to a set. */
	for ( StateList::Iter st = fsm->stateList; st.lte(); st++ ) {
		RedActionTable *actionTable = 0;

		/* Reduce To State Actions. */
		if ( st->toStateActionTable.length() > 0 ) {
			if ( actionTableMap.insert( st->toStateActionTable, &actionTable ) )
				actionTable->id = nextActionTableId++;
		}

		/* Reduce From State Actions. */
		if ( st->fromStateActionTable.length() > 0 ) {
			if ( actionTableMap.insert( st->fromStateActionTable, &actionTable ) )
				actionTable->id = nextActionTableId++;
		}

		/* Reduce EOF actions. */
		if ( st->eofActionTable.length() > 0 ) {
			if ( actionTableMap.insert( st->eofActionTable, &actionTable ) )
				actionTable->id = nextActionTableId++;
		}

		/* Loop the transitions and reduce their actions. */
		for ( TransList::Iter trans = st->outList; trans.lte(); trans++ ) {
			if ( trans->actionTable.length() > 0 ) {
				if ( actionTableMap.insert( trans->actionTable, &actionTable ) )
					actionTable->id = nextActionTableId++;
			}
		}
	}
}

void RedFsmBuild::appendTrans( TransListVect &outList, Key lowKey, 
		Key highKey, FsmTrans *trans )
{
	if ( trans->toState != 0 || trans->actionTable.length() > 0 )
		outList.append( TransEl( lowKey, highKey, trans ) );
}

void RedFsmBuild::makeTrans( Key lowKey, Key highKey, FsmTrans *trans )
{
	/* First reduce the action. */
	RedActionTable *actionTable = 0;
	if ( trans->actionTable.length() > 0 )
		actionTable = actionTableMap.find( trans->actionTable );

	long targ = trans->toState == 0 ? -1 : trans->toState->alg.stateNum;
	long action = actionTable == 0 ? -1 : actionTable->id;

	newTrans( curState, curTrans++, lowKey, highKey, targ, action );
}

void RedFsmBuild::makeTransList( FsmState *state )
{
	TransListVect outList;

	/* If there is only are no ranges the task is simple. */
	if ( state->outList.length() > 0 ) {
		/* Loop each source range. */
		for ( TransList::Iter trans = state->outList; trans.lte(); trans++ ) {
			/* Reduce the transition. If it reduced to anything then add it. */
			appendTrans( outList, trans->lowKey, trans->highKey, trans );
		}
	}

	long length = outList.length();
	initTransList( curState, length );
	curTrans = 0;

	for ( TransListVect::Iter tvi = outList; tvi.lte(); tvi++ )
		makeTrans( tvi->lowKey, tvi->highKey, tvi->value );
	finishTransList( curState );
}

void RedFsmBuild::newAction( int anum, char *name, int line, int col, Action *action )
{
	redFsm->allActions[anum].actionId = anum;
	redFsm->allActions[anum].name = name;
	redFsm->allActions[anum].loc.line = line;
	redFsm->allActions[anum].loc.col = col;
	redFsm->allActions[anum].inlineList = action->inlineList;
	redFsm->allActions[anum].objField = action->objField;
	redFsm->allActions[anum].markType = action->markType;
	redFsm->allActions[anum].markId = action->markId + 1;
}

void RedFsmBuild::makeAction( Action *action )
{
	int line = action->loc.line;
	int col = action->loc.col;

	char *name = 0;
	if ( action->name != 0 ) 
		name = action->name;

	newAction( curAction++, name, line, col, action );
}

void xmlEscapeHost( std::ostream &out, char *data, int len )
{
	char *end = data + len;
	while ( data != end ) {
		switch ( *data ) {
		case '<': out << "&lt;"; break;
		case '>': out << "&gt;"; break;
		case '&': out << "&amp;"; break;
		default: out << *data; break;
		}
		data += 1;
	}
}

void RedFsmBuild::makeStateActions( FsmState *state )
{
	RedActionTable *toStateActions = 0;
	if ( state->toStateActionTable.length() > 0 )
		toStateActions = actionTableMap.find( state->toStateActionTable );

	RedActionTable *fromStateActions = 0;
	if ( state->fromStateActionTable.length() > 0 )
		fromStateActions = actionTableMap.find( state->fromStateActionTable );

	RedActionTable *eofActions = 0;
	if ( state->eofActionTable.length() > 0 )
		eofActions = actionTableMap.find( state->eofActionTable );
	
	if ( toStateActions != 0 || fromStateActions != 0 || eofActions != 0 ) {
		long toStateAction = -1;
		long fromStateAction = -1;
		long eofAction = -1;

		if ( toStateActions != 0 )
			toStateAction = toStateActions->id;
		if ( fromStateActions != 0 )
			fromStateAction = fromStateActions->id;
		if ( eofActions != 0 )
			eofAction = eofActions->id;

		setStateActions( curState, toStateAction,
				fromStateAction, eofAction );
	}
}

void RedFsmBuild::makeStateList()
{
	/* Write the list of states. */
	long length = fsm->stateList.length();
	initStateList( length );
	curState = 0;

	for ( StateList::Iter st = fsm->stateList; st.lte(); st++ ) {
		/* Both or neither should be set. */
		assert( !( (st->eofTarget != 0) xor (st->eofActionTable.length() > 0) ) );

		makeStateActions( st );
		makeTransList( st );

		setId( curState, st->alg.stateNum );
		if ( st->isFinState() )
			setFinal( curState );

		/* If there is an eof target, make an eof transition. */
		if ( st->eofTarget != 0 ) {
			/* Find the eof actions. */
			RedActionTable *eofActions = 0;
			eofActions = actionTableMap.find( st->eofActionTable );
			setEofTrans( curState, st->eofTarget->alg.stateNum, eofActions->id );
		}

		curState += 1;
	}
}

void RedFsmBuild::makeEntryPoints()
{
	if ( fsm->lmRequiresErrorState )
		setForcedErrorState();

	for ( EntryMap::Iter en = fsm->entryPoints; en.lte(); en++ ) {
		/* Get the name instantiation from nameIndex. */
		FsmState *state = en->value;
		long entry = state->alg.stateNum;
		addEntryPoint( en->key, entry );
	}

	for ( RegionList::Iter reg = pd->regionList; reg.lte(); reg++ ) {
		assert( reg->impl->regionNameInst != 0 );

		TokenRegion *use = reg;

		if ( use->zeroLel != 0 )
			use = use->ignoreOnly;

		NameInst *regionName = use->impl->regionNameInst;
		addRegionToEntry( reg->id, regionName->id );
	}
}

void RedFsmBuild::makeMachine()
{
	/* Action tables. */
	reduceActionTables();

	makeActionList();
	makeActionTableList();
	makeConditions();

	/* Start state. */
	startState = fsm->startState->alg.stateNum;
	
	/* Error state. */
	if ( fsm->errState != 0 )
		errState = fsm->errState->alg.stateNum;

	makeEntryPoints();
	makeStateList();
}

void RedFsmBuild::makeConditions()
{
}

RedFsm *RedFsmBuild::reduceMachine()
{
	redFsm = new RedFsm();
	redFsm->wantComplete = true;

	/* Open the definition. */
	makeMachine();

	/* Do this before distributing transitions out to singles and defaults
	 * makes life easier. */
	redFsm->maxKey = findMaxKey();

	redFsm->assignActionLocs();

	/* Find the first final state (The final state with the lowest id). */
	redFsm->findFirstFinState();

	/* Choose default transitions and the single transition. */
	redFsm->chooseDefaultSpan();
		
	/* Maybe do flat expand, otherwise choose single. */
	redFsm->chooseSingle();

	/* Set up incoming transitions. */
	redFsm->setInTrans();

	/* Anlayze Machine will find the final action reference counts, among
	 * other things. We will use these in reporting the usage
	 * of fsm directives in action code. */
	redFsm->analyzeMachine();

	return redFsm;
}

