package org.etsi.its.uppertester.itri;

import java.io.IOException;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Date;
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.IDenmUpperTester;

/**
 * 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 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");
    
    
    /**
     * Regex pattern for module settings decoding operation
     */
    private Pattern _pattern;
    
    /**
     * Reference to the TA codec
     */
    private UpperTesterCodecImpl _codec;
    
    /**
     * Default expiry time for DENM (in ms)
     */
    private long _defaultExpiryTime;
    
    private UdpImpl _utSendPort;
    
    /**
     * Constructor
     */
    public DenmUpperTester() {
        _logger.entering("DenmUpperTester", "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();
        _defaultExpiryTime = 60000;
        _utSendPort = null;
    }
    
    @Override
    public boolean checkDenmEvent(byte[] denmPdu) {
        _logger.entering("DenmUpperTester", "checkDenmEvent");
        
        try {
            byte[] buffer = ByteHelper.intToByteArray(denmPdu.length, 2);
            buffer = ByteHelper.concat(buffer, denmPdu);
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.Situation, buffer, 0);
            // Send PDU
            _logger.info("Sending denmPdu");
            _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 generateDenmEvent(DenmEvent denmEvent) {
        _logger.entering("DenmUpperTester", "generateDenmEvent", String.format("%d - %d", denmEvent.cause, denmEvent.subCause));
        
        try {
            byte[] buffer = new byte[] { (byte)denmEvent.cause };
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)denmEvent.subCause });
            // Fixed all values with default 0x00
            // Traffic flow effect
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Repetition frequency
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Severity of the event
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Timestamp indicating event expiration
            buffer = ByteHelper.concat(buffer, ByteHelper.longToByteArray((long)(new Date().getTime() + _defaultExpiryTime), 6));
            // M|CT|Rel|T
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Linked Cause
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Linked SubCause
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean generateDenmEvent(DenmEvent denmEvent, short trafficFlowEffect) {
        _logger.entering("DenmUpperTester", "generateDenmEvent", String.format("%d - %d - trafficFlowEffect: %d", denmEvent.cause, denmEvent.subCause, trafficFlowEffect));
        
        try {
            byte[] buffer = new byte[] { (byte)denmEvent.cause };
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)denmEvent.subCause });
            // Fixed all values with default 0x00
            // Traffic flow effect
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)trafficFlowEffect });
            // Repetition frequency
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Severity of the event
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Timestamp indicating event expiration
            buffer = ByteHelper.concat(buffer, ByteHelper.longToByteArray((long)(new Date().getTime() + _defaultExpiryTime), 6));
            // M|CT|Rel|T
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Linked Cause
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Linked SubCause
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean generateDenmEvent(DenmEvent denmEvent, DenmEvent linkedDenmEvent) {
        _logger.entering("DenmUpperTester", "generateDenmEvent", String.format("%d - %d - %d - %d", denmEvent.cause, denmEvent.subCause, linkedDenmEvent.cause, linkedDenmEvent.subCause));
        
        try {
            byte[] buffer = new byte[] { (byte)denmEvent.cause };
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)denmEvent.subCause });
            // Fixed all values with default 0x00
            // Traffic flow effect
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Repetition frequency
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Severity of the event
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Timestamp indicating event expiration
            buffer = ByteHelper.concat(buffer, ByteHelper.longToByteArray((long)(new Date().getTime() + _defaultExpiryTime), 6));
            // M|CT|Rel|T
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Linked Cause
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)linkedDenmEvent.cause });
            // Linked SubCause
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)linkedDenmEvent.subCause });
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean generateDenmEvent(DenmEvent denmEvent, long expirationTime) {
        _logger.entering("DenmUpperTester", "generateDenmEvent", String.format("%d - %d - %d", denmEvent.cause, denmEvent.subCause, expirationTime));
        
        try {
            byte[] buffer = new byte[] { (byte)denmEvent.cause };
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)denmEvent.subCause });
            // Fixed all values with default 0x00
            // Traffic flow effect
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Repetition frequency
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Severity of the event
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Timestamp indicating event expiration
            buffer = ByteHelper.concat(buffer, ByteHelper.longToByteArray(expirationTime, 6));
            // M|CT|Rel|T
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Linked Cause
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Linked SubCause
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean generateDenmEvent(DenmEvent denmEvent, TestAdapterMessageTypeEnum type, int event) {
        throw new RuntimeException("Not implemented");
    }

    @Override
    public boolean generateDenmEvent(DenmEvent denmEvent, long expirationTime, short frequency) {
        _logger.entering("DenmUpperTester", "generateDenmEvent", String.format("%d - %d - %d - 0x%02x", denmEvent.cause, denmEvent.subCause, expirationTime, (byte)frequency));
        
        try {
            byte[] buffer = new byte[] { (byte)denmEvent.cause };
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)denmEvent.subCause });
            // Fixed all values with default 0x00
            // Traffic flow effect
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Repetition frequency
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)frequency });
            // Severity of the event
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Timestamp indicating event expiration
            buffer = ByteHelper.concat(buffer, ByteHelper.longToByteArray(expirationTime, 6));
            // M|CT|Rel|T
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Linked Cause
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Linked SubCause
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _utSendPort.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", String.format("%d - %d", denmEvent.cause, denmEvent.subCause));
        
        try {
            byte[] buffer = new byte[] { (byte)denmEvent.cause };
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)denmEvent.subCause });
            // Fixed all values with default 0x00
            // Traffic flow effect
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Repetition frequency
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Severity of the event
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Timestamp indicating event expiration
            buffer = ByteHelper.concat(buffer, ByteHelper.longToByteArray((long)(new Date().getTime() + _defaultExpiryTime), 6));
            // M|CT|Rel|T
            byte event = 0x00; // bit<7,0> unset
            // M: 1 bit - 0: the event is not mobile, 1: the event is mobile
            if (mobile) event |= 0x80; // Set bit<7>
            // CT: 3 bits - Cause type - 0: itsStation, 1: geographical region, Other values are forbidden.
            if (causeType == 1) event |= 0x10; // Set bit<6,4>
            // Rel: 3 bits - Relevance - 0: physically relevant, 1: difficult driving conditions, Other values are forbidden.
            if (causeType == 1) event |= 0x02; // Set bit<3,1>
            // T: 1 bit - Time critical - 0: not critical, 1: time critical road safety event
            if (timeCritical == true) event |= 0x01; // Set bit<0>
            buffer = ByteHelper.concat(buffer, new byte[] { event });
            // Linked Cause
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            // Linked SubCause
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)0x00 });
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.UtDenmEvent, buffer, 0);
            _utSendPort.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 {
            byte[] buffer = new byte[] { (byte)denmEvent.cause };
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)denmEvent.subCause });
            // stationId
            buffer = ByteHelper.concat(buffer, ByteHelper.intToByteArray(actionId.stationId, 4));
            // sequenceNo
            buffer = ByteHelper.concat(buffer, ByteHelper.intToByteArray(actionId.sequenceNo, 2));
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.eventCancellation, buffer, 0);
            _utSendPort.send(buffer);

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

    @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 {
            byte[] buffer = new byte[] { (byte)denmEvent.cause };
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)denmEvent.subCause });
            // stationId
            buffer = ByteHelper.concat(buffer, ByteHelper.intToByteArray(actionId.stationId, 4));
            // sequenceNo
            buffer = ByteHelper.concat(buffer, ByteHelper.intToByteArray(actionId.sequenceNo, 2));
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.eventNegation, buffer, 0);
            _utSendPort.send(buffer);

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

    @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 {
            byte[] buffer = new byte[] { (byte)denmEvent.cause };
            buffer = ByteHelper.concat(buffer, new byte[] { (byte)denmEvent.subCause });
            // stationId
            buffer = ByteHelper.concat(buffer, ByteHelper.intToByteArray(actionId.stationId, 4));
            // sequenceNo
            buffer = ByteHelper.concat(buffer, ByteHelper.intToByteArray(actionId.sequenceNo, 2));
            // Expiry time
            buffer = ByteHelper.concat(buffer, ByteHelper.longToByteArray(expiryTime, 6));
            buffer = _codec.Encode(TestAdapterMessageTypeEnum.eventCharact, buffer, 0);
            _utSendPort.send(buffer);

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

    @Override
    public boolean initialize(String settings) {
        _logger.entering("DenmUpperTester", this + ".initialize", (settings == null) ? "(null)" : settings);
        
        // Sanity check
        if ((settings == null) || settings.isEmpty()) {
            _logger.severe("DenmUpperTester.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("DenmUpperTester.initialize: Malformed parameters (2)");
                return false;
            }
            
            try {
                _utSendPort.send(new byte[] { TestAdapterMessageTypeEnum.UtDenmInitialize.convert() });
                _utSendPort.receive();
            } catch (IOException e) {
                e.printStackTrace();
            }
            
            _logger.exiting("DenmUpperTester", "initialize");
            return true;
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (SocketException e) {
            e.printStackTrace();
        }
        return false;
    }

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