/* $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 std::vector<SignalInstruction*> InstructionList;

enum SignalProgramMgmtCode {
	SPMC_REMOVE,      ///< Remove program
	SPMC_CLONE,       ///< Clone program
};

/** 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 nullptr, 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 find_index(program->instructions, 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) ;
	virtual ~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() const;
		bool CheckSignalValid();
		void Invalidate();

		virtual bool Evaluate(SignalVM& vm);
		virtual ~SignalStateCondition();

		SignalReference this_sig;
		TileIndex sig_tile;
		Trackdir sig_track;
};

// -- 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 nullptr.
	*/
	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;

/// 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);
}

/// Shows the programming window for the signal identified by @p tile and
/// @p track.
void ShowSignalProgramWindow(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);

SignalProgram *GetExistingSignalProgram(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();

/// 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 dependency_target, SignalReference signal_to_update);
///@}

#endif
