/* $Id$ */

/*
 * This file is part of OpenTTD.
 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
 * OpenTTD 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 General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
 */

/** @file programmable_signals.h Programmable Signals. */

#ifndef PROGRAMMABLE_SIGNALS_H
#define PROGRAMMABLE_SIGNALS_H
#include "rail_map.h"
#include "core/smallvec_type.hpp"
#include <map>

/** @defgroup progsigs Programmable Signals. */
///@{

/**
 * The Programmable Signal virtual machine.
 *
 * This structure contains the state of the currently executing signal program.
 */
struct SignalVM;

class SignalInstruction;
class SignalSpecial;
typedef SmallVector<SignalInstruction*, 4> InstructionList;

/** The actual programmable signal information. */
struct SignalProgram {
	SignalProgram(TileIndex tile, Track track, bool raw = false);
	~SignalProgram();
	void DebugPrintProgram();
	
	TileIndex tile;
	Track track;
	
	SignalSpecial *first_instruction;
	SignalSpecial *last_instruction;
	InstructionList instructions;
};

/**
 * Programmable Signal opcode.
 *
 * Opcode types are discriminated by this enumeration. It is primarily used for
 * code which must be able to inspect the type of a signal operation, rather than
 * evaluate it (such as the programming GUI).
 */
enum SignalOpcode {
	PSO_FIRST      = 0,   ///< Start pseudo instruction.
	PSO_LAST       = 1,   ///< End pseudo instruction.
	PSO_IF         = 2,   ///< If instruction.
	PSO_IF_ELSE    = 3,   ///< If Else pseudo instruction.
	PSO_IF_ENDIF   = 4,   ///< If Endif pseudo instruction.
	PSO_SET_SIGNAL = 5,   ///< Set signal instruction.
	
	PSO_END,
	PSO_INVALID   = 0xFF
};
template <> struct EnumPropsT<SignalOpcode> : MakeEnumPropsT<SignalOpcode, byte, PSO_FIRST, PSO_END, PSO_INVALID, 8> {};

/** Signal instruction base class. All instructions must derive from this. */
class SignalInstruction {
public:
	/* Get the instruction's opcode. */
	inline SignalOpcode Opcode() const { return this->opcode; }
	
	/* Get the previous instruction. If this is NULL, then this is the first
	 * instruction. */
	inline SignalInstruction *Previous() const { return this->previous; }
	
	/* Get the Id of this instruction. */
	inline int Id() const 
	/* Const cast is safe (perculiarity of SmallVector). */
	{ return program->instructions.FindIndex(const_cast<SignalInstruction*>(this)); }
	
	/* Insert this instruction, placing it before @p before_insn. */
	virtual void Insert(SignalInstruction *before_insn);
	
	/* Evaluate the instruction. The instruction should update the VM state. */
	virtual void Evaluate(SignalVM &vm) = 0;
	
	/* Remove the instruction. When removing itself, an instruction should
	 * <ul>
	 *   <li>Set next->previous to previous.
	 *   <li>Set previous->next to next.
	 *   <li>Destroy any other children.
	 * </ul> */
	virtual void Remove() = 0;
	
	/* Gets a reference to the previous member. This is only intended for use by
	 * the saveload code. */
	inline SignalInstruction *&GetPrevHandle()
	{ return previous; }

	/* Sets the previous instruction of this instruction. This is only intended 
	 * to be used by instructions to update links during insertion and removal. */
	inline void SetPrevious(SignalInstruction *prev)
	{ previous = prev; }
	/* Set the next instruction. This is only intended to be used by instructions
	 * to update links during insertion and removal. */
	virtual void SetNext(SignalInstruction *next_insn) = 0;
	
protected:
	/* Constructs an instruction.
	 * @param prog the program to add this instruction to
	 * @param op the opcode of the instruction. */
	SignalInstruction(SignalProgram *prog, SignalOpcode op) ;
	~SignalInstruction();

	const SignalOpcode opcode;
	SignalInstruction *previous;
	SignalProgram *program;
};

/**
 * Programmable Signal condition code.
 *
 * These discriminate conditions in much the same way that SignalOpcode 
 * discriminates instructions.
 */
enum SignalConditionCode {
	PSC_ALWAYS = 0,       ///< Always true.
	PSC_NEVER = 1,        ///< Always false.
	PSC_NUM_GREEN = 2,    ///< Number of green signals behind this signal.
	PSC_NUM_RED = 3,      ///< Number of red signals behind this signal.
	PSC_SIGNAL_STATE = 4, ///< State of another signal.
	
	PSC_MAX = PSC_SIGNAL_STATE
};

class SignalCondition {
public:
	/* Get the condition's code. */
	inline SignalConditionCode ConditionCode() const { return this->cond_code; }
	
	/* Evaluate the condition. */
	virtual bool Evaluate(SignalVM& vm) = 0;
	
	/* Destroy the condition. Any children should also be destroyed. */
	virtual ~SignalCondition();
	
protected:
	SignalCondition(SignalConditionCode code) : cond_code(code) {}
	
	const SignalConditionCode cond_code;
};


/* -- Condition codes. -- */

/**
 * Simple condition code. These conditions have no complex inputs, and can be
 * evaluated directly from VM state and their condition code.
 */
class SignalSimpleCondition: public SignalCondition {
public:
	SignalSimpleCondition(SignalConditionCode code);
	virtual bool Evaluate(SignalVM& vm);
};

/** Comparator to use for variable conditions. */
enum SignalComparator {
	SGC_EQUALS = 0,           ///< the variable is equal to the specified value.
	SGC_NOT_EQUALS = 1,       ///< the variable is not equal to the specified value.
	SGC_LESS_THAN = 2,        ///< the variable is less than specified value.
	SGC_LESS_THAN_EQUALS = 3, ///< the variable is less than or equal to the specified value.
	SGC_MORE_THAN = 4,        ///< the variable is greater than the specified value.
	SGC_MORE_THAN_EQUALS = 5, ///< the variable is grater than or equal to the specified value.
	SGC_IS_TRUE = 6,          ///< the variable is true (non-zero).
	SGC_IS_FALSE = 7,         ///< the variable is false (zero).
	
	SGC_LAST = SGC_IS_FALSE
};

/** Which field to modify in a condition. A parameter to CMD_MODIFY_SIGNAL_INSTRUCTION. */
enum SignalConditionField {
	SCF_COMPARATOR = 0, ///< the comparator (value from SignalComparator enum).
	SCF_VALUE = 1,      ///< the value (integer value).
};

/**
 * A conditon based upon comparing a variable and a value. This condition can be
 * considered similar to the conditonal jumps in vehicle orders.
 *
 * The variable is specified by the conditon code, the comparison by @p comparator, and
 * the value to compare against by @p value. The condition returns the result of that value.
 */
class SignalVariableCondition: public SignalCondition {
public:
	/* Constructs a condition refering to the value @p code refers to. Sets the
	 * comparator and value to sane defaults. */
	SignalVariableCondition(SignalConditionCode code);
	
	SignalComparator comparator;
	uint32 value;
	
	/* Evaluates the condition. */
	virtual bool Evaluate(SignalVM &vm);
};

/** A condition which is based upon the state of another signal. */
class SignalStateCondition: public SignalCondition {
	public:
		SignalStateCondition(SignalReference this_sig, TileIndex sig_tile, 
												 Trackdir sig_track);
		
		void SetSignal(TileIndex tile, Trackdir track);
		bool IsSignalValid();
        void Invalidate();
		
		virtual bool Evaluate(SignalVM& vm);
		virtual ~SignalStateCondition();
		
		SignalReference this_sig;
		TileIndex sig_tile;
		Trackdir sig_track;
		SignalState state;
};


/* -- Instructions. -- */

/**
 * The special start and end pseudo instructions.
 *
 * These instructions serve two purposes:
 * <ol>
 *   <li>They permit every other instruction to assume that there is another
 *       following it. This makes the code much simpler (and by extension less
 *       error prone).</li>
 *   <li>Particularly in the case of the End instruction, they provide an 
 *       instruction in the user interface that can be clicked on to add 
 *       instructions at the end of a program.</li>
 * </ol>
 */
class SignalSpecial: public SignalInstruction {
public:
	/**
	 * Constructs a special signal of the opcode @p op in program @p prog.
	 *
	 * Generally you should not need to call this; it will be called by the 
	 * program's constructor. An exception is in the saveload code, which needs
	 * to construct raw objects to deserialize into.
	 */
	SignalSpecial(SignalProgram *prog, SignalOpcode op);
	
	/**
	 * Evaluates the instruction. If this is an Start instruction, flow will be
	 * vectored to the first instruction; if it is an End instruction, the program
	 * will terminate and the signal will be left red.
	 */
	virtual void Evaluate(SignalVM &vm);
	
	/**
	 * Links the first and last instructions in the program. Generally only to be
	 * called from the SignalProgram constructor.
	 */
	static void link(SignalSpecial *first, SignalSpecial *last);
	
	/**
	 * Removes this instruction. If this is the start instruction, then all of
	 * the other instructions in the program will be successively removed,
	 * (emptying it). If this is the End instruction, then it will do nothing.
	 *
	 * This operation, unlike when executed on most instructions, does not destroy
	 * the instruction.
	 */
	virtual void Remove();
	
	/**
	 * The next instruction after this one. On the End instruction, this should 
	 * be NULL.
	 */
	SignalInstruction *next;
	
	virtual void SetNext(SignalInstruction *next_insn);
};

/**
 * If signal instruction. This is perhaps the most important, as without it,
 *  programmable signals are pretty useless.
 *
 * It's also the most complex!
 */
class SignalIf: public SignalInstruction {
public:
	/**
	 * The If-Else and If-Endif pseudo instructions. The Else instruction 
	 * follows the Then block, and the Endif instruction follows the Else block.
	 *
	 * These serve two purposes:
	 * <ul>
	 *  <li>They correctly vector the execution to after the if block 
	 *      (if needed).
	 *  <li>They provide an instruction for the GUI to insert other instructions
	 *      before.
	 * </ul>
	 */
	class PseudoInstruction: public SignalInstruction {
	public:
		/**
		 * Normal constructor. The pseudo instruction will be constructed as 
		 * belonging to @p block.
		 */
		PseudoInstruction(SignalProgram *prog, SignalIf *block, SignalOpcode op);
		
		/**
		 * Constructs an empty instruction of type @p op. This should only be used
		 * by the saveload code during deserialization. The instruction must have
		 * its block field set correctly before the program is run.
		 */
		PseudoInstruction(SignalProgram *prog, SignalOpcode op);
		
		/**
		 * Removes the pseudo instruction. Unless you are also removing the If it
		 * belongs to, this is nonsense and dangerous.
		 */
		virtual void Remove();
		
		/**
		 * Evaluate the pseudo instruction. This involves vectoring execution to
		 * the instruction after the if.
		 */
		virtual void Evaluate(SignalVM &vm);
		
		/** The block to which this instruction belongs. */
		SignalIf *block;
		virtual void SetNext(SignalInstruction *next_insn);
	};
	
public:
	/**
	 * Constructs an If instruction belonging to program @p prog. If @p raw is 
	 * true, then the instruction is constructed raw (in order for the 
	 * deserializer to be able to correctly deserialize the instruction).
	 */
	SignalIf(SignalProgram *prog, bool raw = false);
	
	/** Sets the instruction's condition, and releases the old condition. */
	void SetCondition(SignalCondition *cond);
	
	/** Evaluates the If and takes the appropriate branch. */
	virtual void Evaluate(SignalVM &vm);
	
	virtual void Insert(SignalInstruction *before_insn);
	
	/** Removes the If and all of its children. */
	virtual void Remove();
	
	SignalCondition *condition;    ///< The if conditon.
	SignalInstruction *if_true;    ///< The branch to take if true.
	SignalInstruction *if_false;   ///< The branch to take if false.
	SignalInstruction *after;      ///< The branch to take after the If.
	
	virtual void SetNext(SignalInstruction *next_insn);
};

/** Set signal instruction. This sets the state of the signal and terminates execution. */
class SignalSet: public SignalInstruction {
public:
	/* Constructs the instruction and sets the state the signal is to be set to. */
	SignalSet(SignalProgram *prog, SignalState = SIGNAL_STATE_RED);
	
	virtual void Evaluate(SignalVM &vm);
	virtual void Remove();
	
	/* The state to set the signal to. */
	SignalState to_state;
	
	/* The instruction following this one (for the editor). */
	SignalInstruction *next;
	
	virtual void SetNext(SignalInstruction *next_insn);
};

/* The map type used for looking up signal programs. */
typedef std::map<SignalReference, SignalProgram*> ProgramList;

/* The global signal program list. */
extern ProgramList _signal_programs;

/* The map type used for looking up signal programs. */
typedef std::map<SignalReference, uint16> SpeedLimits;

/* The global signal program list */
extern SpeedLimits _speedlimits;

/* Verifies that a SignalReference refers to a signal which has a program. */
static inline bool HasProgrammableSignals(SignalReference ref)
{
	return IsTileType(ref.tile, MP_RAILWAY) && GetRailTileType(ref.tile) == RAIL_TILE_SIGNALS
	    && IsPresignalProgrammable(ref.tile, ref.track);
}

/* Verifies that a SignalReference refers to a speed-signal. */
static inline bool HasSpeedSignals(SignalReference ref)
{
	return GetRailTileType(ref.tile) == RAIL_TILE_SIGNALS
	    && IsSpeedSignal(ref.tile, ref.track);
}

/* Shows the programming window for the signal identified by @p tile and 
 * @p track. */
void ShowSignalProgramWindow(SignalReference ref);

/* Shows the window to set the value of the speed-signal identified by @p
 * tile and @p track. */
void ShowSpeedSignalWindow(SignalReference ref);

/* Gets the signal program for the tile identified by @p t and @p track.
 * An empty program will be constructed if none is specified. */
SignalProgram *GetSignalProgram(SignalReference ref);

/* Gets the speed for the signal identified by @p t and @p track. */
uint16 GetSignalSpeed(SignalReference ref);

/* Frees a signal program by tile and track. */
void FreeSignalProgram(SignalReference ref);

/* Frees all signal programs (For use when creating a new game). */
void FreeSignalPrograms();

/* Frees a signal speed by tile and track. */
void FreeSignalSpeed(SignalReference ref);

/* Frees all signal speeds (For use when creating a new game). */
void FreeSignalSpeeds();

/* Runs the signal program, specifying the following parameters. */
SignalState RunSignalProgram(SignalReference ref, uint num_exits, uint num_green);

/* Remove dependencies on signal @p on from @p by. */
void RemoveProgramDependencies(SignalReference by, SignalReference on);
///@}

#endif
