/**
 * @author      ETSI / STF462 / Alexandre Berge
 * @version     $URL$$
 *              $Id$$
 */
package org.etsi.ttcn.codec.its.v2g;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;

import org.etsi.adapter.TERFactory;
import org.etsi.common.ByteHelper;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import com.siemens.ct.exi.CodingMode;
import com.siemens.ct.exi.EXIFactory;
import com.siemens.ct.exi.EncodingOptions;
import com.siemens.ct.exi.FidelityOptions;
import com.siemens.ct.exi.GrammarFactory;
import com.siemens.ct.exi.SchemaIdResolver;
import com.siemens.ct.exi.api.sax.EXIResult;
import com.siemens.ct.exi.api.sax.EXISource;
import com.siemens.ct.exi.exceptions.EXIException;
import com.siemens.ct.exi.grammars.Grammars;
import com.siemens.ct.exi.helpers.DefaultEXIFactory;

public class ExiHelper {

    private static String schemaId = "v2g";
    private static boolean locked = false;
    protected static String handshakeSchemaName = "ttcn_PowerUp\\xsd\\V2G_CI_AppProtocol.xsd";
    protected static String v2gSchemaName = "ttcn_PowerUp\\xsd\\V2G_CI_MsgBody.xsd";

    public static void setSchemaId(String schemaName) {
        while(locked) {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        }
        TERFactory.getInstance().logError("Changing Schema ID:" +schemaName);
        schemaId = schemaName;
    }

    public static void lockSchemaId() {
        locked = true;
    }

    private static void unlockSchemaId() {
        locked = false;
    }
    
    public static byte[] encode(byte[] xmlInput) {
        InputStream xmlIn = new ByteArrayInputStream(xmlInput);
        ByteArrayOutputStream exiOut = new ByteArrayOutputStream();
        
        //ByteHelper.dump("encode EXI", xmlInput);
        TERFactory.getInstance().logDebug("encode XML:" + new String(xmlInput));
        
        EXIFactory exiFactory = createEXIFactory(); 
        try {
            EXIResult exiResult = new EXIResult(exiFactory);
            exiResult.setOutputStream(exiOut); 
            XMLReader xmlReader = XMLReaderFactory.createXMLReader();
            xmlReader.setContentHandler(exiResult.getHandler());
            xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler",
            exiResult.getLexicalHandler());
            xmlReader.setProperty("http://xml.org/sax/properties/declaration-handler",
            exiResult.getLexicalHandler());
            xmlReader.parse(new InputSource(xmlIn));
        } catch (Exception e) {
            TERFactory.getInstance().logError("ERROR: Unable to EXI-encode message: " + e.getMessage());
            e.printStackTrace();
            return xmlInput;
        } 
        
        ByteHelper.dump("encode EXI", exiOut.toByteArray());
        
        return exiOut.toByteArray();
    }

    public static byte[] decode(byte[] exiInput) {
        InputStream exiIn = new ByteArrayInputStream(exiInput);
        ByteArrayOutputStream xmlOut = new ByteArrayOutputStream();

        ByteHelper.dump("decode EXI", exiInput);
        
        EXIFactory exiFactory = createEXIFactory();
        try {
            EXISource exiSource = new EXISource(exiFactory); 
            XMLReader xmlReader = exiSource.getXMLReader(); 
            TransformerFactory transformerFactory = TransformerFactory.newInstance(); 
            Transformer transformer = transformerFactory.newTransformer();
            SAXSource saxSource = new SAXSource(new InputSource(exiIn)); 
            saxSource.setXMLReader(xmlReader);
            transformer.transform(saxSource, new StreamResult(xmlOut));
        } catch (Exception e) {
            TERFactory.getInstance().logError("ERROR: Unable to EXI-decode message: " + e.getMessage());
            e.printStackTrace();
            unlockSchemaId();
            return exiInput;
        } 
        
        //ByteHelper.dump("decode XML:", xmlOut.toByteArray());
        try {
            TERFactory.getInstance().logDebug("decode XML:" + xmlOut.toString("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        unlockSchemaId();
        return xmlOut.toByteArray();
    }
    
    private static EXIFactory createEXIFactory() {

        EXIFactory exiFactory = DefaultEXIFactory.newInstance(); 
        
        try {
            // Encoding options
            EncodingOptions encodingOptions = EncodingOptions.createDefault();
            exiFactory.setEncodingOptions(encodingOptions);
            
            // Fidelity options
            FidelityOptions fidelityOptions = FidelityOptions.createDefault();
            fidelityOptions.setFidelity(FidelityOptions.FEATURE_STRICT, false);
            fidelityOptions.setFidelity(FidelityOptions.FEATURE_PREFIX, false);
            fidelityOptions.setFidelity(FidelityOptions.FEATURE_DTD, false);
            fidelityOptions.setFidelity(FidelityOptions.FEATURE_LEXICAL_VALUE, false);
            fidelityOptions.setFidelity(FidelityOptions.FEATURE_COMMENT, false);
            fidelityOptions.setFidelity(FidelityOptions.FEATURE_PI, false);
            fidelityOptions.setFidelity(FidelityOptions.FEATURE_SC, false);            
            exiFactory.setFidelityOptions(fidelityOptions); 
        }
        catch(Exception e) {
            TERFactory.getInstance().logError("ERROR: Unable to setup EXIFactory: " + e.getMessage());
            e.printStackTrace();            
        }
        
        // Coding mode
        exiFactory.setCodingMode(CodingMode.BIT_PACKED);
        
        // Value partition capacity
        exiFactory.setValuePartitionCapacity(0);
        
        // SCHEMA OPTIONS
        loadGrammar(exiFactory);

        return exiFactory;
    }
    
    private static void loadGrammar(EXIFactory exiFactory) {
        GrammarFactory grammarFactory = GrammarFactory.newInstance();
        Grammars grammars = null;
        MySchemaIdResolver schemaIdResolver = new MySchemaIdResolver(grammarFactory);
        exiFactory.setSchemaIdResolver(schemaIdResolver);
        try {
            grammars = schemaIdResolver.resolveSchemaId(schemaId); 
            if (grammars != null)
                exiFactory.setGrammars(grammars);
        } catch (EXIException e) {
            e.printStackTrace();
        }
    }    
    
    
    private static class MySchemaIdResolver implements SchemaIdResolver {

        private GrammarFactory grammarFactory;
        
        public MySchemaIdResolver(GrammarFactory grammarFactory) {
            this.grammarFactory = grammarFactory;
        }
        
        @Override
        public Grammars resolveSchemaId(String schemaId) throws EXIException {
            Grammars grammars = null;
            String explicitSchemaName = null;
            if(schemaId.equals("handshake")) {
                explicitSchemaName = handshakeSchemaName;
            }
            if(schemaId.equals("v2g")) {
                explicitSchemaName = v2gSchemaName;
            }    
            File schemaFile = new File(explicitSchemaName);
            grammars = grammarFactory.createGrammars(schemaFile.getAbsolutePath());
            return grammars;
        }
    }
}
