/**
 * @author	STF 424_ITS_Test_Platform
 * @version    $URL:$
 *             $Id:$
 */
package org.etsi.its.codec.uppertester;

import java.util.LinkedList;
import java.util.Map;

import org.etsi.common.ByteHelper;
import org.etsi.its.codec.TestAdapterMessageTypeEnum;
import org.etsi.its.codec.adapter.IType;
import org.etsi.its.codec.adapter.IValue;
import org.etsi.its.codec.adapter.UpperTesterCodecImpl;

/**
 * This class provides a representation in TLV sequence format of Cam/denm PDU
 */
public class CamPduInstance implements IPduInstance {

	/**
	 * This list hosts all Cam/denm PDU elements
	 * @see PduItem
	 */
	private LinkedList<PduItem> _items;

	/**
	 * This map will contains all fields set in the CAM/DENM PDU
	 * The key shall match TTCN-3 field name, i.g. generationTime
	 * The value shall be the name of the method to be called in Hitachi ITSCam object to be compared with our CAM/DENM PDU
	 */
	private Map<String, String> _camPduTypeIdentifiers;

	/**
	 * The instance of the class UpperTesterCodecImpl
	 */
	private static UpperTesterCodecImpl _utImpl;

	/**
	 * Counter used to parse <code>_items</code>
	 * 
	 * @see "GetFirstItem(), GetNextItem(), GetPrevItem()"
	 */
	private int _idx;

	/**
	 * Constructor
	 */
	private CamPduInstance(Map<String, String> camPduTypeIdentifiers) {
		_items = new LinkedList<PduItem>();
		_idx = -1;
		_camPduTypeIdentifiers = camPduTypeIdentifiers;
		_utImpl = new UpperTesterCodecImpl();
	}

	/**
	 * Add a new <code>PduItem</code> value in the list
	 * @param item The  <code>PduItem</code> value to be added
	 */
	public void AddItem(final PduItem item) {
		_items.add(item);
	}

	/**
	 * This method returns the name of the method (provided by the IUT's API, e.g. Hitachi or Nec) to be executed to retrieve the physical value of the Cam/denm PDU element 
	 * @param itemType The the Cam/denm PDU element
	 * @return The name of a method
	 */
	public String GetMethodFromItemType(final String itemType) {
		// Sanity check
		if (itemType == null) {
			return null;
		}

		if (_camPduTypeIdentifiers.containsKey(itemType)) {
			return _camPduTypeIdentifiers.get(itemType);
		}

		return null;
	} // End of method GetMethodFromItemType

	/**
	 * This methods provides the first Cam/Denm PDU element from the list <code>_items</code>
	 * @return The first element of the list, null if the list is empty
	 */
	public PduItem GetFirstItem() {
		// Sanity check
		if (_items.size() == 0) {
			return null;
		}

		_idx = 0;
		return _items.get(_idx);
	} // End of method GetFirstItem

	/**
	 * This methods provides the next Cam/Denm PDU element from the list <code>_items</code>
	 * @return The next element of the list, null if the end of the list was reached
	 */
	public PduItem GetNextItem() {
		_idx += 1;

		if (_idx < _items.size()) {
			return _items.get(_idx);
		}

		return null;
	} // End of method GetNextItem

	/**
	 * This methods provides the previous Cam/Denm PDU element from the list <code>_items</code>
	 * @return The previous element of the list, null if the beginning of the list was reached
	 */
	public PduItem GetPrevItem() {
		_idx -= 1;

		if ((_idx >= 0) && (_idx < _items.size())) {
			return _items.get(_idx);
		}

		return null;
	} // End of method GetPreviousItem

	/**
	 * Initialise this module
	 * 
	 * @param pduInTLVFormat
	 * @return true on success, false otherwise
	 */
	private boolean initialize(final byte[] pduInTLVFormat) {
		// Sanity checks already done

		// Convert byte array into CamPduItem suite
		IValue tlvs = _utImpl.Decode(pduInTLVFormat);
		if (tlvs == null) {
			return false;
		}
		// Skip UT fields
		IType item = tlvs.getFirstType(); // Skip checkLdm
		item = tlvs.getNextType(); // ItsPduHeader
		while (item != null) {
			System.out.println(String.format("CamPduInstance.initialize: process '%s'", ((TestAdapterMessageTypeEnum)item.getTypeId()).name())); // TODO To be removed
			switch ((TestAdapterMessageTypeEnum)item.getTypeId()) {
				case ItsPduHeader:
					tlvs.getNextType(); // Skip Protocol version, should not be checked
					tlvs.getNextType(); // Skip MessageID, should not be checked
					tlvs.getNextType(); // Skip Generation Time, should not be checked
					break;
				case CoopAwareness: {
						// stationID
						item = tlvs.getNextType();
						String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
						if (_camPduTypeIdentifiers.containsKey(key)) {
							AddItem(new PduItem(key, ByteHelper.byteArrayToLong((byte[])item.getValue())));
						} else {
							throw new RuntimeException(String.format("Key not found: %s", key));
						}
					} // End of case CoopAwareness
					break;
				case StationCharacteristics: {
						// mobileItsStation
						item = tlvs.getNextType();
						String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
						if (_camPduTypeIdentifiers.containsKey(key)) {
							AddItem(new PduItem(key, ByteHelper.byteArrayToBoolean((byte[])item.getValue())));
						} else {
							throw new RuntimeException(String.format("Key not found: %s", key));
						}
						// privateItsStation
						item = tlvs.getNextType();
						key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
						if (_camPduTypeIdentifiers.containsKey(key)) {
							AddItem(new PduItem(key, ByteHelper.byteArrayToBoolean((byte[])item.getValue())));
						} else {
							throw new RuntimeException(String.format("Key not found: %s", key));
						}
						// physicalRelevantItsStation
						item = tlvs.getNextType();
						key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
						if (_camPduTypeIdentifiers.containsKey(key)) {
							AddItem(new PduItem(key, ByteHelper.byteArrayToBoolean((byte[])item.getValue())));
						} else {
							throw new RuntimeException(String.format("Key not found: %s", key));
						}
					} // End of case StationCharacteristics
					break;
				case ReferencePosition:
					// Skip it
					break;
				case Longitude: { // See DENM.asn - Longitude ::= SEQUENCE {...} - See http://fr.wikipedia.org/wiki/Longitude
						// Retrieve Longitude information
						IType hemisphere = tlvs.getNextType(); // hemisphere
						IType degree = tlvs.getNextType(); // degree
						// Convert it into a double value
						Double longitude = new Double((ByteHelper.byteArrayToLong((byte[])degree.getValue())) * 0.0000001); // Multiples of 0.1 microdegree;
						if (ByteHelper.byteArrayToInt((byte[])hemisphere.getValue()) == 1) { // north - See DENM.asn - hemisphere ENUMERATED { east (0), west (1) }
							longitude *= -1.0;
						}
						String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name().toLowerCase();
						if (_camPduTypeIdentifiers.containsKey(key)) {
							AddItem(new PduItem(key, longitude));
						} else {
							throw new RuntimeException(String.format("Key not found: %s", key));
						}
					} // End of case Longitude
					break;
				case Latitude: { // See DENM.asn - Latitude ::= SEQUENCE {...} - See http://fr.wikipedia.org/wiki/Latitude
						// Retrieve Latitude information
						IType hemisphere = tlvs.getNextType(); // hemisphere
						IType degree = tlvs.getNextType(); // degree
						// Convert it into a double value
						Double latitude = new Double((ByteHelper.byteArrayToLong((byte[])degree.getValue())) * 0.0000001); // Multiples of 0.1 microdegree
						if (ByteHelper.byteArrayToInt((byte[])hemisphere.getValue()) == 1) { // north - See DENM.asn - hemisphere ENUMERATED { north (0), south (1) }
							latitude *= -1.0;
						}
						String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name().toLowerCase();
						if (_camPduTypeIdentifiers.containsKey(key)) {
							AddItem(new PduItem(key, latitude));
						} else {
							throw new RuntimeException(String.format("Key not found: %s", key));
						}
					} // End of case Latitude
					break;
				case elevation: { // See DENM.asn - Elevation ::= INTEGER (-10000..16767215)
						Double elevation = new Double((ByteHelper.byteArrayToInt((byte[])item.getValue())) * 0.1); // Multiples of 0.1 m
						String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
						if (_camPduTypeIdentifiers.containsKey(key)) {
							AddItem(new PduItem(key, elevation));
						} else {
							throw new RuntimeException(String.format("Key not found: %s", key));
						}
					} // End of case elevation
					break;
				case heading: { // See DENM.asn - Direction ::= INTEGER{north(0), east(7200), south(14400), west(21600)} (0..28799)
						if (((byte[])item.getValue()).length != 0) { // Not omitted
							Double heading = new Double((ByteHelper.byteArrayToInt((byte[])item.getValue())) * 0.01);
							String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
							if (_camPduTypeIdentifiers.containsKey(key)) {
								AddItem(new PduItem(key, heading));
							} else {
								throw new RuntimeException(String.format("Key not found: %s", key));
							}
						}
					} // End of case heading
					break;
				case positionConfidence: 
					// No break;
				case elevationConfidence: {
						if (((byte[])item.getValue()).length != 0) { // Not omitted
							Integer confidence = ByteHelper.byteArrayToInt((byte[])item.getValue());
							String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
							if (_camPduTypeIdentifiers.containsKey(key)) {
								AddItem(new PduItem(key, confidence));
							} else {
								throw new RuntimeException(String.format("Key not found: %s", key));
							}
						}
					} // End of case elevationConfidence
					break;
				case streetName: { // See DENM.asn - StreetName ::= IA5String(SIZE(1..32))
						if (((byte[])item.getValue()).length != 0) { // Not omitted
							String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
							if (_camPduTypeIdentifiers.containsKey(key)) {
								AddItem(new PduItem(key, ByteHelper.byteArrayWithLengthToString((byte[])item.getValue())));
							} else {
								throw new RuntimeException(String.format("Key not found: %s", key));
							}
						}
					} // End of case streetName
					break;
				case roadSegmentID: { // See DENM.asn - RoadSegmentID ::= INTEGER (0..99999999)
						if (((byte[])item.getValue()).length != 0) { // Not omitted
							String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
							if (_camPduTypeIdentifiers.containsKey(key)) {
								AddItem(new PduItem(key, ByteHelper.byteArrayToInt((byte[])item.getValue())));
							} else {
								throw new RuntimeException(String.format("Key not found: %s", key));
							}
						}
					} // End of case roadSegmentID
					break;
				case CamParameters: 
					// No break;
				case YawRate: 
					// No break;
				case TurnAdvice: 
					// No break;
				case PosConfidenceEllipse: 
					// No break;
				case VehicleCommonParameters:
					// No break;
				case BasicVehicle:
					break;
				case camParameters:
					if (((byte[])item.getValue()).length != 0) { // Not omitted
						throw new RuntimeException();
					}
					break;
				case profileDependent:
					if (((byte[])item.getValue()).length != 0) { // Not omitted
						throw new RuntimeException();
					}
					break;
				case vehicleType:
					// No break;
				case occupancy :
					// No break;
				case curvature:
					// No break;
				case curvatureChange:
					// No break;
				case dangerousGoods: {
						if (((byte[])item.getValue()).length != 0) { // Not omitted
							String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
							if (_camPduTypeIdentifiers.containsKey(key)) {
								AddItem(new PduItem(key, ByteHelper.byteArrayToInt((byte[])item.getValue())));
							} else {
								throw new RuntimeException(String.format("Key not found: %s", key));
							}
						}
					}
					break;
				case accelerationControl: { // See DENM.asn - AccelerationControl ::= BIT STRING {...}
						String key = null;
						byte flags = (byte)(int)ByteHelper.byteArrayToInt((byte[])item.getValue());
						if ((flags & 0x01) != 0x00) {
							key = "accelerationControl_BrakePedal";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if ((flags & 0x02) != 0x00) {
							key = "accelerationControl_ThrottlePedal";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if ((flags & 0x04) != 0x00) {
							key = "accelerationControl_CruiseControl";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if ((flags & 0x08) != 0x00) {
							key = "accelerationControl_Acc";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if ((flags & 0x10) != 0x00) {
							key = "accelerationControl_Limiter";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if ((flags & 0x20) != 0x00) {
							key = "accelerationControl_BrakeAssist";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if (key == null) {
							AddItem(new PduItem("accelerationControl_BrakePedal", new Boolean(false)));
							AddItem(new PduItem("accelerationControl_ThrottlePedal", new Boolean(false)));
							AddItem(new PduItem("accelerationControl_CruiseControl", new Boolean(false)));
							AddItem(new PduItem("accelerationControl_Acc", new Boolean(false)));
							AddItem(new PduItem("accelerationControl_Limiter", new Boolean(false)));
							AddItem(new PduItem("accelerationControl_BrakeAssist", new Boolean(false)));
						}
					}
					break;
				case stationLengthConfidence:
					// No break;
				case stationWidthConfidence:
					// No break;
				case vehicleSpeedConfidence:
					// No break;
				case longAccelerationConfidence:
					// No break;
				case yawRateConfidence:
					// No break;
				case curvatureConfidence:
					// No break;
				case headingConfidence:
					// No break;
				case semiMajorConfidence:
					// No break;
				case semiMinorConfidence: {
						if (((byte[])item.getValue()).length != 0) { // Not omitted
							String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
							if (_camPduTypeIdentifiers.containsKey(key)) {
								AddItem(new PduItem(key, ByteHelper.byteArrayToInt((byte[])item.getValue())));
							} else {
								throw new RuntimeException(String.format("Key not found: %s", key));
							}
						}
					}
					break;
				case stationWidth:
					// No break;
				case longAcceleration: // multiples of 0.01 m/s^2
					// No break;
				case yawRateValue:
					// No break;
				case stationLength: {
						String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
						if (_camPduTypeIdentifiers.containsKey(key)) {
							AddItem(new PduItem(key, new Float((float)ByteHelper.byteArrayToInt((byte[])item.getValue()) / 100.0)));
						} else {
							throw new RuntimeException(String.format("Key not found: %s", key));
						}
					}
					break;
				case distance:
					// No break;
				case distanceToStopLine: {
						String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
						if (_camPduTypeIdentifiers.containsKey(key)) {
							AddItem(new PduItem(key, new Float((float)ByteHelper.byteArrayToInt((byte[])item.getValue()))));
						} else {
							throw new RuntimeException(String.format("Key not found: %s", key));
						}
					}
					break;
				case vehicleSpeed: {
						String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
						if (_camPduTypeIdentifiers.containsKey(key)) {
							AddItem(new PduItem(key, new Double((double)ByteHelper.byteArrayToInt((byte[])item.getValue()) / 100.0)));
						} else {
							throw new RuntimeException(String.format("Key not found: %s", key));
						}
					}
					break;
				case semiMajorOrientation: {
						String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
						if (_camPduTypeIdentifiers.containsKey(key)) {
							AddItem(new PduItem(key, new Double((double)(ByteHelper.byteArrayToInt((byte[])item.getValue())) / 80.0)));
						} else {
							throw new RuntimeException(String.format("Key not found: %s", key));
						}
					}
					break;
				case direction : {
						String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
						byte flags = (byte)(int)ByteHelper.byteArrayToInt((byte[])item.getValue());
						int value = 0;
						switch (flags) {
							case 0x01: // TurnDirection_sharpLeft_
								value = 7;
								break;
							case 0x02: // TurnDirection_Left_
								value = 6;
								break;
							case 0x04: // TurnDirection_slightLeft_
								value = 5;
								break;
							case 0x08: // TurnDirection_straight_
								value = 4;
								break;
							case 0x10: // TurnDirection_slightRight_
								value = 3;
								break;
							case 0x20: // TurnDirection_right_
								value = 2;
								break;
							case 0x30: // TurnDirection_sharpRight_
								value = 1;
								break;
							case 0x40: // TurnDirection_uTurn_
								value = 0;
								break;
						} // End of 'switch' statement
						AddItem(new PduItem(key, new Integer(value)));
					}
					break;
				case yawDirection: {
						String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
						if (_camPduTypeIdentifiers.containsKey(key)) {
							AddItem(new PduItem(key, ByteHelper.byteArrayToInt((byte[])item.getValue())));
						} else {
							throw new RuntimeException(String.format("Key not found: %s", key));
						}
					}
					break;
				case exteriorLights: { // See DENM.asn - ExteriorLights ::= BIT STRING {...}
						String key = null;
						byte flags = (byte)(int)ByteHelper.byteArrayToInt((byte[])item.getValue());
						if ((flags & 0x01) != 0x00) {
							key = "exteriorLights_LowBeamHeadlightsOn";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if ((flags & 0x02) != 0x00) {
							key = "exteriorLights_HighBeamHeadlightsOn";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if ((flags & 0x04) != 0x00) {
							key = "exteriorLights_LeftTurnSignalOn";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if ((flags & 0x08) != 0x00) {
							key = "exteriorLights_RightTurnSignalOn";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if ((flags & 0x10) != 0x00) {
							key = "exteriorLights_AutomaticLightControlOn";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if ((flags & 0x20) != 0x00) {
							key = "exteriorLights_DaytimeRunningLightsOn";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if ((flags & 0x30) != 0x00) {
							key = "exteriorLights_FogLightOn";
							AddItem(new PduItem(key, new Boolean(true)));
						}
						if ((flags & 0x40) != 0x00) {
							key = "exteriorLights_ParkingLightsOn";
							AddItem(new PduItem(key, new Boolean(true)));
						} 
						if (key == null) {
							AddItem(new PduItem("exteriorLights_LowBeamHeadlightsOn", new Boolean(false)));
							AddItem(new PduItem("exteriorLights_HighBeamHeadlightsOn", new Boolean(false)));
							AddItem(new PduItem("exteriorLights_LeftTurnSignalOn", new Boolean(false)));
							AddItem(new PduItem("exteriorLights_RightTurnSignalOn", new Boolean(false)));
							AddItem(new PduItem("exteriorLights_AutomaticLightControlOn", new Boolean(false)));
							AddItem(new PduItem("exteriorLights_DaytimeRunningLightsOn", new Boolean(false)));
							AddItem(new PduItem("exteriorLights_FogLightOn", new Boolean(false)));
							AddItem(new PduItem("exteriorLights_ParkingLightsOn", new Boolean(false)));
						}
					}
					break;
				case turnAdvice:
					break;
				case doorOpen: { // See DENM.asn - DoorOpen ::= BIT STRING {...}
					String key = null;
					byte flags = (byte)(int)ByteHelper.byteArrayToInt((byte[])item.getValue());
					if ((flags & 0x01) != 0x00) { // DoorOpen_luggage_
						key = "doorOpen_Luggage";
						AddItem(new PduItem(key, new Boolean(true)));
					}
					if ((flags & 0x02) != 0x00) { // any passenger door
						key = "doorOpen_Maintenance";
						AddItem(new PduItem(key, new Boolean(true)));
					}
					if ((flags & 0x04) != 0x00) { // hood, other access to engine, or similar
						key = "doorOpen_Passenger";
						AddItem(new PduItem(key, new Boolean(true)));
					}
					if ((flags & 0x08) != 0x00) {// DoorOpen_driver_
						key = "doorOpen_Driver";
						AddItem(new PduItem(key, new Boolean(true)));
					}
					if (key == null) {
						AddItem(new PduItem("doorOpen_Driver", new Boolean(false)));
						AddItem(new PduItem("doorOpen_Passenger", new Boolean(false)));
						AddItem(new PduItem("doorOpen_Maintenance", new Boolean(false)));
						AddItem(new PduItem("doorOpen_Luggage", new Boolean(false)));
					}
				}
				break;
				case crashStatus: {
						if (((byte[])item.getValue()).length != 0) { // Not omitted
							String key = ((TestAdapterMessageTypeEnum)item.getTypeId()).name();
							if (_camPduTypeIdentifiers.containsKey(key)) {
								AddItem(new PduItem(key, ByteHelper.byteArrayToBoolean((byte[])item.getValue())));
							} else {
								throw new RuntimeException(String.format("Key not found: %s", key));
							}
						}
					}
					break;
				default:
					throw new RuntimeException();
			} // End of 'switch' statement

			item = tlvs.getNextType();
		} // End of 'while' statement

		return true;
	}

	/**
	 * Create a new instance of this class
	 * 
	 * @param pduInTLVFormat
	 * @param pduTypeIdentifiers 
	 * @return A new instance of CamPduInstance
	 */
	public static CamPduInstance createInstance(final byte[] pduInTLVFormat, final Map<String, String> pduTypeIdentifiers) {
		// Sanity checks
		if ((pduInTLVFormat == null) || (pduInTLVFormat.length == 0) || (pduTypeIdentifiers == null) || (pduTypeIdentifiers.size() == 0)) {
			throw new IllegalArgumentException("CamPduInstance.createInstance: Wrong parameters, check that Cam/Denm PDU is not null or empty, or camPduTypeIdentifiers is properly built");
		}
		// Create a new instance of CamPduInstance class
		CamPduInstance instance = new CamPduInstance(pduTypeIdentifiers);
		if (!instance.initialize(pduInTLVFormat)) {
			throw new RuntimeException("CamPduInstance.createInstance: failed to initialize CamPduInstance instance");
		}
		
		return instance;
	} // End of method createInstance
	
} // End of class CamPduInstance
