package de.uni_hildesheim.sse.trans;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

import de.uni_hildesheim.sse.model.varModel.Project;
import de.uni_hildesheim.sse.trans.convert.CNFConverter;
import de.uni_hildesheim.sse.trans.convert.MaxTermConverter2;
import de.uni_hildesheim.sse.trans.convert.ModelOptimizer;
import de.uni_hildesheim.sse.trans.convert.OptimizationParameter;
import de.uni_hildesheim.sse.trans.in.IModelReader;
import de.uni_hildesheim.sse.trans.in.InputType;
import de.uni_hildesheim.sse.trans.in.ModelReader;
import de.uni_hildesheim.sse.trans.in.ParserException;
import de.uni_hildesheim.sse.trans.in.rsf.RSFReader;
import de.uni_hildesheim.sse.trans.out.DimacsWriter;
import de.uni_hildesheim.sse.trans.out.IModelWriter;
import de.uni_hildesheim.sse.trans.out.IVMLWriter;
import de.uni_hildesheim.sse.trans.out.OutputType;
import de.uni_hildesheim.sse.utils.logger.EASyLoggerFactory;
import de.uni_hildesheim.sse.utils.logger.EASyLoggerFactory.EASyLogger;

/**
 * ModelTranslator utility class, responsible for translating models into another file format.
 * @author El-Sharkawy
 *
 */
public class ModelTranslator {
    
    private static final EASyLogger LOGGER = EASyLoggerFactory.INSTANCE.getLogger(ModelTranslator.class, Main.ID);
    
    /**
     * Should avoid instantiation of this utility class.
     */
    private ModelTranslator() {}
    
    // checkstyle: stop parameter number check
    
    /**
     * Translates a model into another file format.
     * @param in The input model, which shall be translated.
     * @param out The destination of the translated model.
     * @param inType The type of <tt>in</tt>. Must be one of {@link InputType}.
     * @param outType Specification in which format <tt>in</tt> shall be translated. Must be one of {@link OutputType}.
     * @param comment An optional comment (e.g. information about the translated model), can be <tt>null</tt>.
     * @param version Optionally the version of the translated model. Can be <tt>null</tt>. If not <tt>null</tt>,
     *     it must be in {@link de.uni_hildesheim.sse.utils.modelManagement.Version#Version(String)} syntax.
     * @param optimizations Specification which optimizations shall be applied to the transformed model. Will only be
     *     applied to transformations into CNF.
     */
    public static void translate(File in, Writer out, InputType inType, OutputType outType, String comment,
        String version, OptimizationParameter optimizations) {
        
        Project model = readSourceModel(in, inType, outType, optimizations);
        transformAndWriteModel(out, outType, comment, version, model, optimizations);
    }

    /**
     * Transforms and writes the model to the specified format.
     * @param out The destination of the translated model.
     * @param outType Specification in which format <tt>in</tt> shall be translated. Must be one of {@link OutputType}.
     * @param comment An optional comment (e.g. information about the translated model), can be <tt>null</tt>.
     * @param version Optionally the version of the translated model. Can be <tt>null</tt>. If not <tt>null</tt>,
     *     it must be in {@link de.uni_hildesheim.sse.utils.modelManagement.Version#Version(String)} syntax.
     * @param model The read model.
     * @param optimizations Specification which optimizations shall be applied to the transformed model. Will only be
     *     applied to transformations into CNF.
     */
    private static void transformAndWriteModel(Writer out, OutputType outType, String comment, String version,
        Project model, OptimizationParameter optimizations) {
        
        if (null != model) {
            IModelWriter writer = null;
            switch (outType) {
            case IVML:
                LOGGER.info("2.) Write model."); 
                writer = new IVMLWriter(out, model);
                break;
            case DIMCAS:
                int step = 2;
                LOGGER.info(step++ + ".) Convert model."); 
                CNFConverter.convert(model, new MaxTermConverter2());
                
                if (optimizations.hasAtLeastOneOption()) {
                    LOGGER.info(step++ + ".) Optimize model."); 
                    optimizeModel(model, optimizations);
                }
                
                LOGGER.info(step++ + ".) Write model."); 
                writer = new DimacsWriter(model, out);
                break;
            default:
                LOGGER.debug("Not supported input model specified.");
                break;
            }
            
            if (null != writer) {
                writer.write(comment, version);
                try {
                    out.flush();
                    out.close();
                } catch (IOException e) {
                    LOGGER.exception(e);
                }
            }
        }
    }

    /**
     * Optimizes a CNF Model using the {@link ModelOptimizer}.
     * @param model A model in CNF form to be optimized.
     * @param optimizations A specification which optimizations shall be applied.
     */
    private static void optimizeModel(Project model, OptimizationParameter optimizations) {
        boolean optimizationApplied = true;
        int run = 1;

        while (optimizationApplied) {
            // Initialization
            LOGGER.info("Optimization run " + run++);
            ModelOptimizer optimizer = new ModelOptimizer(model);
            optimizationApplied = false;
            int removed = 0;

            // Removing unused variables
            if (optimizations.removeUnusedVariables()) {
                removed = optimizer.removeUnusedVariables();
                LOGGER.info("Removed " + removed + " unused variables.");
                optimizationApplied |= removed > 0;
            }
            
            // Removing constant "variables"
            if (optimizations.eleminateConstants()) {
                removed = optimizer.handleConstantVariables();
                LOGGER.info("Removed " + removed + " constraints with constant values.");
                optimizationApplied |= removed > 0;
            }
            
            // Duplicated constraints
            if (optimizations.removeDuplicatedConstraints()) {
                removed = optimizer.removeDuplicatedConstraints();
                LOGGER.info("Removed " + removed + " duplicated constraints.");
                optimizationApplied |= removed > 0;
            }
        }
    }
    
    /**
     * Reads the source model and stores it in a {@link Project}.
     * @param in The input model, which shall be translated.
     * @param inType The type of <tt>in</tt>. Must be one of {@link InputType}.
     * @param outType Specification in which format <tt>in</tt> shall be translated. Must be one of {@link OutputType}.
     *     Depending of the {@link OutputType} some optimizations are already possible while reading the input model.
     * @param optimizations Specification which optimizations shall be applied to the transformed model. Will only be
     *     applied to transformations into CNF.
     * @return The read model or <tt>null</tt> if an error occurred.
     */
    private static Project readSourceModel(File in, InputType inType, OutputType outType,
        OptimizationParameter optimizations) {
        
        Project model = null;

        LOGGER.info("1.) Read model."); 
        IModelReader reader = null;
        switch (inType) {
        case MODEL:
            try {
                reader = new ModelReader(in, optimizations);
            } catch (IOException e) {
                LOGGER.exception(e);
            }
            break;
        case RSF:
            try {
                reader = new RSFReader(in, outType == OutputType.DIMCAS);
            } catch (IOException e) {
                LOGGER.exception(e);
            }
            break;
        default:
            LOGGER.debug("Not supported input model specified.");
            break;
        }
        
        if (null != reader) {
            try {
                model = reader.read();
            } catch (IOException e) {
                LOGGER.exception(e);
            } catch (ParserException e) {
                LOGGER.exception(e);
            }
        }
        
        return model;
    }
    
    /**
     * Translates a model into another file format.
     * Default method which should be used.
     * @param in The input model, which shall be translated.
     * @param out The destination of the translated model.
     * @param inType The type of <tt>in</tt>. Must be one of {@link InputType}.
     * @param outType Specification in which format <tt>in</tt> shall be translated. Must be one of {@link OutputType}.
     * @param comment An optional comment (e.g. information about the translated model), can be <tt>null</tt>.
     * @param version Optionally the version of the translated model. Can be <tt>null</tt>. If not <tt>null</tt>,
     *     it must be in {@link de.uni_hildesheim.sse.utils.modelManagement.Version#Version(String)} syntax.
     * @param optimizations Specification which optimizations shall be applied to the transformed model. Will only be
     *     applied to transformations into CNF.
     */
    public static void translate(File in, File out, InputType inType, OutputType outType, String comment,
        String version, OptimizationParameter optimizations) {
        
        try {
            FileWriter outWriter = new FileWriter(out);
            translate(in, outWriter, inType, outType, comment, version, optimizations);
        } catch (IOException e) {
            LOGGER.exception(e);
        }
    }

    // checkstyle: resume parameter number check
}
