#ifndef dialog_playback_hpp
#define dialog_playback_hpp

#include "dialog.hpp"

struct dialog_playback
	{
	dialog_playback():
		current_state( STATE_READY ),
		option_to_execute( -1 )
		{
		}
		
		
	enum dialog_state
		{
		STATE_READY,
		STATE_RUN_CONVERSATION,
		STATE_DISPLAYING_OPTIONS,
		STATE_EXECUTING_OPTIONS,
		STATE_DISPLAYING_SPEECH,
		STATE_SENDING_EVENT,
		STATE_UNDEFINED,
		};


	dialog_state state() 
		{ 
		return current_state;
		}
	

	void load_dialog( string const& filename )
		{
		dialog& dlg = dialogs.add( dialog() );
		dlg.load_dialog( filename );
		}


	int conversation_count() const
		{
		int count = 0;
		for( int i = 0; i < dialogs.count(); ++i )
			{
			dialog const& dlg = dialogs[ i ];
			count += dlg.conversation_count();
			}
		return count;
		}


	string_id conversation_id( int index ) const
		{
		int count = 0;
		for( int i = 0; i < dialogs.count(); ++i )
			{
			dialog const& dlg = dialogs[ i ];
			for( int j = 0; j < dlg.conversation_count(); ++j )
				{
				if( count == index ) return dlg.conversation_item( j ).id;
				count++;
				}
			}

		return "";
		}
		
		
	void start_conversation( string_id conversation_id )
		{
		PIXIE_ASSERT( current_state == STATE_READY, "Already running a conv" );		
		
		// reset system
		current_state = STATE_READY;
		current_option_list.clear();
		stack_clear();

		// Find the right conv
		conversation const* conv = find_conversation( conversation_id );
		if( conv )
			{
			stack_entry& entry = stack_push();
			entry.conversation_id = conversation_id;
			entry.conversation_index = 0;
			current_state = STATE_RUN_CONVERSATION;
			return;
			}
		else
			{
			current_state = STATE_READY;
			}

		PIXIE_ASSERT( false, "Conversation not found" );		
		}


	void execute()
		{
		switch( current_state )
			{
			case STATE_UNDEFINED: PIXIE_ASSERT( false, "Undefined state" ); break;

			case STATE_READY: break;

			case STATE_RUN_CONVERSATION:
				{
				stack_entry* entry = stack_top();
				PIXIE_ASSERT( entry, "Conversation stack corrupted" );
				run_conversation_entry( entry->conversation_id, entry->conversation_index );
				} break;
		
			case STATE_DISPLAYING_OPTIONS: break;

			case STATE_EXECUTING_OPTIONS:
				{
				stack_entry* top = stack_top();
				PIXIE_ASSERT( top, "Conversation stack corrupted");
				if( current_option_list.count() > 0 && option_to_execute >= 0 && option_to_execute < current_option_list.count() )
					{
					conversation_option const& option = current_option_list[ option_to_execute ];
					if( !option.is_persistent )
						{
						used_option_entry& entry = used_options.add( used_option_entry() );
						entry.conversation_id = top->conversation_id;
						entry.line = option.line;
						}

					string_id conversation_id = current_option_list[ option_to_execute ].target;
					redirect( conversation_id );											
					current_option_list.clear();
					}
				} break;

			case STATE_DISPLAYING_SPEECH:
				{
				stack_entry* entry = stack_top();
				PIXIE_ASSERT( entry, "Conversation stack corrupted" );
				++entry->conversation_index;
				run_conversation_entry( entry->conversation_id, entry->conversation_index );
				} break;


			case STATE_SENDING_EVENT:
				{
				stack_entry* entry = stack_top();
				PIXIE_ASSERT( entry, "Conversation stack corrupted" );
				++entry->conversation_index;
				current_state = STATE_RUN_CONVERSATION;
				} break;
			}
		}


	conversation_line const* current_conversation_line()
		{
		if( current_state != STATE_DISPLAYING_SPEECH ) return 0;

		stack_entry* entry = stack_top();
		if( !entry ) return 0;
		
		conversation const* conv = find_conversation( entry->conversation_id );
		conversation_entry const& conv_entry = conv->entries[ entry->conversation_index ];
		return &conv_entry.line;
		}


	array<conversation_option> const* current_options()
		{
		if( current_state != STATE_DISPLAYING_OPTIONS ) return 0;

		stack_entry* entry = stack_top();
		if( !entry ) return 0;
		
		return &current_option_list;
		}




	conversation_event const* current_conversation_event()
		{
		if( current_state != STATE_SENDING_EVENT ) return 0;

		stack_entry* entry = stack_top();
		if( !entry ) return 0;
		
		conversation const* conv = find_conversation( entry->conversation_id );
		conversation_entry const& conv_entry = conv->entries[ entry->conversation_index ];
		return &conv_entry.event;
		}


	void select_option( int option_index )
		{
		if( option_index >= 0 && option_index < current_option_list.count() )
			{
			option_to_execute = option_index;
			current_state = STATE_EXECUTING_OPTIONS;
			}
		}


	bool has_conversation( string_id conversation_id )
		{
		return find_conversation( conversation_id ) != 0;
		}

	
	conversation* find_conversation( string_id conversation_id )
		{
		for( int i = dialogs.count() - 1; i >= 0; --i )
			{
			dialog& dlg = dialogs[ i ];
			conversation* conv = dlg.find_conversation( conversation_id );
			if( conv && conversation_id == conv->id ) return conv;
			}

		return 0;
		}


	private:
		struct stack_entry
			{
			string_id conversation_id;
			int conversation_index;			
			};


		stack_entry* stack_top()
			{
			if( stack.count() == 0 ) return 0;

			return &stack[ stack.count() - 1 ];
			}
			
			
		stack_entry& stack_push()
			{
			return stack.add( stack_entry() );
			}
			
			
		void stack_pop()
			{
			if( stack.count() == 0 ) return;
			stack.remove( stack.count() - 1 );
			}


		int stack_size()
			{
			return stack.count();
			}


		void stack_clear()
			{
			stack.clear();
			}


		void run_conversation_entry( string_id conversation_id, int current_index )
			{
			const conversation* conv = find_conversation( conversation_id );
			
			// Is conv finished?
			if( current_index >= conv->entries.count() )
				{
				// Yes, so bail out
				current_state = STATE_READY;
				current_option_list.clear();
				stack_clear();
				return;
				}

			conversation_entry const* entry = &conv->entries[ current_index ];
			if( entry->type == conversation_entry::ENTRY_TYPE_LINE )
				{
				current_state = STATE_DISPLAYING_SPEECH;
				}
			else if( entry->type == conversation_entry::ENTRY_TYPE_OPTION )
				{
				while( entry->type == conversation_entry::ENTRY_TYPE_OPTION )
					{
					run_conversation_entry( entry->option );
					++current_index;
					if( conv->entries.count() == current_index ) break;
					entry = &conv->entries[ current_index ];
					}
					
				if( current_option_list.count() > 0 )
					{
					current_state = STATE_DISPLAYING_OPTIONS;
					}
				else
					{
					stack_entry* se = stack_top();
					PIXIE_ASSERT( se, "Conversation stack corrupted");
					se->conversation_index = current_index;
					current_state = STATE_RUN_CONVERSATION;
					}
				}
			else if( entry->type == conversation_entry::ENTRY_TYPE_REDIRECT )
				{
				redirect( entry->redirect.target );
				}
			else if( entry->type == conversation_entry::ENTRY_TYPE_EVENT )
				{
				current_state = STATE_SENDING_EVENT;
				}
			}
		
		
		void run_conversation_entry(conversation_option option)
			{
			stack_entry* entry = stack_top();
			PIXIE_ASSERT( entry, "Conversation stack corrupted" );

			for( int i = 0; i < used_options.count(); ++i )
				{
				used_option_entry& used_option = used_options[ i ];
				if( used_option.conversation_id == entry->conversation_id && used_option.line == option.line ) return;
				}

			current_option_list.add( option );
			}
			

		void redirect( string_id target )
			{
			if( target == "RETURN" && stack_size() > 1 )
				{
				stack_pop();
				current_state = STATE_RUN_CONVERSATION;
				stack_entry* top = stack_top();
				if( top ) top->conversation_index = 0;
				}
			else if( target == "EXIT" || ( target == "RETURN" && stack_size() <= 1 ) )
				{
				current_state = STATE_READY;
				current_option_list.clear();
				stack_clear();
				}
			else
				{
				// Find the right conv
				conversation const* conv = find_conversation( target );
				if( conv )
					{
					stack_entry& se = stack_push();
					se.conversation_id = target;
					se.conversation_index = 0;
					current_state = STATE_RUN_CONVERSATION;
					}
				else
					{
					current_state = STATE_READY;
					}
				}
			}
			
		
	private:

		dialog_state current_state;
		array<dialog> dialogs;
		array<stack_entry> stack;

		array<conversation_option> current_option_list;
		struct used_option_entry
			{
			string_id conversation_id;
			string_id line;
			};
		array<used_option_entry> used_options; 
		int option_to_execute;
	};


#endif /* dialog_playback_hpp */
