package com.beowulfe.hap.characteristics;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import javax.json.JsonObjectBuilder;
import javax.json.JsonValue;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Base class for implementing {@link Characteristic}.
 *
 * @author Andy Lintner
 */
public abstract class BaseCharacteristic<T> implements Characteristic {
	
	private final Logger logger = LoggerFactory.getLogger(BaseCharacteristic.class);
	
	private final String type;
	private final String format;
	private final boolean isWritable;
	private final boolean isReadable;
	private final boolean isEventable;
	private final String description;
	
	/**
	 * Default constructor
	 * 
	 * @param type a string containing a UUID that indicates the type of characteristic. Apple defines a set of these,
	 *  however implementors can create their own as well.
	 * @param format a string indicating the value type, which must be a recognized type by the consuming device.
	 * @param isWritable indicates whether the value can be changed.
	 * @param isReadable indicates whether the value can be retrieved.
	 * @param description a description of the characteristic to be passed to the consuming device.
	 */
	public BaseCharacteristic(String type, String format, boolean isWritable, boolean isReadable, String description) {
		if (type == null || format == null || description == null) {
			throw new NullPointerException();
		}
		
		this.type = type;
		this.format = format;
		this.isWritable = isWritable;
		this.isReadable = isReadable;
		this.isEventable = this instanceof EventableCharacteristic;
		this.description = description;
	}

	@Override
	/**
	 * {@inheritDoc}
	 */
	public final CompletableFuture<JsonObject> toJson(int iid) {
		return makeBuilder(iid).thenApply(builder -> builder.build());
	}
	
	/**
	 * Creates the JSON serialized form of the accessory for use over the Homekit Accessory Protocol.
	 * 
	 * @param instanceId the static id of the accessory.
	 * @return a future that will complete with the JSON builder for the object.
	 */
	protected CompletableFuture<JsonObjectBuilder> makeBuilder(int instanceId) {
		return getValue().exceptionally(t -> {
			logger.error("Could not retrieve value "+this.getClass().getName(), t);
			return null;
		}).thenApply(value -> {
			JsonArrayBuilder perms = Json.createArrayBuilder();
			if (isWritable) {
				perms.add("pw");
			}
			if (isReadable) {
				perms.add("pr");
			}
			if (isEventable) {
				perms.add("ev");
			}
			JsonObjectBuilder builder = Json.createObjectBuilder()
				.add("iid", instanceId)
				.add("type", type)
				.add("perms", perms.build())
				.add("format", format)
				.add("events", false)
				.add("bonjour", false)
				.add("description", description);
			setJsonValue(builder, value);
			return builder;
		});
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public final void setValue(JsonValue jsonValue) {
		try {
			this.setValue(convert(jsonValue));
		} catch (Exception e) {
			logger.error("Error while setting JSON value", e);
		}
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void supplyValue(JsonObjectBuilder builder) {
		try {
			setJsonValue(builder, getValue().get());
		} catch (InterruptedException | ExecutionException e) {
			logger.error("Error retrieving value", e);
			setJsonValue(builder, getDefault());
		}
	}
	
	/**
	 * Converts from the JSON value to a Java object of the type T
	 * 
	 * @param jsonValue the JSON value to convert from.
	 * @return the converted Java object.
	 */
	protected abstract T convert(JsonValue jsonValue);
	
	/**
	 * Update the characteristic value using a new value supplied by the connected client.
	 * 
	 * @param value the new value to set.
	 * @throws Exception if the value cannot be set.
	 */
	protected abstract void setValue(T value) throws Exception;
	
	/**
	 * Retrieves the current value of the characteristic.
	 * 
	 * @return a future that will complete with the current value.
	 */
	protected abstract CompletableFuture<T> getValue();

	/**
	 * Supplies a default value for the characteristic to send to connected clients when the real value.
	 * cannot be retrieved.
	 * 
	 * @return a sensible default value.
	 */
	protected abstract T getDefault();

	/**
	 * Writes the value key to the serialized characteristic
	 * 
	 * @param builder The JSON builder to add the value to
	 * @param value The value to add
	 */
	protected void setJsonValue(JsonObjectBuilder builder, T value) {
		//I don't like this - there should really be a way to construct a disconnected JSONValue...
		if (value instanceof Boolean) {
			builder.add("value", (Boolean) value);
		} else if (value instanceof Double) {
			builder.add("value", (Double) value);
		} else if (value instanceof Integer) {
			builder.add("value", (Integer) value);
		} else if (value instanceof Long) {
			builder.add("value", (Long) value);
		} else if (value instanceof BigInteger) {
			builder.add("value", (BigInteger) value);
		} else if (value instanceof BigDecimal){
			builder.add("value", (BigDecimal) value);
		} else if (value == null) {
			// Do not add null value, HomeKit cannot handle that
		} else {
			builder.add("value", value.toString());
		}
	}
}

@@ -5275,60 +5275,33 @@
 %0A%09%09%09
-// Do not add null 
+builder.addNull(%22
 value
-, HomeKit cannot handle that
+%22);
 %0A%09%09%7D

Support null values - these work according to the HAP spec