package org.etsi.its.uppertester.itri;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;
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.UdpImpl;
import org.etsi.its.uppertester.ICamUpperTester;

/**
 * ITRI implementation of CAM upper tester
 *
 * Please refer to TR 1xx xxx V<0.0.3> (<2011-08>) document for Upper tester messages encoding description
 */
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");
    
    /**
     * Regex pattern for module settings decoding operation
     */
    private Pattern _pattern;
    
    /**
     * Reference to the TA codec
     */
    private UpperTesterCodecImpl _codec;
    
    private UdpImpl _utSendPort;
    
    /**
     * Constructor
     */
    public CamUpperTester() {
        _logger.entering("CamUpperTester", "Constructor", String.format("version: %s", Version));
        _pattern = Pattern.compile("(\\w+)\\:(\\S+)\\:(\\d+)\\:(\\d+)"); // For Java regex test, see http://www.cis.upenn.edu/~matuszek/General/RegexTester/regex-tester.html
        _codec = new UpperTesterCodecImpl();
        _utSendPort = null;
    }
    
    @Override
    public boolean changeCurvature(int curvature) {
        _logger.entering("CamUpperTester", "changeCurvature", String.format("curvature: %d", curvature));
        
        try {
            byte[] buffer = ByteHelper.intToByteArray(curvature, 2);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.changeCurvature, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean changeHeading(int direction) {
        _logger.entering("CamUpperTester", "changeHeading", String.format("direction: %d", direction));
        
        try {
            byte[] buffer = ByteHelper.intToByteArray(direction, 2);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.changeHeading, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean changePosition(int distance) {
        _logger.entering("CamUpperTester", "changePosition", String.format("distance: %d", distance));
        
        try {
            byte[] buffer = ByteHelper.intToByteArray(distance, 4);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.changePosition, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean changeSpeed(int variation) {
        _logger.entering("CamUpperTester", "changeSpeed", String.format("variation: %d", variation));
        
        try {
            byte[] buffer = ByteHelper.intToByteArray(variation, 4);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.changeSpeed, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean checkLdm(byte[] camPdu) {
        _logger.entering("CamUpperTester", "checkLdm");
        
        try {
            byte[] buffer = ByteHelper.intToByteArray(camPdu.length, 2);
            buffer = ByteHelper.concat(buffer, camPdu);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.checkLdm, buffer, 0);
            // Send PDU
            _logger.info("Sending camPdu");
            _utSendPort.send(buffer);
            _logger.info("Wait for response");
            // Wait for response
            byte[] response = _utSendPort.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("crashed: %s", new Boolean(crashed)));
        
        try {
            byte[] buffer = _codec.Encode(TestAdapterMessageTypeEnum.setCrashSignal, new byte[] { (crashed == true) ? (byte)0x01 : (byte)0x00 }, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean setDangerousGoodsStatus(int dangerousGoodsStatus) {
        _logger.entering("CamUpperTester", "setDangerousGoodsStatus", String.format("dangerousGoodsStatus: %d", dangerousGoodsStatus));
        
        try {
            byte[] buffer = ByteHelper.intToByteArray(dangerousGoodsStatus, 2);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.setDangerousGoodsStatus, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean setDistanceToStopLine(int distance) {
        _logger.entering("CamUpperTester", "setDistanceToStopLine", String.format("distance: %d", distance));
        
        try {
            byte[] buffer = ByteHelper.intToByteArray(distance, 2);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.setDistanceToStopLine, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean setDoorStatus(byte doorStatus) {
        _logger.entering("CamUpperTester", "setDoorStatus", String.format("doorStatus: 0x%02x", doorStatus));
        
        try {
            byte[] buffer = _codec.Encode(TestAdapterMessageTypeEnum.setDoorStatus, new byte[] { doorStatus }, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean setExteriorLightsStatus(byte lightStatus) {
        _logger.entering("CamUpperTester", "setExteriorLightsStatus", String.format("lightStatus: 0x%02x", lightStatus));
        
        try {
            byte[] buffer = _codec.Encode(TestAdapterMessageTypeEnum.setExteriorLightsStatus, new byte[] { lightStatus }, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean setLengthWidthPrecision(boolean lengthWidthPrecision) {
        _logger.entering("CamUpperTester", "setLengthWidthPrecision", String.format("lengthWidthPrecision: %s", new Boolean(lengthWidthPrecision)));
        
        try {
            byte[] buffer = _codec.Encode(TestAdapterMessageTypeEnum.setLengthWidthPrecision, new byte[] { (lengthWidthPrecision == true) ? (byte)0x01 : (byte)0x00 }, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean setLightBarStatus(short status) {
        _logger.entering("CamUpperTester", "setLightBarStatus", String.format("status: %d", status));
        
        try {
            byte[] buffer = _codec.Encode(TestAdapterMessageTypeEnum.setLightBarStatus, new byte[] { (byte)status }, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean setOccupancy(int occupancy) {
        _logger.entering("CamUpperTester", "setOccupancy", String.format("occupancy: %d", occupancy));
        
        try {
            byte[] buffer = _codec.Encode(TestAdapterMessageTypeEnum.setOccupancy, new byte[] { (byte)occupancy }, 0);
            _utSendPort.send(buffer);

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

    @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(), 1));
            if (courseOfJourney.length() != 0) datas.write(courseOfJourney.getBytes("ASCII"));
            byte[] buffer = datas.toByteArray();
            // lineRef
            datas.reset();
            datas.write(ByteHelper.intToByteArray(lineRef.length(), 1));
            if (lineRef.length() != 0) datas.write(lineRef.getBytes("ASCII"));
            buffer = ByteHelper.concat(buffer, datas.toByteArray());
            // routeRef
            datas.reset();
            datas.write(ByteHelper.intToByteArray(routeRef.length(), 1));
            if (routeRef.length() != 0) datas.write(routeRef.getBytes("ASCII"));
            buffer = ByteHelper.concat(buffer, datas.toByteArray());
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.setPtLineDescription, buffer, 0);
            _utSendPort.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));
        
        try {
            byte[] buffer = ByteHelper.intToByteArray(delay, 2);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.setScheduleDeviation, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean setSireneStatus(short status) {
        _logger.entering("CamUpperTester", "setSireneStatus", String.format("status: %d", status));
        
        try {
            byte[] buffer = _codec.Encode(TestAdapterMessageTypeEnum.setSireneStatus, new byte[] { (byte)status }, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean setTrafficLightPriority(short priority) {
        _logger.entering("CamUpperTester", "setTrafficLightPriority", String.format("priority: %d", priority));
        
        try {
            byte[] buffer = _codec.Encode(TestAdapterMessageTypeEnum.setTrafficLightPriority, new byte[] { (byte)priority }, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean setTurnAdvice(int direction, int distance) {
        _logger.entering("CamUpperTester", "setTurnAdvice", String.format("direction: %d - distance: %d", direction, distance));
        
        try {
            byte[] buffer = ByteHelper.concat(new byte[] { (byte)new Byte((byte) direction) }, ByteHelper.intToByteArray(distance, 2));
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.setTurnAdvice, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean initialize(String settings) {
        _logger.entering("CamUpperTester", this + ".initialize", (settings == null) ? "(null)" : settings);
        
        // Sanity check
        if ((settings == null) || settings.isEmpty()) {
            _logger.severe("CamUpperTester.initialize: Wrong parameters");
            return false;
        }
        
        try {
            Matcher matcher = _pattern.matcher(settings);
            if (matcher.find()) {
                _utSendPort = new UdpImpl();
                _utSendPort.initialize(matcher.group(2), Integer.parseInt(matcher.group(3)), Integer.parseInt(matcher.group(4)));
            } else {
                _logger.severe("CamUpperTester.initialize: Malformed parameters (2)");
                return false;
            }
            
            try {
                _utSendPort.send(new byte[] { TestAdapterMessageTypeEnum.UtCamInitialize.convert() });
                _utSendPort.receive();
            } catch (IOException e) {
                e.printStackTrace();
            }
            
            _logger.exiting("CamUpperTester", "initialize");
            return true;
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean uninitialize() {
        _logger.entering("CamUpperTester", "uninitialize");
        
        if (_utSendPort != null) {
            _utSendPort.uninitialize();
            _utSendPort = null;
        }
        
        return true;
    }
    
} // End of class CamUpperTester
