package net.sourceforge.rtf.template;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.text.Format;
import java.util.HashMap;
import java.util.Map;

import net.sourceforge.rtf.ITemplateEngine;
import net.sourceforge.rtf.context.image.ImageLink;
import net.sourceforge.rtf.context.image.ImageWrapper;
import net.sourceforge.rtf.format.DefaultHtmlToRTFStringFormat;
import net.sourceforge.rtf.format.DefaultImageLinkFormat;
import net.sourceforge.rtf.format.DefaultInputStreamFormat;
import net.sourceforge.rtf.format.DefaultRTFCodeStringFormat;
import net.sourceforge.rtf.format.rtfcode.IRTFCode;
import net.sourceforge.rtf.format.rtfcode.RTFCodeString;

public abstract class AbstractTemplateEngine implements ITemplateEngine {
	
    private IContext context;

    /**
     * Formats to use for formatting of values.
     */
    protected Map formats = null;
    
    /**
     * Reader that contains the template.
     */
    protected Reader template;    

    public AbstractTemplateEngine() {
        formats = new HashMap();
        /**
         * Manage image with Inputstream
         */
        formats.put(FileInputStream.class, new DefaultInputStreamFormat());
        formats.put(InputStream.class, new DefaultInputStreamFormat());
        formats.put(ImageWrapper.class, new DefaultInputStreamFormat());
        // Manage RTF code
        formats.put(RTFCodeString.class, new DefaultRTFCodeStringFormat());
        formats.put(String.class, new DefaultHtmlToRTFStringFormat());
        formats.put(ImageLink.class, new DefaultImageLinkFormat());
    }
    
	/**
     * Put a value for the given key.
     *
     * @param key
     * @param value
     */
    public void put(String key, Object value) {
    	if (this.context == null) {
    		this.context = newContext();
    	}
    	this.context.put(key, value);
    }  
    
    /**
     * Merge.
     *
     * @param file    name of file to merge into
     */
    public void merge(String file) throws Exception
    {
        merge(new File(file));
    }

    /**
     * Merge.
     *
     * @param file    file to merge into
     */
    public void merge(File file) throws Exception
    {

        Writer writer = null;
        try
        {
            writer = new FileWriter(file);
            merge(writer);
        }
        finally
        {
            if (writer != null)
            {
                writer.close();
            }
        }
    }    
    
    public void merge(Writer writer) throws Exception {
    	if (template == null)
    		throw new IOException("Template must be defined. Use setTemplate method to set it.");
    	mergeWithTemplateEngine(writer);    	
    }
    
    /**
     * Use a pre-populated formats map as the map for this template
     *
     * @param commonFormats  a pre-built map of formats
     */
    public void setFormatMap( Map commonFormats)
    {
        formats = commonFormats;
    }

    /**
     * Set the default format for a given class.
     *
     * @param clazz   class to set format for
     * @param format  format to use for instances of the given class
     */
    public void setDefaultFormat(Class clazz, Format format)
    {
        formats.put(clazz, format);
    }

    /**
     * Get the default format for a given class.
     *
     * @param clazz   class to get default format for
     * @return        format
     */
    public Format getDefaultFormat(Class clazz)
    {
        return (Format)formats.get(clazz);
    }

    
    protected void setContext(IContext context) {
    	this.context = context;
    }
    
    public IContext getContext() {
        if (context == null) {
            initializeContext();
        }
		return context;
	}

    public void setTemplate(Reader template) {
    	this.template = template;
    }

    /**
     * Return true if value must be escaped and false otherwise 
     * @param value
     * @return
     */
    public boolean mustBeEscaped(Object value) {
    	if (value instanceof InputStream)
    		return false;
    	if (value instanceof ImageLink)
    		return false;
    	if (value instanceof ImageWrapper)
    		return false;
    	if (value != null && value.toString().contains("<a")) {
    		return false;
    	}
    	if (value != null && value.toString().contains("<strong")) {
    		return false;
    	}
    	if (value != null && value instanceof IRTFCode) {
    		return ((IRTFCode)value).isEscaped();
    	}
    	return true;
    }
    
    /**
     * Escape special chracters { and } with \{ and \}
     * otherwise, RTF document generated is not valid.
     * @param value
     * @return
     */
    protected Object escapeSpecialCharacters(Object value) {
    	if(value != null && value instanceof String ) {
    		String stringValue = (String)value;
    		if (stringValue.indexOf("{") != -1) {
    			stringValue = stringValue.replaceAll("\\{", "\\\\{");
    		}
    		if (stringValue.indexOf("}") != -1) {
    			stringValue = stringValue.replaceAll("\\}", "\\\\}");
    		}
    		return stringValue;
        }
    	return value;
    }    
    
    public IContext initializeContext() {
        this.context = newContext();
        return this.context;
    }
    
    /**
     * Use a pre-populated Context.
     * This method treats any previously populated keys in the context kindly,
     * so it won't act unpredictably if this is called late
     *
     * @param innerContext  a pre-populated Context object
     */
    public void setGlobalContext( IContext globalContext)
    {           
        IContext newcontext = newContext( globalContext );
        if (context != null) {
            Object[] keys = context.getKeys();
            for( int kk = 0; kk < keys.length; kk++ ) {
                newcontext.put( (String)keys[kk], context.get( (String)keys[kk] ) );
            }
        }
        context = newcontext;
    }    
    
    /**
     * 
     * @return
     */
    private IContext newContext(IContext context) {
        IContext newcontext = newContext();
        if (context != null) {            
            Object[] keys = context.getKeys();
            for( int kk = 0; kk < keys.length; kk++ ) {
                newcontext.put( (String)keys[kk], context.get( (String)keys[kk] ) );
            }
        }
        return newcontext;
    }
    
    protected abstract void mergeWithTemplateEngine(Writer writer) throws Exception;
    
}
