/**
 * Copyright (C) 2010-14 pvmanager developers. See COPYRIGHT.TXT
 * All rights reserved. Use is subject to license terms. See LICENSE.TXT
 */
package org.epics.pvmanager.loc;

import org.epics.pvmanager.ChannelWriteCallback;
import org.epics.pvmanager.ChannelHandlerReadSubscription;
import org.epics.pvmanager.MultiplexedChannelHandler;
import org.epics.pvmanager.ChannelHandlerWriteSubscription;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import static org.epics.vtype.ValueFactory.*;
import org.epics.util.array.ArrayDouble;
import org.epics.util.array.ListDouble;
import org.epics.vtype.VDouble;
import org.epics.vtype.VDoubleArray;
import org.epics.vtype.VString;
import org.epics.vtype.VStringArray;
import org.epics.vtype.VTable;
import org.epics.vtype.VType;

/**
 * Implementation for channels of a {@link LocalDataSource}.
 *
 * @author carcassi
 */
class LocalChannelHandler extends MultiplexedChannelHandler<Object, Object> {
    
    private static Logger log = Logger.getLogger(LocalChannelHandler.class.getName());

    LocalChannelHandler(String channelName) {
        super(channelName);
    }

    @Override
    public void connect() {
        processConnection(new Object());
    }

    @Override
    public void disconnect() {
        initialValue = null;
        type = null;
        processConnection(null);
    }

    @Override
    protected synchronized void addReader(ChannelHandlerReadSubscription subscription) {
        // Override for test visibility purposes
        super.addReader(subscription);
    }

    @Override
    protected synchronized void addWriter(ChannelHandlerWriteSubscription subscription) {
        // Override for test visibility purposes
        super.addWriter(subscription);
    }

    @Override
    protected synchronized void removeReader(ChannelHandlerReadSubscription subscription) {
        // Override for test visibility purposes
        super.removeReader(subscription);
    }

    @Override
    protected synchronized void removeWrite(ChannelHandlerWriteSubscription subscription) {
        // Override for test visibility purposes
        super.removeWrite(subscription);
    }
    
    private Object checkValue(Object value) {
        if (type != null && !type.isInstance(value)) {
            throw new IllegalArgumentException("Value " + value + " is not of type " + type.getSimpleName());
        }
        return value;
    }
    
    private Object wrapValue(Object value) {
        if (value instanceof VType) {
            return value;
        } else if (value instanceof Number) {
            // Special support for numbers
            return newVDouble(((Number) value).doubleValue(), alarmNone(), timeNow(),
                    displayNone());
        } else if (value instanceof String) {
            // Special support for strings
            return newVString(((String) value),
                    alarmNone(), timeNow());
        } else if (value instanceof double[]) {
            return newVDoubleArray(new ArrayDouble((double[]) value), alarmNone(), timeNow(), displayNone());
        } else if (value instanceof ListDouble) {
            return newVDoubleArray((ListDouble) value, alarmNone(), timeNow(), displayNone());
        } else if (value instanceof List) {
            boolean matches = true;
            List list = (List) value;
            for (Object object : list) {
                if (!(object instanceof String)) {
                    matches = false;
                }
            }
            if (matches) {
                @SuppressWarnings("unchecked")
                List<String> newList = (List<String>) list;
                return newVStringArray(Collections.unmodifiableList(newList), alarmNone(), timeNow());
            } else {
                throw new UnsupportedOperationException("Type " + value.getClass().getName() + " contains non Strings");
            }
        } else {
            // TODO: need to implement all the other arrays
            throw new UnsupportedOperationException("Type " + value.getClass().getName() + "  is not yet supported");
        }
    }

    @Override
    public void write(Object newValue, ChannelWriteCallback callback) {
        try {
            // If the string can be parse to a number, to it
            if (newValue instanceof String) {
                String value = (String) newValue;
                try {
                    newValue = Double.valueOf(value);
                } catch (NumberFormatException ex) {
                }
            }
            newValue = checkValue(wrapValue(newValue));
            processMessage(newValue);
            callback.channelWritten(null);
        } catch (Exception ex) {
            callback.channelWritten(ex);
        }
    }

    @Override
    protected boolean isWriteConnected(Object payload) {
        return isConnected(payload);
    }
    
    private Object initialValue;
    private Class<?> type;
    
    synchronized void setInitialValue(Object value) {
        if (initialValue != null && !initialValue.equals(value)) {
            String message = "Different initialization for local channel " + getChannelName() + ": " + value + " but was " + initialValue;
            log.log(Level.WARNING, message);
            throw new RuntimeException(message);
        }
        initialValue = value;
        if (getLastMessagePayload() == null) {
            processMessage(checkValue(wrapValue(value)));
        }
    }
    
    synchronized void setType(String typeName) {
        if (typeName == null) {
            return;
        }
        Class<?> newType = null;
        if ("VDouble".equals(typeName)) {
            newType = VDouble.class;
        }
        if ("VString".equals(typeName)) {
            newType = VString.class;
        }
        if ("VDoubleArray".equals(typeName)) {
            newType = VDoubleArray.class;
        }
        if ("VStringArray".equals(typeName)) {
            newType = VStringArray.class;
        }
        if ("VTable".equals(typeName)) {
            newType = VTable.class;
        }
        if (newType == null) {
            throw new IllegalArgumentException("Type " + typeName + " for channel " + getChannelName() + " is not supported by local datasource.");
        }
        if (type != null && !type.equals(newType)) {
            throw new IllegalArgumentException("Type mismatch for channel " + getChannelName() + ": " + typeName + " but was " + type.getSimpleName());
        }
        type = newType;
    }

    @Override
    public synchronized Map<String, Object> getProperties() {
        Map<String, Object> properties = new HashMap<>();
        properties.put("Name", getChannelName());
        properties.put("Type", type);
        properties.put("Initial Value", initialValue);
        return properties;
    }
    
}

@@ -886,16 +886,53 @@
 e.VType;
+%0Aimport org.epics.vtype.ValueFactory;
 %0A%0A/**%0A *
@@ -2554,1701 +2554,8 @@
    %7D
-%0A    %0A    private Object wrapValue(Object value) %7B%0A        if (value instanceof VType) %7B%0A            return value;%0A        %7D else if (value instanceof Number) %7B%0A            // Special support for numbers%0A            return newVDouble(((Number) value).doubleValue(), alarmNone(), timeNow(),%0A                    displayNone());%0A        %7D else if (value instanceof String) %7B%0A            // Special support for strings%0A            return newVString(((String) value),%0A                    alarmNone(), timeNow());%0A        %7D else if (value instanceof double%5B%5D) %7B%0A            return newVDoubleArray(new ArrayDouble((double%5B%5D) value), alarmNone(), timeNow(), displayNone());%0A        %7D else if (value instanceof ListDouble) %7B%0A            return newVDoubleArray((ListDouble) value, alarmNone(), timeNow(), displayNone());%0A        %7D else if (value instanceof List) %7B%0A            boolean matches = true;%0A            List list = (List) value;%0A            for (Object object : list) %7B%0A                if (!(object instanceof String)) %7B%0A                    matches = false;%0A                %7D%0A            %7D%0A            if (matches) %7B%0A                @SuppressWarnings(%22unchecked%22)%0A                List%3CString%3E newList = (List%3CString%3E) list;%0A                return newVStringArray(Collections.unmodifiableList(newList), alarmNone(), timeNow());%0A            %7D else %7B%0A                throw new UnsupportedOperationException(%22Type %22 + value.getClass().getName() + %22 contains non Strings%22);%0A            %7D%0A        %7D else %7B%0A            // TODO: need to implement all the other arrays%0A            throw new UnsupportedOperationException(%22Type %22 + value.getClass().getName() + %22  is not yet supported%22);%0A        %7D%0A    %7D
 %0A%0A  
@@ -3004,25 +3004,43 @@
 ckValue(
-wrap
 Value
+Factory.toVTypeChecked
 (newValu
@@ -3883,17 +3883,35 @@
 lue(
-wrap
 Value
+Factory.toVTypeChecked
 (val

update to current SNAPSHOT