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

import java.io.ByteArrayOutputStream;
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.ICamUpperTester;

/**
 * Nec implementation of CAM upper tester
 */
public class CamUpperTester implements ICamUpperTester {

    /**
     * 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 CamUpperTester() {
        _logger.entering("CamUpperTester", "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

    private boolean sendValue(final TestAdapterMessageTypeEnum typeId, final int value, final IUdpImpl udp) {
        _logger.entering("CamUpperTester", "sendValue", String.format("%s - %d", typeId.name(), value));

        try {
            byte[] buffer = ByteHelper.intToByteArray(value, 4);
            buffer = _codec.Encode(typeId, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtCamEvent, buffer, 0);
            udp.send(buffer);

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

    private boolean sendValue(final TestAdapterMessageTypeEnum typeId, final short value, final IUdpImpl udp) {
        _logger.entering("CamUpperTester", "sendValue", String.format("%s - %d", typeId.name(), value));

        try {
            byte[] buffer = ByteHelper.intToByteArray(value, 2);
            buffer = _codec.Encode(typeId, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtCamEvent, buffer, 0);
            udp.send(buffer);

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

    private boolean sendValue(final TestAdapterMessageTypeEnum typeId, final boolean value, final IUdpImpl udp) {
        _logger.entering("CamUpperTester", "sendValue", String.format("%s - %s", typeId.name(), (value) ? "true" : "false"));
        return sendValue(typeId, (byte)((value) ? 0x01 : 0x00), udp);
    } // End of method sendValue

    private boolean sendValue(final TestAdapterMessageTypeEnum typeId, final byte value, final IUdpImpl udp) {
        _logger.entering("CamUpperTester", "sendValue", String.format("%s - %02x", typeId.name(), value));

        try {
            byte[] buffer = new byte[] { value };
            buffer = _codec.Encode(typeId, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtCamEvent, buffer, 0);
            udp.send(buffer);

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

    @Override
    public boolean changeCurvature(int curvature) {
        _logger.entering("CamUpperTester", "changeCurvature", String.format("curvature: %d", curvature));
        return sendValue(TestAdapterMessageTypeEnum.changeCurvature, curvature, _udps.get(V2xModulesEnum.Vdp));
    }

    @Override
    public boolean changeHeading(int direction) {
        _logger.entering("CamUpperTester", "changeHeading", String.format("direction: %d", direction));
        return sendValue(TestAdapterMessageTypeEnum.changeHeading, direction, _udps.get(V2xModulesEnum.Position));
    }

    @Override
    public boolean changePosition(int distance) {
        _logger.entering("CamUpperTester", "changePosition", String.format("distance: %d", distance));
        return sendValue(TestAdapterMessageTypeEnum.changePosition, distance, _udps.get(V2xModulesEnum.Position));
    }

    @Override
    public boolean changeSpeed(int variation) {
        _logger.entering("CamUpperTester", "changeSpeed", String.format("variation: %d", variation));
        return sendValue(TestAdapterMessageTypeEnum.changeSpeed, variation, _udps.get(V2xModulesEnum.Position));
    }

    @Override
    public boolean checkLdm(byte[] camPdu) {
        _logger.entering("CamUpperTester", "checkLdm");

        // FIXME To be done
//        try {
//            // Send PDU
//            _logger.info("Sending camPdu");
//            _udps.get(V2xModulesEnum.Ldm).send(camPdu);
//            _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 setCrashSignal(boolean crashed) {
        _logger.entering("CamUpperTester", "setCrashSignal", String.format("status: %s", (crashed) ? "true" : "false"));
        return sendValue(TestAdapterMessageTypeEnum.setCrashSignal, (boolean)crashed, _udps.get(V2xModulesEnum.Vdp));
    }

    @Override
    public boolean setDangerousGoodsStatus(int dangerousGoodsStatus) {
        _logger.entering("CamUpperTester", "setDangerousGoodsStatus", String.format("dangerousGoodsStatus: %d", dangerousGoodsStatus));
        return sendValue(TestAdapterMessageTypeEnum.setDangerousGoodsStatus, dangerousGoodsStatus, _udps.get(V2xModulesEnum.Vdp));
    }

    @Override
    public boolean setDistanceToStopLine(int distance) {
        _logger.entering("CamUpperTester", "setDistanceToStopLine", String.format("distance: %d", distance));
        return sendValue(TestAdapterMessageTypeEnum.setDistanceToStopLine, distance, _udps.get(V2xModulesEnum.Vdp));
    }

    @Override
    public boolean setDoorStatus(byte doorStatus) {
        _logger.entering("CamUpperTester", "setDoorStatus", String.format("status: %02x", doorStatus));
        return sendValue(TestAdapterMessageTypeEnum.setDoorStatus, (byte)doorStatus, _udps.get(V2xModulesEnum.Vdp));
    }

    @Override
    public boolean setExteriorLightsStatus(byte lightStatus) {
        _logger.entering("CamUpperTester", "setExteriorLightsStatus", String.format("status: %02x", lightStatus));
        return sendValue(TestAdapterMessageTypeEnum.setExteriorLightsStatus, (byte)lightStatus, _udps.get(V2xModulesEnum.Vdp));
    }

    @Override
    public boolean setLengthWidthPrecision(boolean lengthWidthPrecision) {
        _logger.entering("CamUpperTester", "setLengthWidthPrecision", String.format("status: %s", (lengthWidthPrecision) ? "true" : "false"));
        return sendValue(TestAdapterMessageTypeEnum.setLengthWidthPrecision, (boolean)lengthWidthPrecision, _udps.get(V2xModulesEnum.Vdp));
    }

    @Override
    public boolean setLightBarStatus(short status) {
        _logger.entering("CamUpperTester", "setLightBarStatus", String.format("status: %d", status));
        return sendValue(TestAdapterMessageTypeEnum.setLightBarStatus, (short)status, _udps.get(V2xModulesEnum.Vdp));
    }

    @Override
    public boolean setOccupancy(int occupancy) {
        _logger.entering("CamUpperTester", "setOccupancy", String.format("occupancy: %d", occupancy));
        return sendValue(TestAdapterMessageTypeEnum.setOccupancy, occupancy, _udps.get(V2xModulesEnum.Vdp));
    }

    @Override
    public boolean setPtLineDescription(String courseOfJourney, String lineRef, String routeRef) {
        _logger.entering("CamUpperTester", "setPtLineDescription", String.format("%s - %s - %s", courseOfJourney, lineRef, routeRef));

        try {
            // courseOfJourney
            ByteArrayOutputStream datas = new ByteArrayOutputStream();
            datas.write(ByteHelper.intToByteArray(courseOfJourney.length(), 4));
            if (courseOfJourney.length() != 0) datas.write(courseOfJourney.getBytes("ASCII"));
            byte[] buffer = _codec.Encode(TestAdapterMessageTypeEnum.courseOfJourney, datas.toByteArray(), 0);
            // lineRef
            datas.reset();
            datas.write(ByteHelper.intToByteArray(lineRef.length(), 4));
            if (lineRef.length() != 0) datas.write(lineRef.getBytes("ASCII"));
            buffer = ByteHelper.concat(buffer, _codec.Encode(TestAdapterMessageTypeEnum.lineRef, datas.toByteArray(), 0));
            // routeRef
            datas.reset();
            datas.write(ByteHelper.intToByteArray(routeRef.length(), 4));
            if (routeRef.length() != 0) datas.write(routeRef.getBytes("ASCII"));
            buffer = ByteHelper.concat(buffer, _codec.Encode(TestAdapterMessageTypeEnum.routeRef, datas.toByteArray(), 0));
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.setPtLineDescription, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtCamEvent, buffer, 0);
            _udps.get(V2xModulesEnum.Vdp).send(buffer);

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

    @Override
    public boolean setScheduleDeviation(int delay) {
        _logger.entering("CamUpperTester", "setScheduleDeviation", String.format("delay: %d", delay));
        return sendValue(TestAdapterMessageTypeEnum.setScheduleDeviation, delay, _udps.get(V2xModulesEnum.Vdp));
    }

    @Override
    public boolean setSireneStatus(short status) {
        _logger.entering("CamUpperTester", "setSireneStatus", String.format("status: %d", status));
        return sendValue(TestAdapterMessageTypeEnum.setSireneStatus, (short)status, _udps.get(V2xModulesEnum.Vdp));
    }

    @Override
    public boolean setTrafficLightPriority(short priority) {
        _logger.entering("CamUpperTester", "setTrafficLightPriority", String.format("priority: %d", priority));
        return sendValue(TestAdapterMessageTypeEnum.setTrafficLightPriority, (short)priority, _udps.get(V2xModulesEnum.Vdp));
    }

    @Override
    public boolean setTurnAdvice(int direction, int distance) {
        _logger.entering("CamUpperTester", "setTurnAdvice", String.format("%01X - %d", direction, distance));

        try {
            byte[] bufferDirection = _codec.Encode(TestAdapterMessageTypeEnum.direction, ByteHelper.intToByteArray(direction, 4), 0);
            byte[] bufferDistance = _codec.Encode(TestAdapterMessageTypeEnum.distance, ByteHelper.intToByteArray(distance, 4), 0);
            byte[] buffer = ByteHelper.concat(bufferDirection, bufferDistance);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.setTurnAdvice, buffer, 0);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtCamEvent, buffer, 0);
            _udps.get(V2xModulesEnum.Vdp).send(buffer);

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

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

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

        try {
            String[] modules = settings.split(";");
            if ((modules == null) ||(modules.length == 0)) {
                _logger.severe("CamUpperTester.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("CamUpperTester.initialize: Malformed parameters (2)");
                    return false;
                }
            } // End of 'for' statement
            if (_udps.isEmpty()) {
                _logger.severe("CamUpperTester.initialize: Malformed parameters (3)");
                return false;
            }

            byte[] buffer = new byte[] {};
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtCamInitialize, 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("CamUpperTester", "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("CamUpperTester", "uninitialize");

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

        return true;
    } // End of method uninitialize

} // End of class CamUpperTester
