

package com.rapplogic.xbee.api.zigbee;

import java.io.IOException;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;

import com.rapplogic.xbee.api.IPacketParser;
import com.rapplogic.xbee.api.XBeeAddress16;
import com.rapplogic.xbee.api.XBeeFrameIdResponse;

/**
 * Series 2 XBee.  This is sent out the UART of the transmitting XBee immediately following
 * a Transmit packet.  Indicates if the Transmit packet (ZNetTxRequest)
 * was successful.
 * <p/>
 * API ID: 0x8b
 * <p/>
 * @author andrew
 */
public class ZNetTxStatusResponse extends XBeeFrameIdResponse {
	
	private final static Logger log = Logger.getLogger(ZNetTxStatusResponse.class);

	public enum DeliveryStatus {
//		0x00 = Success
//		0x01 = MAC ACK Failure
//		0x02 = CCA Failure
//		0x15 = Invalid destination
//		endpoint
//		0x21 = Network ACK Failure
//		0x22 = Not Joined to Network
//		0x23 = Self-addressed
//		0x24 = Address Not Found
//		0x25 = Route Not Found
//		0x26 = Broadcast source failed to hear a neighbor relay 
//		the message 
//		0x2B = Invalid binding table index
//		0x2C = Resource error lack of free buffers, timers, etc.
//		0x2D = Attempted broadcast with APS transmission
//		0x2E = Attempted unicast with APS transmission, but 
//		EE=0
//		0x32 = Resource error lack of free
//		0x74 = Data payload too large
		
		SUCCESS (0),
		MAC_FAILURE (1),
		CCA_FAILURE (0x02),
		INVALID_DESTINATION_ENDPOINT (0x15),
		NETWORK_ACK_FAILURE (0x21),
		NOT_JOINED_TO_NETWORK (0x22),
		SELF_ADDRESSED (0x23),
		ADDRESS_NOT_FOUND (0x24),
		ROUTE_NOT_FOUND (0x25),
		BROADCAST_SOURCE_NEIGHBOR_FAILURE (0x26),
		INVALID_BINDING_TABLE_INDEX (0x2B),
		RESOURCE_ERROR_LACK_FREE_BUFFERS (0x2C),
		ATTEMPTED_BROADCAST_WITH_APS_TX (0x2D),
		ATTEMPTED_UNICAST_WITH_APS_TX_EE_ZERO (0x2E),
		RESOURCE_ERROR_LACK_FREE_BUFFERS_0x32 (0x32), // WUT, SAME AS 2C
		PAYLOAD_TOO_LARGE(0x74), // ZB Pro firmware only
		UNKNOWN(-1);
		
		private static final Map<Integer,DeliveryStatus> lookup = new HashMap<Integer,DeliveryStatus>();
		
		static {
			for(DeliveryStatus s : EnumSet.allOf(DeliveryStatus.class)) {
				lookup.put(s.getValue(), s);
			}
		}
		
	    private final int value;
	    
	    DeliveryStatus(int value) {
	        this.value = value;
	    }

		public static DeliveryStatus get(int value) { 
			return lookup.get(value); 
		}
		
		public int getValue() {
			return value;
		}
	}

	public enum DiscoveryStatus {
//		0x00 = No Discovery	Overhead
//		0x01 = Address Discovery
//		0x02 = Route Discovery
//		0x03 = Address and Route
//		0x40 = Extended Timeout Discovery
				
		// NOTE 0x40 IS A bit field so going to be painful with enums
		
		NO_DISCOVERY (0),
		ADDRESS_DISCOVERY (1),
		ROUTE_DISCOVERY (2),
		ADDRESS_AND_ROUTE_DISCOVERY (3),
		EXTENDED_TIMEOUT_DISCOVERY (0x40),
		EXTENDED_TIMEOUT_DISCOVERY_0x41 (0x41),
		EXTENDED_TIMEOUT_DISCOVERY_0x42 (0x42),
		EXTENDED_TIMEOUT_DISCOVERY_0x43 (0x43),
		UNKNOWN(-1);

		private static final Map<Integer,DiscoveryStatus> lookup = new HashMap<Integer,DiscoveryStatus>();
		
		static {
			for(DiscoveryStatus s : EnumSet.allOf(DiscoveryStatus.class)) {
				lookup.put(s.getValue(), s);
			}
		}
		
	    private final int value;
	    
	    DiscoveryStatus(int value) {
	        this.value = value;
	    }

		public static DiscoveryStatus get(int value) { 
			return lookup.get(value); 
		}
		
		public int getValue() {
			return value;
		}
	}

	private XBeeAddress16 remoteAddress16;
	private int retryCount;
	private DeliveryStatus deliveryStatus;
	private DiscoveryStatus discoveryStatus;
	
	
	public ZNetTxStatusResponse() {

	}

	public XBeeAddress16 getRemoteAddress16() {
		return remoteAddress16;
	}


	public void setRemoteAddress16(XBeeAddress16 remoteAddress) {
		this.remoteAddress16 = remoteAddress;
	}


	public int getRetryCount() {
		return retryCount;
	}


	public void setRetryCount(int retryCount) {
		this.retryCount = retryCount;
	}

	public DeliveryStatus getDeliveryStatus() {
		return deliveryStatus;
	}

	public void setDeliveryStatus(DeliveryStatus deliveryStatus) {
		this.deliveryStatus = deliveryStatus;
	}

	public DiscoveryStatus getDiscoveryStatus() {
		return discoveryStatus;
	}

	public void setDiscoveryStatus(DiscoveryStatus discoveryStatus) {
		this.discoveryStatus = discoveryStatus;
	}
	
	/**
	 * Returns true if the delivery status is SUCCESS
	 * 
	 * @return
	 */
	public boolean isSuccess() {
		return this.getDeliveryStatus() == DeliveryStatus.SUCCESS;
	}
	
	public void parse(IPacketParser parser) throws IOException {		
		this.setFrameId(parser.read("ZNet Tx Status Frame Id"));

		this.setRemoteAddress16(parser.parseAddress16());
		this.setRetryCount(parser.read("ZNet Tx Status Tx Count"));
		
		int deliveryStatus = parser.read("ZNet Tx Status Delivery Status");
		
		if (DeliveryStatus.get(deliveryStatus) != null) {
			this.setDeliveryStatus(DeliveryStatus.get(deliveryStatus));	
		} else {
			log.warn("Unknown delivery status " + deliveryStatus);
			this.setDeliveryStatus(DeliveryStatus.UNKNOWN);
		}

		int discoveryStatus = parser.read("ZNet Tx Status Discovery Status");
		
		if (DiscoveryStatus.get(discoveryStatus) != null) {
			this.setDiscoveryStatus(DiscoveryStatus.get(discoveryStatus));	
		} else {
			log.warn("Unknown discovery status " + discoveryStatus);
			this.setDiscoveryStatus(DiscoveryStatus.UNKNOWN);
		}
	}
	
	public String toString() {
		return super.toString() + 
		",remoteAddress16=" + this.remoteAddress16 +
		",retryCount=" + this.retryCount +
		",deliveryStatus=" + this.deliveryStatus + 
		",discoveryStatus=" + this.discoveryStatus;
	}
}