package org.etsi.its.tool.elvior;

import java.io.IOException;
import java.util.Properties;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.elvior.ttcn.tritci.ChannelEventHandler;
import org.elvior.ttcn.tritci.TciProvider;
import org.elvior.ttcn.tritci.TriTciChannel;
import org.etsi.codec.ITCIRequired;
import org.etsi.codec.TciCDWrapperFactory;
import org.etsi.tool.elvior.TciCDWrapper;
import org.etsi.ttcn.codec.CodecFactory;
import org.etsi.ttcn.tci.TciCDProvided;
import org.etsi.ttcn.tci.TciCDRequired;
import org.etsi.ttcn.tci.Type;
import org.etsi.ttcn.tci.Value;
import org.etsi.ttcn.tri.TriMessage;

public class Its_CodecProvider implements TciCDProvided, ChannelEventHandler, ITCIRequired {
    
    private static Properties _properties = new Properties();
    
    /**
     * Logger instance
     */
    private final static Logger _logger = Logger.getLogger("org.etsi.its");
    
    private TciCDRequired _cdReq;
    
    public Its_CodecProvider() {
        _logger.entering("Its_CodecProvider", "Its_CodecProvider");
        
        // Load Codec settings
        try {
            _properties.load(MainCodec.class.getResourceAsStream("/org/etsi/its/tool/elvior/res/codec.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        String debugLevel = _properties.getProperty("DEBUG_ENABLED", "OFF");
        Level level = Level.OFF;
        if (debugLevel.equalsIgnoreCase("ALL")) {
            level = Level.ALL;
        } else if (debugLevel.equalsIgnoreCase("INFO")) {
            level = Level.INFO;
        } else if (debugLevel.equalsIgnoreCase("SEVERE")) {
            level = Level.SEVERE;
        } else if (!debugLevel.equalsIgnoreCase("OFF")) {
            System.err.println("Unsupported logging level: " + debugLevel);
        }
        _logger.addHandler(new ConsoleHandler());
        _logger.setLevel(level);
        _logger.getHandlers()[0].setLevel(level);
        
        // Register this object as the instance implementing the TCI-CD Provided interface
        TciProvider.getInstance().setTciCDProvided(this);
        TriTciChannel channel = TriTciChannel.getInstance();
        // Set application name that will be displayed in the TestCast log
        channel.setApplicationName("ItsCodec");
        // Register this object as the receiver of events generated by TestCast framework
        channel.setEventHandler(this); 
        
        TciCDWrapperFactory.getInstance().setImpl(new TciCDWrapper());
    }
    
    public void run() { 
        _logger.entering("Its_CodecProvider", "run");
        
        // Attempt to connect to TestCast
        if (TriTciChannel.getInstance().open()) {
            try {
                synchronized(this) {
                    wait();
                }
            } catch (InterruptedException e) {
            }
        }
        
        _logger.exiting("Its_CodecProvider", "run");
    }
    
    @Override
    public void onConnectionClosed() {
        _logger.entering("Its_CodecProvider", "onConnectionClosed");
        synchronized(this) { 
            notify(); 
        }
    }
    
    @Override
    public void onConnectionEstablished() {
        _logger.entering("Its_CodecProvider", "onConnectionEstablished");
    }
    
    @Override
    public void onError(String error) {
        _logger.entering("Its_CodecProvider", "onError", error);
    }
    
    @Override
    public Value decode(TriMessage message, Type decodingHypothesis) {
        _logger.entering(
            "decode", 
            String.format("%s - %s - %s", 
                decodingHypothesis.getName(), 
                decodingHypothesis.getTypeEncoding(), 
                decodingHypothesis.getTypeClass()));
        String encodingName = decodingHypothesis.getTypeEncoding();
        if (encodingName != null) {
            encodingName = decodingHypothesis.getDefiningModule().getModuleName();
        } else { // Use basic codec
            encodingName = "";
        }
        TciCDProvided codec = getCodec(encodingName);
        if (codec == null) {
            return null;
        }
        Value value = codec.decode(message, decodingHypothesis);
        _logger.exiting("decode", (value != null) ? value.toString() : "(null)");
        return value;
    }
    
    @Override
    public TriMessage encode(Value value) {
        _logger.entering(
            "encode",
            String.format(
                ">>> encode: %s - %s - %s - %s - %s - %s - %s - %s - %s",
                value.getType().getName(),
                value.getType().getDefiningModule(),
                value.getType().getDefiningModule().getModuleName(),
                value.getType().getTypeExtension(),
                value.getType().getTypeEncoding(),
                value.getType().getTypeEncodingVariant(),
                value.getValueEncoding(),
                value.getValueEncodingVariant(),
                (value.notPresent() == true) ? "true" : "false"
            )
        );
        
        String encodingName = value.getType().getTypeEncoding();
        if ((encodingName != null) && !encodingName.startsWith("Lib")) {
            encodingName = value.getType().getDefiningModule().getModuleName();
        } else if (encodingName == null) { // Use basic codec
            encodingName = "";
        }
        TciCDProvided codec = getCodec(encodingName);
        if (codec == null) {
            return null;
        }
        TriMessage res = codec.encode(value);
        _logger.exiting("encode", res.toString());
        return res;
    }
    
    /** 
     * This method provides the codec associated to the specified encoding identifier
     * 
     * @param rb TTwb Runtime reference
     * @param encodingName The name of the encoding, specified by the TTCN-3 key words 'with/encode'
     * @return The codec associated to the specified encoding identifier
     */
    @Override
    public TciCDProvided getCodec(String encodingName) {
        _logger.entering("getCodec", encodingName);
        
        _cdReq = TciProvider.getInstance().getTciCDRequired();
        
        // Register External codecs
        CodecFactory cf = CodecFactory.getInstance();
        // TODO Reuse Build-in codec here
        
        String[] asn1Codecs = _properties.getProperty("ASN.1Codecs", "").split(",");
        System.out.println("getCodec: " + asn1Codecs);
        for (String codec : asn1Codecs) {
        	System.out.println("getCodec: " + codec);
            String[] paths = _properties.getProperty(codec, "").split(",");
            try {
                cf.setExternalCodec(paths[0], (TciCDProvided) Class.forName(paths[1]).newInstance());
            } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        } // End of 'foreach' loop 
// TODO To be removed
//        cf.setExternalCodec("LibItsFntp_asn1", cs.getCodec("LibItsFntp_asn1"));
//        cf.setExternalCodec("LibItsFsap_asn1", cs.getCodec("LibItsFsap_asn1"));
//        cf.setExternalCodec("LibItsIicp_asn1", cs.getCodec("LibItsIicp_asn1"));
//        cf.setExternalCodec("LibItsCALMmsap_asn1", cs.getCodec("LibItsCALMmsap_asn1"));
//        cf.setExternalCodec("LibItsCALMllsap_asn1", cs.getCodec("LibItsCALMllsap_asn1"));
        //TODO: add V2G external codecs (xsd)
        
        return new Codec();
    }
    
    private class Codec implements TciCDProvided {

        @Override
        public Value decode(TriMessage message, Type decodingHypothesis) {
            _logger.entering("Codec", "decode", decodingHypothesis.getName());
            
            org.etsi.ttcn.codec.MainCodec codec = new org.etsi.ttcn.codec.MainCodec(_cdReq);
            Value v = null;
            try {
                v = codec.triDecode(message, decodingHypothesis);
            }
            catch(Exception e) {
                e.printStackTrace();
            }
            return v;
        }

        @Override
        public TriMessage encode(Value value) {
            _logger.entering("Codec", "encode");
            
            org.etsi.ttcn.codec.MainCodec codec = new org.etsi.ttcn.codec.MainCodec(_cdReq);
            TriMessage m = null;
            try {
                m = codec.triEncode(value);
            }
            catch(Exception e) {
                e.printStackTrace();
            }
            return m;
        }
    }
    
} // End of class Its_CodecProvider
