/*******************************************************************************
 * Copyright (c) 2014 Oak Ridge National Laboratory.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 ******************************************************************************/
package org.csstudio.vtype.pv.pva;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.logging.Level;

import org.csstudio.vtype.pv.PV;
import org.epics.pvaccess.client.ChannelPut;
import org.epics.pvaccess.client.ChannelPutRequester;
import org.epics.pvdata.factory.PVDataFactory;
import org.epics.pvdata.misc.BitSet;
import org.epics.pvdata.pv.PVField;
import org.epics.pvdata.pv.PVStructure;
import org.epics.pvdata.pv.Status;
import org.epics.pvdata.pv.Structure;

/** A {@link ChannelPutRequester} for writing a value to a {@link PVA_PV},
 *  indicating completion via a {@link Future}
 *
 *  @author Kay Kasemir
 */
@SuppressWarnings("nls")
class PVPutHandler extends PVRequester implements ChannelPutRequester, Future<Object>
{
    final private PV pv;
    final private Object new_value;

    final private CountDownLatch updates = new CountDownLatch(1);
    private volatile Exception error = null;

    /** @param pv PV to write
     *  @param new_value Value to write
     */
    public PVPutHandler(PV pv, Object new_value)
    {
        this.pv = pv;
        this.new_value = new_value;
    }

    // ChannelPutRequester
    @Override
    public void channelPutConnect(final Status status, final ChannelPut channelPut, final Structure structure)
    {
        if (! status.isSuccess())
        {
            error = new Exception("Failed to connect 'put' for " + pv.getName() + ": " + status);
            updates.countDown();
            return;
        }

        try
        {
            final PVStructure write_structure = PVDataFactory.getPVDataCreate().createPVStructure(structure);
            final BitSet bit_set = new BitSet(write_structure.getNumberFields());

            // Locate the value field
            PVField field = write_structure.getSubField("value");

            // Enumerated? Write to value.index
            if (field instanceof PVStructure  &&  "enum_t".equals(field.getField().getID()))
                field = ((PVStructure)field).getSubField("index");

            // Indicate what's changed & change it
            bit_set.set(field.getFieldOffset());
            PVStructureHelper.setField(field, new_value);

            // Perform write
            channelPut.put(write_structure, bit_set);
        }
        catch (Exception ex)
        {
            logger.log(Level.WARNING, "Failed to write " + pv.getName() + " = " + new_value, ex);
            error = new Exception("Failed to write " + pv.getName() + " = " + new_value, ex);
            updates.countDown();
        }
    }

    // ChannelPutRequester
    @Override
    public void putDone(final Status status, final ChannelPut channelPut)
    {
        if (status.isSuccess())
            logger.log(Level.FINE, "Write {0} = {1} completed",
                    new Object[] { pv.getName(), new_value });
        else
        {
            error = new Exception("Write " + pv.getName() + " = " + new_value + " failed, " + status.toString());
            logger.log(Level.WARNING, "", error);
        }
        updates.countDown();
        channelPut.destroy();
    }

    // ChannelPutRequester
    @Override
    public void getDone(final Status status, final ChannelPut channelPut, final PVStructure pvStructure, final BitSet bitSet)
    {
        // Only used for createChannelPutGet
        logger.log(Level.WARNING, "Unexpected call to ChannelPutRequester.getDone(), channel {0}", channelPut.getChannel().getChannelName());
    }

    // Future
    @Override
    public boolean cancel(final boolean mayInterruptIfRunning)
    {
        return false;
    }

    // Future
    @Override
    public boolean isCancelled()
    {
        return false;
    }

    // Future
    @Override
    public boolean isDone()
    {
        return updates.getCount() == 0;
    }

    // Future
    @Override
    public Object get() throws InterruptedException, ExecutionException
    {
        updates.await();
        if (error != null)
            throw new ExecutionException(error);
        return null;
    }

    // Future
    @Override
    public Object get(final long timeout, final TimeUnit unit) throws InterruptedException,
            ExecutionException, TimeoutException
    {
        if (! updates.await(timeout, unit))
            throw new TimeoutException(pv.getName() + " write timeout");
        if (error != null)
            throw new ExecutionException(error);
        return null;
    }
}

@@ -2359,72 +2359,850 @@
 ield
-%0A            PVField field = write_structure.getSubField(%22value%22
+ at deepest level in structure%0A            PVField field = null;%0A            PVStructure search = write_structure;%0A            while (search != null)%0A            %7B%0A                final PVField%5B%5D fields = search.getPVFields();%0A                if (fields.length != 1)%0A                    throw new Exception(%22Can only write to simple struct.element.value path, got %22 + structure);%0A                if (fields%5B0%5D.getFieldName().equals(%22value%22))%0A                %7B%0A                    field = fields%5B0%5D;%0A                    break;%0A                %7D%0A                else if (fields%5B0%5D instanceof PVStructure)%0A                    search = (PVStructure) fields%5B0%5D;%0A                else%0A                    search = null;%0A            %7D%0A            if (field == null)%0A                throw new Exception(%22Cannot locate 'value' to write in %22 + structure
 );%0A%0A

Support writing to pva://channel/struct/sub