package org.etsi.ttcn.tool.elvior.codec;

import java.io.IOException;
import java.util.Properties;

import org.elvior.ttcn.tritci.ChannelEventHandler;
import org.elvior.ttcn.tritci.TciProvider;
import org.elvior.ttcn.tritci.TriTciChannel;
import org.etsi.ttcn.codec.core.ITciRequired;
import org.etsi.ttcn.codec.core.ITciCDWrapper;
import org.etsi.ttcn.codec.core.MainCodec;
import org.etsi.ttcn.codec.core.TciCDWrapperFactory;
import org.etsi.ttcn.codec.core.CodecFactory;
import org.etsi.ttcn.tci.TciCDProvided;
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();
    
    private ITciCDWrapper _cdReq;
    
    private CodecFactory _cf;
    
    public Its_CodecProvider() {
        
        // Load Codec settings
        _cdReq = null;
        _cf = null;
        try {
            _properties.load(CodecMain.class.getResourceAsStream("./etc/codec.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
                
        // 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); 
        
        initializeCodecs();
    }
    
    private void initializeCodecs() { // FIXME: ASN.1Codecs => ExternalCodecs + remove LibIts_asn1 stuff
        TciCDWrapperFactory.getInstance().setImpl(new TciCDWrapper());
        _cdReq = TciCDWrapperFactory.getTciCDInstance();
        
        // Register External codecs
        _cf = CodecFactory.getInstance();
        // TODO Reuse Build-in codec here
        
        String[] asn1Codecs = _properties.getProperty("ASN.1Codecs", "").split(",");
        for (String codec : asn1Codecs) {
            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 'for' loop
        try { // This comes with 'with encode () "UpperTester"' for all UtxxxTrigger/UtxxxUpdate messages
            _cf.setExternalCodec("per-basic-unaligned:1997", LibIts_asn1.class.newInstance());
        } catch (InstantiationException | IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
    
    public void run() { 
        
        // Attempt to connect to TestCast
        if (TriTciChannel.getInstance().open()) {
            try {
                synchronized(this) {
                    wait();
                }
            } catch (InterruptedException e) {
            }
        }
        
    }
    
    @Override
    public void onConnectionClosed() {
        synchronized(this) { 
            notify(); 
        }
    }
    
    @Override
    public void onConnectionEstablished() {
     }
    
    @Override
    public void onError(String error) {
    }
    
    @Override
    public Value decode(TriMessage message, Type decodingHypothesis) {
        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);
        return value;
    }
    
    @Override
    public TriMessage encode(Value value) {
        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);
        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(final String encodingName) {
        // FIXME Remove argument here and in the interface ITciRequired
        return new Codec();
    }
    
    private class Codec implements TciCDProvided {

        @Override
        public Value decode(TriMessage message, Type decodingHypothesis) {
            
            MainCodec codec = new MainCodec(_cdReq);
            Value v = null;
            try {
                v = codec.triDecode(message, decodingHypothesis);
            }
            catch(Exception e) {
                e.printStackTrace();
            }
            return v;
        }

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