/**
 * @author      STF 424_ITS_Test_Platform
 * @version     $URL: $
 *              $Id: $
 */
package org.etsi.its.uppertester.nec;

import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.etsi.common.ByteHelper;
import org.etsi.its.codec.TestAdapterMessageTypeEnum;
import org.etsi.its.codec.adapter.UpperTesterCodecImpl;
import org.etsi.its.network.IUdpImpl;
import org.etsi.its.network.UdpFactory;
import org.etsi.its.network.nec.V2xModulesEnum;
import org.etsi.its.uppertester.IDenmUpperTester;

/**
 * Nec implementation of DENM upper tester
 */
public class DenmUpperTester implements IDenmUpperTester {

    /**
     * Module version
     */
    public static final String Version = "1.0.0.0";

    /**
     * Logger instance
     */
    private final static Logger _logger = Logger.getLogger("org.etsi.its");

    /**
     * Reference to the TA codec
     */
    private UpperTesterCodecImpl _codec;

    /**
     * Regex pattern for module settings decoding operation
     */
    private Pattern _pattern;

    /**
     * List of UDP modules
     */
    private Map<V2xModulesEnum, IUdpImpl> _udps;

    /**
     * Constructor
     */
    public DenmUpperTester() {
        _logger.entering("DenmUpperTester", "Constructor", String.format("version: %s", Version));
        _codec = new UpperTesterCodecImpl();
        _pattern = Pattern.compile("(\\w+)\\:(\\S+)\\:(\\d+)\\:(\\d+)"); // For Java regex test, see http://www.cis.upenn.edu/~matuszek/General/RegexTester/regex-tester.html
        _udps = new HashMap<V2xModulesEnum, IUdpImpl>();
    } // End of Constructor

    @Override
    public boolean generateDenmEvent(DenmEvent denmEvent) {
        _logger.entering("DenmUpperTester", "generateDenmEvent", String.format("%d - %d", denmEvent.cause, denmEvent.subCause));

        try {
            byte[] bufferSubcause = ByteHelper.intToByteArray(denmEvent.subCause, 4);
            bufferSubcause = _codec.Encode(TestAdapterMessageTypeEnum.subCause, bufferSubcause, 0);
            byte[] bufferCause = ByteHelper.intToByteArray(denmEvent.cause, 4);
            bufferCause = _codec.Encode(TestAdapterMessageTypeEnum.cause, bufferCause, 0);
            byte[] buffer = ByteHelper.concat(bufferCause, bufferSubcause);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _udps.get(V2xModulesEnum.DenmTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    } // End of method generateDenmEvent

    @Override
    public boolean generateDenmEvent(DenmEvent denmEvent, short trafficFlowEffect) {
        _logger.entering("DenmUpperTester", "generateDenmEvent", String.format("%d - %d - trafficFlowEffect:%d", denmEvent.cause, denmEvent.subCause, trafficFlowEffect));

        try {
            // Set trafficFlowEffect
            byte[] bufferType = ByteHelper.intToByteArray(trafficFlowEffect, Short.SIZE / 8);
            bufferType = _codec.Encode(TestAdapterMessageTypeEnum.eventTrafficFlowEffect, bufferType, 0);
            bufferType = _codec.Encode(TestAdapterMessageTypeEnum.EventOption, bufferType, 0);
            // Set Cause/SubCause
            byte[] bufferSubcause = ByteHelper.intToByteArray(denmEvent.subCause, 4);
            bufferSubcause = _codec.Encode(TestAdapterMessageTypeEnum.subCause, bufferSubcause, 0);
            byte[] bufferCause = ByteHelper.intToByteArray(denmEvent.cause, 4);
            bufferCause = _codec.Encode(TestAdapterMessageTypeEnum.cause, bufferCause, 0);
            byte[] buffer = ByteHelper.concat(bufferCause, bufferSubcause, bufferType);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _udps.get(V2xModulesEnum.DenmTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    } // End of method generateDenmEvent

    @Override
    public boolean generateDenmEvent(DenmEvent denmEvent, DenmEvent linkedDenmEvent) {
        _logger.entering("DenmUpperTester", "generateDenmEvent", String.format("%d - %d", denmEvent.cause, denmEvent.subCause));

        try {
            // Set linkedCause
            byte[] bufferSubcause = ByteHelper.intToByteArray(linkedDenmEvent.subCause, 4);
            bufferSubcause = _codec.Encode(TestAdapterMessageTypeEnum.subCause, bufferSubcause, 0);
            byte[] bufferCause = ByteHelper.intToByteArray(linkedDenmEvent.cause, 4);
            bufferCause = _codec.Encode(TestAdapterMessageTypeEnum.cause, bufferCause, 0);
            byte[] bufferLinkedDenmEvent = _codec.Encode(TestAdapterMessageTypeEnum.eventLinkedCause, ByteHelper.concat(bufferCause, bufferSubcause), 0);
            bufferLinkedDenmEvent = _codec.Encode(TestAdapterMessageTypeEnum.EventOption, bufferLinkedDenmEvent, 0);
            // Set Cause
            bufferSubcause = ByteHelper.intToByteArray(denmEvent.subCause, 4);
            bufferSubcause = _codec.Encode(TestAdapterMessageTypeEnum.subCause, bufferSubcause, 0);
            bufferCause = ByteHelper.intToByteArray(denmEvent.cause, 4);
            bufferCause = _codec.Encode(TestAdapterMessageTypeEnum.cause, bufferCause, 0);
            
            byte[] buffer = ByteHelper.concat(bufferCause, bufferSubcause);
            buffer = ByteHelper.concat(buffer, bufferLinkedDenmEvent);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _udps.get(V2xModulesEnum.DenmTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    } // End of method generateDenmEvent

    @Override
    public boolean generateDenmEvent(DenmEvent denmEvent, long expirationTime) {
        _logger.entering("DenmUpperTester", "generateDenmEvent", String.format("%d - %d - expirationTime:%d", denmEvent.cause, denmEvent.subCause, expirationTime));

        try {
            // Set expirationTime
            byte[] bufferType = ByteHelper.longToByteArray(expirationTime, Long.SIZE / 8);
            bufferType = _codec.Encode(TestAdapterMessageTypeEnum.expirationTime, bufferType, 0);
            // Set EventOption
            bufferType = _codec.Encode(TestAdapterMessageTypeEnum.EventOption, bufferType, 0);
            // Set Cause/SubCause
            byte[] bufferSubcause = ByteHelper.intToByteArray(denmEvent.subCause, 4);
            bufferSubcause = _codec.Encode(TestAdapterMessageTypeEnum.subCause, bufferSubcause, 0);
            byte[] bufferCause = ByteHelper.intToByteArray(denmEvent.cause, 4);
            bufferCause = _codec.Encode(TestAdapterMessageTypeEnum.cause, bufferCause, 0);
            byte[] buffer = ByteHelper.concat(bufferCause, bufferSubcause, bufferType);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _udps.get(V2xModulesEnum.DenmTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean generateDenmEvent(DenmEvent denmEvent, TestAdapterMessageTypeEnum type, int event) {
        _logger.entering("DenmUpperTester", "generateDenmEvent", String.format("%d - %d - %s:%d", denmEvent.cause, denmEvent.subCause, type.name(), event));

        try {
            // Set type value
            byte[] bufferType = ByteHelper.intToByteArray(event, Integer.SIZE / 8);
            bufferType = _codec.Encode(type, bufferType, 0);
            // Set EventOption
            bufferType = _codec.Encode(TestAdapterMessageTypeEnum.EventOption, bufferType, 0);
            // Set Cause/SubCause
            byte[] bufferSubcause = ByteHelper.intToByteArray(denmEvent.subCause, 4);
            bufferSubcause = _codec.Encode(TestAdapterMessageTypeEnum.subCause, bufferSubcause, 0);
            byte[] bufferCause = ByteHelper.intToByteArray(denmEvent.cause, 4);
            bufferCause = _codec.Encode(TestAdapterMessageTypeEnum.cause, bufferCause, 0);
            byte[] buffer = ByteHelper.concat(bufferCause, bufferSubcause, bufferType);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _udps.get(V2xModulesEnum.DenmTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean generateDenmEvent(DenmEvent denmEvent, long expirationTime, short frequency) {
        _logger.entering("DenmUpperTester", "generateDenmEvent", String.format("%d - %d - %d - %d", denmEvent.cause, denmEvent.subCause, expirationTime, frequency));

        try {
            // Set Frequency
            byte[] bufferFrequency = ByteHelper.intToByteArray(frequency, 2);
            bufferFrequency = _codec.Encode(TestAdapterMessageTypeEnum.frequency, bufferFrequency, 0);
            // Set ExpirationTime
            byte[] bufferExpirationTime = ByteHelper.longToByteArray(expirationTime, Long.SIZE / 8);
            bufferExpirationTime = _codec.Encode(TestAdapterMessageTypeEnum.expirationTime, bufferExpirationTime, 0);
            // Set EventOption
            byte [] bufferType = _codec.Encode(TestAdapterMessageTypeEnum.EventTimeFrequency, ByteHelper.concat(bufferExpirationTime, bufferFrequency), 0);
            bufferType = _codec.Encode(TestAdapterMessageTypeEnum.EventOption, bufferType, 0);
            // Set cause/subCause
            byte[] bufferSubcause = ByteHelper.intToByteArray(denmEvent.subCause, 4);
            bufferSubcause = _codec.Encode(TestAdapterMessageTypeEnum.subCause, bufferSubcause, 0);
            byte[] bufferCause = ByteHelper.intToByteArray(denmEvent.cause, 4);
            bufferCause = _codec.Encode(TestAdapterMessageTypeEnum.cause, bufferCause, 0);
            byte[] buffer = ByteHelper.concat(bufferCause, bufferSubcause, bufferType);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _udps.get(V2xModulesEnum.DenmTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean generateDenmEvent(DenmEvent denmEvent, boolean mobile, short causeType, short relevance, boolean timeCritical) {
        _logger.entering("DenmUpperTester", "generateDenmEvent");

        try {
            byte[] bufferEventChar = _codec.Encode(TestAdapterMessageTypeEnum.eventmobility, new byte[] { (byte)((mobile) ? 0x01 : 0x00) }, 0);
            bufferEventChar = ByteHelper.concat(bufferEventChar, _codec.Encode(TestAdapterMessageTypeEnum.causeType, ByteHelper.intToByteArray(relevance, Short.SIZE / 8), 0));
            bufferEventChar = ByteHelper.concat(bufferEventChar, _codec.Encode(TestAdapterMessageTypeEnum.relevance, ByteHelper.intToByteArray(causeType, Short.SIZE / 8), 0));
            bufferEventChar = ByteHelper.concat(bufferEventChar, _codec.Encode(TestAdapterMessageTypeEnum.timeCriticality, new byte[] { (byte)((timeCritical) ? 0x01 : 0x00) }, 0));
            // Set EventChar
            bufferEventChar = _codec.Encode(TestAdapterMessageTypeEnum.eventCharact, bufferEventChar, 0);
            bufferEventChar = _codec.Encode(TestAdapterMessageTypeEnum.EventOption, bufferEventChar, 0);
            // Set cause/subCause
            byte[] bufferSubcause = ByteHelper.intToByteArray(denmEvent.subCause, 4);
            bufferSubcause = _codec.Encode(TestAdapterMessageTypeEnum.subCause, bufferSubcause, 0);
            byte[] bufferCause = ByteHelper.intToByteArray(denmEvent.cause, 4);
            bufferCause = _codec.Encode(TestAdapterMessageTypeEnum.cause, bufferCause, 0);
            byte[] buffer = ByteHelper.concat(bufferCause, bufferSubcause, bufferEventChar);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _udps.get(V2xModulesEnum.DenmTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean generateDenmEventCancellation(DenmEvent denmEvent, ActionId actionId) {
        _logger.entering("DenmUpperTester", "generateDenmEventCancellation", String.format("%d - %d - %d - %d", denmEvent.cause, denmEvent.subCause, actionId.stationId, actionId.sequenceNo));

        try {
            // Set ActionId
            byte[] sequenceNo = ByteHelper.intToByteArray(actionId.sequenceNo, 2);
            sequenceNo = _codec.Encode(TestAdapterMessageTypeEnum.sequenceNo, sequenceNo, 0);
            byte[] stationId = ByteHelper.intToByteArray(actionId.stationId, 4);
            stationId = _codec.Encode(TestAdapterMessageTypeEnum.stationID, stationId, 0);
            byte[] bufferActionID = _codec.Encode(TestAdapterMessageTypeEnum.ActionID, ByteHelper.concat(stationId, sequenceNo), 0);
            // Set eventCancellation
            byte[] bufferEventCancellation = _codec.Encode(TestAdapterMessageTypeEnum.eventCancellation, bufferActionID, 0);
            bufferEventCancellation = _codec.Encode(TestAdapterMessageTypeEnum.EventOption, bufferEventCancellation, 0);
            // Set Cause/SubCause
            byte[] bufferSubcause = ByteHelper.intToByteArray(denmEvent.subCause, 4);
            bufferSubcause = _codec.Encode(TestAdapterMessageTypeEnum.subCause, bufferSubcause, 0);
            byte[] bufferCause = ByteHelper.intToByteArray(denmEvent.cause, 4);
            bufferCause = _codec.Encode(TestAdapterMessageTypeEnum.cause, bufferCause, 0);
            byte[] buffer = ByteHelper.concat(bufferCause, bufferSubcause, bufferEventCancellation);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _udps.get(V2xModulesEnum.DenmTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    } // End of method generateDenmEventCancellation

    @Override
    public boolean generateDenmEventNegation(DenmEvent denmEvent, ActionId actionId) {
        _logger.entering("DenmUpperTester", "generateDenmEventNegation", String.format("%d - %d - %d - %d", denmEvent.cause, denmEvent.subCause, actionId.stationId, actionId.sequenceNo));

        try {
            // Set ActionId
            byte[] sequenceNo = ByteHelper.intToByteArray(actionId.sequenceNo, 2);
            sequenceNo = _codec.Encode(TestAdapterMessageTypeEnum.sequenceNo, sequenceNo, 0);
            byte[] stationId = ByteHelper.intToByteArray(actionId.stationId, 4);
            stationId = _codec.Encode(TestAdapterMessageTypeEnum.stationID, stationId, 0);
            byte[] bufferActionID = _codec.Encode(TestAdapterMessageTypeEnum.ActionID, ByteHelper.concat(stationId, sequenceNo), 0);
            // Set eventCancellation
            byte[] bufferEventNegation = _codec.Encode(TestAdapterMessageTypeEnum.eventNegation, bufferActionID, 0);
            bufferEventNegation = _codec.Encode(TestAdapterMessageTypeEnum.EventOption, bufferEventNegation, 0);
            // Set Cause/SubCause
            byte[] bufferSubcause = ByteHelper.intToByteArray(denmEvent.subCause, 4);
            bufferSubcause = _codec.Encode(TestAdapterMessageTypeEnum.subCause, bufferSubcause, 0);
            byte[] bufferCause = ByteHelper.intToByteArray(denmEvent.cause, 4);
            bufferCause = _codec.Encode(TestAdapterMessageTypeEnum.cause, bufferCause, 0);
            byte[] buffer = ByteHelper.concat(bufferCause, bufferSubcause, bufferEventNegation);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _udps.get(V2xModulesEnum.DenmTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    } // End of method generateDenmEventNegation

    @Override
    public boolean updateDenmEvent(DenmEvent denmEvent, ActionId actionId, long expiryTime) {
        _logger.entering("DenmUpperTester", "updateDenmEvent", String.format("%d - %d - %d - %d - %d", denmEvent.cause, denmEvent.subCause, actionId.stationId, actionId.sequenceNo, expiryTime));

        try {
            // Set expiry time
            byte[] bufferExpiryTime = ByteHelper.longToByteArray(expiryTime, Long.SIZE / Byte.SIZE);
            bufferExpiryTime = _codec.Encode(TestAdapterMessageTypeEnum.expiryTime, bufferExpiryTime, 0);
            // Set ActionId
            byte[] sequenceNo = ByteHelper.intToByteArray(actionId.sequenceNo, 2);
            sequenceNo = _codec.Encode(TestAdapterMessageTypeEnum.sequenceNo, sequenceNo, 0);
            byte[] stationId = ByteHelper.intToByteArray(actionId.stationId, 4);
            stationId = _codec.Encode(TestAdapterMessageTypeEnum.stationID, stationId, 0);
            byte[] bufferActionID = _codec.Encode(TestAdapterMessageTypeEnum.ActionID, ByteHelper.concat(stationId, sequenceNo), 0);
            // Set updateEventExpiry
            byte[] updateEventExpiry = _codec.Encode(TestAdapterMessageTypeEnum.updateEventExpiry, ByteHelper.concat(bufferActionID, bufferExpiryTime), 0);
            updateEventExpiry = _codec.Encode(TestAdapterMessageTypeEnum.EventOption, updateEventExpiry, 0);
            // Set Cause/SubCause
            byte[] bufferSubcause = ByteHelper.intToByteArray(denmEvent.subCause, 4);
            bufferSubcause = _codec.Encode(TestAdapterMessageTypeEnum.subCause, bufferSubcause, 0);
            byte[] bufferCause = ByteHelper.intToByteArray(denmEvent.cause, 4);
            bufferCause = _codec.Encode(TestAdapterMessageTypeEnum.cause, bufferCause, 0);
            byte[] buffer = ByteHelper.concat(bufferCause, bufferSubcause, updateEventExpiry);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _udps.get(V2xModulesEnum.DenmTrigger).send(buffer);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean checkDenmEvent(byte[] denmPdu) {
        _logger.entering("DenmUpperTester", "checkDenmEvent");

        // FIXME To be done
//        try {
//            // Send PDU
//            _logger.info("Sending denmPdu");
//            _udps.get(V2xModulesEnum.Ldm).send(denmPdu);
//            _logger.info("Wait for response");
//            // Wait for response
//            byte[] response = _udps.get(V2xModulesEnum.Ldm).receive();
//            if (
//                    (response[0] == TestAdapterMessageTypeEnum.UtResult.convert()) &&
//                    (response[1] == 0x00) && 
//                    (response[2] == 0x01) && 
//                    (response[3] == 0x01)) {
//                    return true;
//                } // else, return false
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
        return false;
    }

    @Override
    public boolean initialize(final String settings) {
        _logger.entering("DenmUpperTester", "initialize", (settings == null) ? "(null)" : settings);

        // Sanity check
        if ((settings == null) || settings.isEmpty()) {
            _logger.severe("DenmUpperTester.initialize: Wrong parameters");
            return false;
        }

        try {
            String[] modules = settings.split(";");
            if ((modules == null) ||(modules.length == 0)) {
                _logger.severe("DenmUpperTester.initialize: Malformed parameters");
                return false;
            }
            for (String module : modules) {
                Matcher matcher = _pattern.matcher(module);
                if (matcher.find()) {
                    V2xModulesEnum moduleId = V2xModulesEnum.valueOf(matcher.group(1));
                    _udps.put(
                            moduleId, 
                            UdpFactory.getInstance().getUdpImpl(moduleId));
                    _udps.get(moduleId).initialize(matcher.group(2), Integer.parseInt(matcher.group(3)), Integer.parseInt(matcher.group(4)));
                } else {
                    _logger.severe("DenmUpperTester.initialize: Malformed parameters (2)");
                    return false;
                }
            } // End of 'for' statement
            if (_udps.isEmpty()) {
                _logger.severe("DenmUpperTester.initialize: Malformed parameters (3)");
                return false;
            }

            byte[] buffer = new byte[] {};
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmInitialize, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtInitialize, buffer, 0);
            for (V2xModulesEnum key : _udps.keySet()) {
                _udps.get(key).send(buffer);
                _udps.get(key).receive();
            } // End of 'for' statement

            _logger.exiting("DenmUpperTester", "initialize");
            return true;
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    } // End of method initialize

    @Override
    public boolean uninitialize() {
        _logger.entering("DenmUpperTester", "uninitialize");

        for (V2xModulesEnum key : _udps.keySet()) {
            _udps.get(key).uninitialize();
        }
        _udps.clear();

        return true;
    } // End of method uninitialize

} // End of class DenmUpperTester
