/**
 *  Adapter control port implementation for dynamic interactions with Test Adapter
 *  
 *  @author     ETSI / STF424
 *  @version    $URL$
 *              $Id$
 *
 */
package org.etsi.its.adapter.ports;

import org.etsi.adapter.GnssSupportFactory;
import org.etsi.adapter.IGnssSupport;
import org.etsi.adapter.TERFactory;
import org.etsi.common.ByteHelper;
import org.etsi.its.adapter.Management;
import org.etsi.ttcn.tci.BooleanValue;
import org.etsi.ttcn.tri.TriStatus;

/** This class implements behaviour for Adapter controller port
 *
 */

public class AdapterControlPort extends AdapterPort implements IPort, IObservable { 

    /* AdapterControl Primitives */
    private static final byte AcGnPrimitive = 0;
    private static final byte AcGn6Primitive = 1;
    private static final byte AcGnssPrimitive = 2;
    private static final byte AcSecPrimitive = 3;
    
    /* AdapterControl Response */
    private static final byte AcGnResponse = 0;
    //private static final byte AcGn6Response = 1;
    public static final byte AcGnssResponse = 2;
    public static final byte AcGnssDistanceCovered = 3;
    public static final byte AcGnssTimeReached = 4;
    public static final byte AcSecResponse = 5;

    /* GN Commands */
    private static final byte AcStartBeaconing = 0;
    private static final byte AcStopBeaconing = 1;
    private static final byte AcStartPassBeaconing = 2;
    private static final byte AcStopPassBeaconing = 3;
    private static final byte AcStartBeaconingMultipleNeighbour = 4;
    private static final byte AcStopBeaconingMultipleNeighbour = 5;
    private static final byte AcGetLongPosVector = 6;
    
    /* GN Responses */
    protected static final byte AcGnResponseFailure = 0;
    protected static final byte AcLongPosVector = 6;

    /* SCENARIO commands */
    private static final byte AcLoadScenario = 0x70;
    private static final byte AcStartScenario = 0x71;
    private static final byte AcStopScenario = 0x72;
    private static final byte AcAwaitDistanceToCover = 0x73;
    private static final byte AcChangeSpead = 0x74;
    private static final byte AcChangeHeading = 0x75;
    private static final byte AcAwaitTimeInRunningScenario = 0x76;

    /* Set the certificate to be used by the Test Adapter */
    private static final byte AcEnableSecurity = 0x7a;
    private static final byte AcDisableSecurity = 0x7b;
    
    
    public static final byte AcTrue = 0x01;
    public static final byte AcFalse = 0x00;
    
    private static final String GNSS_SCENARIO_SUPPORT = "GnssScenarioSupport";
    private IGnssSupport GNSS;
    private boolean isScenarioStarted = false;
    private boolean gnssScenarioSupport;
    /**
     * Constructor
     * @param   portName        Name of the port
     * @param   componentName   Name of the component owning this port instance
     */
    public AdapterControlPort(final String portName, final String componentName) {
        super(portName, componentName);
        try {
            gnssScenarioSupport = ((BooleanValue) TERFactory.getInstance().getTaParameter(GNSS_SCENARIO_SUPPORT)).getBoolean();
        }
        catch (Throwable th) {
            gnssScenarioSupport = false;
        }
        if (gnssScenarioSupport) {
            GNSS = GnssSupportFactory.getInstance();
        }
    }

    @Override
    public boolean send(final byte[] message) {

        boolean result = true;
        try { 
            // Decode non protocol part
            switch(message[0]) {
                case AcGnPrimitive: {
                    byte[] data = ByteHelper.extract(message, 2, message.length - 2);
                    switch (message[1]) {
                        case AcGetLongPosVector:
                            ProcessAcGetLongPosVector(data);
                            break;
                        case AcStartBeaconing:
                            Management.getInstance(getComponentName()).startBeaconing(data);
                            break;
                        case AcStopBeaconing:
                            Management.getInstance(getComponentName()).stopBeaconing();
                            break;
                        case AcStartPassBeaconing:
                            Management.getInstance(getComponentName()).startEnqueueingBeacons(data);
                            break;
                        case AcStopPassBeaconing:
                            Management.getInstance(getComponentName()).stopEnqueueingBeacons();
                            break;
                        case AcStartBeaconingMultipleNeighbour:
                             // TODO
                            break;
                        case AcStopBeaconingMultipleNeighbour:
                             // TODO
                            break;
                    } 
                }
                break;
                case AcGn6Primitive:
                    /* FIXME
                    try {
                        byte[] buf = new byte[4096];
                        DatagramSocket remoteAdapterSocket = new DatagramSocket();
                        InetAddress remoteAdapterAddress = InetAddress.getByName(((CharstringValue)TERFactory.getInstance().getTaParameter("Gn6RemoteAdapterIp")).getString());
                        int remoteAdapterPort = Integer.decode(((CharstringValue)TERFactory.getInstance().getTaParameter("Gn6RemoteAdapterPort")).getString());
                        
                        DatagramPacket command = new DatagramPacket(new byte[] {CMD_IFACES}, 1, remoteAdapterAddress, remoteAdapterPort);
                        DatagramPacket response = new DatagramPacket(buf, buf.length);
                        
                        remoteAdapterSocket.send(command);
                        remoteAdapterSocket.receive(response); 
                        
                        byte[] data = ByteHelper.extract(response.getData(), response.getOffset(), response.getLength());
                        if(data[0] == RPL_IFACES) {
                            // enqueue response... 
                            byte[] buffer = impl.Encode(TestAdapterMessageTypeEnum.AcGn6Response, ByteHelper.extract(data, 1, data.length - 1), new Date().getTime());                        
                            setChanged();
                            notifyObservers(new PortEvent(buffer, getPortName(), getComponentName()));
                        }
                    } catch (SocketException e) {
                        e.printStackTrace();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }         
                    */
                    break;
                case AcGnssPrimitive:
                    if (gnssScenarioSupport) {
                        byte[] data = ByteHelper.extract(message, 2, message.length - 2);
                        switch (message[1]) {
                            case AcLoadScenario:
                                LoadScenario(ByteHelper.byteArrayToShort(data));
                                break;
                            case AcStartScenario:
                                StartScenario();
                                break;
                            case AcStopScenario:
                                StopScenario();
                                break;
                            case AcAwaitDistanceToCover:
                                Float distance = ByteHelper.byteArrayToFloat(data);
                                AwaitDistanceToCover(distance);
                                break;
                            case AcChangeSpead:
//                                ChangeSpeed(speed);
                                break;
                            case AcChangeHeading:
//                                ChangeHeading(heading);
                                break;
                            case AcAwaitTimeInRunningScenario:
                                int time = ByteHelper.byteArrayToInt(data);
                                AwaitTimeInRunningScenario(time);
                                break;
                        }
                    }
                    else {
                        TERFactory.getInstance().getTriStatus(TriStatus.TRI_OK, "AcGnssPrimitive cannot be handled as the "
                                + GNSS_SCENARIO_SUPPORT + " is set to false!");
                        result = false;
                    }
                    break;
                case AcSecPrimitive:
                    switch (message[1]) {
                        case AcEnableSecurity: 
                            byte[] data = ByteHelper.extract(message, 2, message.length - 2);
                            ProcessAcEnableSecurity(data);
                            break;
                        case AcDisableSecurity: 
                            ProcessAcDisableSecurity();
                            break;
                    } // End of 'switch' statement
                    break;
                default:
                    ByteHelper.dump("Unsupported AC primitive", message);
                    break;
            } // End of 'switch' statement

            return result;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    } // End of method send

    /**
     * Processes AcGetLongPosVector command
     * @param  data
     */
    private void ProcessAcGetLongPosVector(final byte[] gnAddress)  {

        new Thread(new Runnable() {
            @Override
            public void run() { 
        
                byte[] response = null;
                byte[] responseHdr = {(byte)AcGnResponse, (byte)0x00};
                byte[] longPosVector = Management.getInstance(getComponentName()).getLongPositionVector(gnAddress);
                if((longPosVector == null) || (longPosVector.length == 0)) {
                    responseHdr[1] = (byte)AcGnResponseFailure;
                    response = ByteHelper.concat(responseHdr, new byte[]{AcTrue});   
                } 
                else {
                    responseHdr[1] = (byte)AcLongPosVector;
                    response = ByteHelper.concat(responseHdr, longPosVector);
                }
                
                setChanged();
                notifyObservers(new PortEvent(response, getPortName(), getComponentName()));
            }
        }).start();
    }

    private void LoadScenario(final int scenario) {
        new Thread(new Runnable() {
            @Override
            public void run() { 
                boolean result = GNSS.loadScenario(scenario);
                byte[] response = {(byte)AcGnssResponse, (byte)(result?AcTrue:AcFalse)};
                
                setChanged();
                notifyObservers(new PortEvent(response, getPortName(), getComponentName()));
            }
        }).start();
    }

    private void StartScenario() {
        new Thread(new Runnable() {
            @Override
            public void run() { 
                isScenarioStarted = GNSS.startScenario();
                byte[] response = {(byte)AcGnssResponse, (byte)(isScenarioStarted?AcTrue:AcFalse)};
                
                setChanged();
                notifyObservers(new PortEvent(response, getPortName(), getComponentName()));
            }
        }).start();
    }

    private void StopScenario() {
        new Thread(new Runnable() {
            @Override
            public void run() { 
                boolean result = GNSS.stopScenario();
                isScenarioStarted = !result;
                byte[] response = {(byte)AcGnssResponse, (byte)(isScenarioStarted?AcFalse:AcTrue)};
                
                setChanged();
                notifyObservers(new PortEvent(response, getPortName(), getComponentName()));
            }
        }).start();
    }

    private void AwaitDistanceToCover(final double distance) {
        new Thread(new Runnable() {
            @Override
            public void run() { 
                boolean result = GNSS.awaitDistanceToCover(AdapterControlPort.this, distance);
                byte[] response = {(byte)AcGnssResponse, (byte)(result?AcFalse:AcTrue)};
                
                setChanged();
                notifyObservers(new PortEvent(response, getPortName(), getComponentName()));
            }
        }).start();
    }

   private void AwaitTimeInRunningScenario(final int time) {
        new Thread(new Runnable() {
            @Override
            public void run() { 
                boolean result = GNSS.awaitTimeInRunningScenario(AdapterControlPort.this, time);
                byte[] response = {(byte)AcGnssResponse, (byte)(result?AcFalse:AcTrue)};
                setChanged();
                notifyObservers(new PortEvent(response, getPortName(), getComponentName()));
            }
        }).start();
    }
    
    private void ProcessAcEnableSecurity(final byte[] data) {
        new Thread(new Runnable() {
            @Override
            public void run() { 
                Management.getInstance(getComponentName()).setSecuredMode(data);
                byte[] response = {(byte)AcSecResponse, (byte)AcTrue };
                
                setChanged();
                notifyObservers(new PortEvent(response, getPortName(), getComponentName()));
            }
        }).start();
    }

    private void ProcessAcDisableSecurity() {
        new Thread(new Runnable() {
            @Override
            public void run() { 
                Management.getInstance(getComponentName()).unsetSecuredMode();
                byte[] response = {(byte)AcSecResponse, (byte)AcTrue };
                
                setChanged();
                notifyObservers(new PortEvent(response, getPortName(), getComponentName()));
            }
        }).start();
    }


    @Override
    public void dispose() {
        if (gnssScenarioSupport) {
            if (GNSS!=null) {
                if (isScenarioStarted) {
                    GNSS.stopScenario();
                }
                GNSS.dispose();
            }
            GNSS = null;
        }
    }

} // End of class AdapterControlPort
