/*-------------------------------------------------------------------------------
Copyright (c)2000 TogetherSoft Corporation. Patents pending. All rights reserved.
-------------------------------------------------------------------------------*/

package com.togethersoft.modules.patterns.JAVA.CLASS.JSP;

import java.util.Enumeration;
import java.util.Vector;
import java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;

import com.togethersoft.openapi.sci.SciLanguageHelper;
import com.togethersoft.openapi.sci.SciElement;
import com.togethersoft.openapi.sci.SciClass;
import com.togethersoft.openapi.sci.SciOperation;
import com.togethersoft.openapi.sci.SciTag;
import com.togethersoft.openapi.sci.SciTagList;

import com.togethersoft.openapi.sci.enum.SciOperationEnumeration;

import com.togethersoft.openapi.sci.pattern.SciPattern;
import com.togethersoft.openapi.sci.pattern.SciPatternUtil;
import com.togethersoft.openapi.sci.pattern.SciPatternProperty;

import com.togethersoft.openapi.ide.IdeAccess;

import com.togethersoft.openapi.util.propertyMap.PropertyMap;
import com.togethersoft.openapi.util.propertyMap.Property;
import com.togethersoft.openapi.util.propertyMap.DefaultPropertyMap;

import com.togethersoft.modules.patterns.UNIVERSAL.PatternBase;
import com.togethersoft.modules.patterns.UNIVERSAL.PatternBaseException;
import com.togethersoft.modules.patterns.UNIVERSAL.PatternUIBuilder;

/**
 * JavaServer Page pattern
 * @author TogetherSoft
 */
public class Pattern extends PatternBase implements SciPattern {

    private static final String PATTERN_NAME = "JavaServer Page";

    public Pattern() {
        setPatternDisplayName(PATTERN_NAME);
    }

    protected void finalize() throws Throwable {
        super.finalize();

        if (myUIBuilder != null) {
            myUIBuilder.removeAllListenedInspectorProperties();
            myUIBuilder = null;
        }

        aClass = null;

        myStylesheet = null;
        myPreinclude = null;
        myPostinclude = null;
    }

    public boolean prepare() {
        SciElement element = getElement();
        if (element != null) {
            aClass = (SciClass) element;
            setPropertyValue(NAME, aClass.getName());
        }
        else {
            aClass = null;
            setPropertyValue(NAME, "JavaServerPage");
        }

        // initialize the properties with empties
        for(int i = 0; i < myBeanScopeProperties.length; i++) {
            setPropertyValue(myBeanScopeProperties[i], null);
        }

        setPropertyValue(OUTPUT_FORMAT, myOutputFormats[0]);

        if(aClass != null && getOutputFileForClass(aClass) != null) {
            setPropertyValue(OUTPUT_FILE, getOutputFileForClass(aClass));
        }
        else {
            setPropertyValue(OUTPUT_FILE, makeDefaultFileName(getStringPropertyValue(NAME)));
        }

        if(aClass != null && getStylesheetForClass(aClass) != null) {
            setPropertyValue(STYLESHEET_URL, getStylesheetForClass(aClass));
        }
        else {
            setPropertyValue(STYLESHEET_URL, null);
        }

        if(aClass != null && getPreincludeForClass(aClass) != null) {
            setPropertyValue(PREINCLUDE_URL, getPreincludeForClass(aClass));
        }
        else {
            setPropertyValue(PREINCLUDE_URL, null);
        }

        if(aClass != null && getPostincludeForClass(aClass) != null) {
            setPropertyValue(POSTINCLUDE_URL, getPostincludeForClass(aClass));
        }
        else {
            setPropertyValue(POSTINCLUDE_URL, null);
        }

        // find pattern dependencies from links
        String beanInclusionScope = null;
        String beanInclusionScopeProperty = null;
        SciClass aBeanClass = null;

        if(aClass != null) {
            for(int i = 0; i < myBeanScopes.length; i++) {
                beanInclusionScope = myBeanScopes[i];
                beanInclusionScopeProperty = myBeanScopeProperties[i];

                Enumeration thisScopeBeans = SciPatternUtil.findPatternLinkSuppliers(aClass, PATTERN_NAME, beanInclusionScope);
                while(thisScopeBeans.hasMoreElements()) {
                    aBeanClass = (SciClass) thisScopeBeans.nextElement();
                    addPropertyValue(beanInclusionScopeProperty, aBeanClass.getQualifiedName());
                }
            }
        }

        initializeInspector();
        assignActivityResponse();
        return true;
    }

    public boolean canApply() {
        try {
            String className = classNameShouldBeCorrect(NAME);
            SciClass jspClass = findClassByName(className);
            shouldBeWritable(NAME, jspClass);

            String beanName = null;
            for(int i = 0; i < myBeanScopeProperties.length; i++) {
                Enumeration beans = getProperties().properties(myBeanScopeProperties[i]);
                while(beans.hasMoreElements()) {
                    beanName = (String) ((Property) beans.nextElement()).getValue();
                    checkStringValue(beanName, true);
                }
            }

            return true;
        }
        catch(PatternBaseException e) {
            return false;
        }
    }

    public void apply() {
        if (!canApply()) {
            return;
        }
        super.apply();

        String jspName = getStringPropertyValue(NAME);
        if (aClass == null) {
            aClass = getClassByName(jspName, false);
        }
        else {
            aClass.setName(jspName);
        }

        // rip off all the beans here and collect them up in a vector
        Vector linkedBeansVector = new Vector();

        for(int i = 0; i < myBeanScopes.length; i++) {
            //String beanInclusionScope = myBeanScopes[i];
            String beanInclusionScopeProperty = myBeanScopeProperties[i];

            Enumeration beans = getProperties().properties(myBeanScopeProperties[i]);
            while(beans.hasMoreElements()) {
                Property beanProperty = (Property) beans.nextElement();
                String beanNameString = (String) beanProperty.getValue();
                SciClass beanClass = getClassByName(beanNameString, false);
                linkedBeansVector.addElement(beanClass);

                // add pattern links to the chosen beans
                SciPatternUtil.addPatternLink(aClass, beanClass, PATTERN_NAME, null, myBeanScopes[i], false);
            }
        }

        // now to generate the JSP source..
        String jspFileName = getStringPropertyValue(OUTPUT_FILE);
        setOutputFileForClass(aClass, jspFileName);

        myStylesheet = getStringPropertyValue(STYLESHEET_URL);
        setStylesheetForClass(aClass, myStylesheet);

        myPreinclude = getStringPropertyValue(PREINCLUDE_URL);
        setPreincludeForClass(aClass, myPreinclude);

        myPostinclude = getStringPropertyValue(POSTINCLUDE_URL);
        setPostincludeForClass(aClass, myPostinclude);

        generateJSP(getOutputStreamWriter(jspFileName), linkedBeansVector);
        setPropertyValue(SciPatternProperty.RESULT_ELEMENT, aClass);

        linkedBeansVector = null;
        myUIBuilder.removeAllListenedInspectorProperties();
    }

    /**
     *  Generate .jsp file displaying properties from beans provided in
     *  the beans vector of SciClass-es.
     *  @param fileName         file name to generate the content into
     *  @param beansVector      vector of SciClass, ought to be beans with get methods
     */
    private void generateJSP(OutputStreamWriter out, Vector beansVector) {
        StringBuffer jspContent = new StringBuffer(myHTML.makeHead("Generated by Together", "Bean display"));

        // insert the preinclude
        if(myPreinclude != null && myPreinclude.length() > 0) {
            jspContent.append(myHTML.makeIncludePage(myPreinclude));
        }

        // rip out the classes, pick the getxxx methods
        Enumeration beansEnum = beansVector.elements();
        while(beansEnum.hasMoreElements()) {
            SciClass aClass = (SciClass) beansEnum.nextElement();

            // put bean title
            jspContent.append(myHTML.makeHeading(aClass.getQualifiedName(), 2));

            // instantiate the bean
            String beanInstanceName = makeBeanInstanceName(aClass.getQualifiedName());
            jspContent.append(myHTML.makeUseBean(aClass.getQualifiedName(), beanInstanceName, SCOPE_PAGE)); // FIXME: scope!

            StringBuffer tableContent = new StringBuffer();

            Enumeration accessibleProperties = getAccessibleProperties(aClass.operations());

            int row = 0;
            while(accessibleProperties.hasMoreElements()) {
                String propertyName = (String)accessibleProperties.nextElement();
                StringBuffer tableRow = new StringBuffer();
                tableRow.append(myHTML.makeTableCell(myHTML.makeBold(propertyName)));
                tableRow.append(myHTML.makeTableCell(myHTML.makeItalic(myHTML.makeGetProperty(beanInstanceName, propertyName))));
                tableContent.append(myHTML.makeTableRow(tableRow.toString(),
                    (++row % 2 == 0) ? myHTML.EVEN_ROW_COLOR : myHTML.ODD_ROW_COLOR));
            }

            // close the table
            jspContent.append(myHTML.makeTable(tableContent.toString(), "1"));
        }

        // insert the postinclude
        if( myPostinclude != null && myPostinclude.length() > 0) {
            jspContent.append(myHTML.makeIncludePage(myPostinclude));
        }

        jspContent.append(myHTML.makeTail(null));

        try {
            out.write(jspContent.toString());
            out.close();
        } catch(IOException ioe) {
            ioe.printStackTrace();      // FIXME: diagnose fail somehow!
        }
    }

    /**
     *  Retrieve the names of accessible properties from the raw list of operations.
     *  @param   operations     as returned by SciClass.operations()
     *  @return                 Enumeration of String containing property names
     */
    private Enumeration getAccessibleProperties(SciOperationEnumeration operations) {
        Vector properties = new Vector();

        while(operations.hasMoreElements()) {
            SciOperation operation = operations.nextSciOperation();
            String operationName = operation.getName();
            if(operationName.startsWith(PREFIX_GET) && operationName.length() > PREFIX_GET.length()) {
                String propertyName = operationName.substring(3, operationName.length());
                // FIXME: maybe replace underscores with spaces for nice readability
                properties.addElement(propertyName);
            }
        }

        return properties.elements();
    }

    /**
     *  Prepare a bean instance name: make it all lowercase and replace dots
     *  with underscores in fully qualified names.
     */
    private String makeBeanInstanceName(String fqn) {
        StringBuffer instanceName = new StringBuffer(fqn.toLowerCase());
        if(fqn.indexOf('.') > -1) {
            for(int i = 0; i<instanceName.length(); i++) {
                if(instanceName.charAt(i) == '.') {
                    instanceName.setCharAt(i, '_');
                }
            }
        }

        return instanceName.toString();
    }

    private String getOutputFileForClass(SciClass clazz) {
        return getTagForClass(clazz, FILENAME_TAG);
    }

    private void setOutputFileForClass(SciClass clazz, String fileName) {
        setTagForClass(clazz, FILENAME_TAG, fileName);
    }

    private String getStylesheetForClass(SciClass clazz) {
        return getTagForClass(clazz, STYLESHEET_TAG);
    }

    private void setStylesheetForClass(SciClass clazz, String cssURL) {
        setTagForClass(clazz, STYLESHEET_TAG, cssURL);
    }

    private String getPreincludeForClass(SciClass clazz) {
        return getTagForClass(clazz, PREINCLUDE_TAG);
    }

    private void setPreincludeForClass(SciClass clazz, String preincludeURL) {
        setTagForClass(clazz, PREINCLUDE_TAG, preincludeURL);
    }

    private String getPostincludeForClass(SciClass clazz) {
        return getTagForClass(clazz, POSTINCLUDE_TAG);
    }

    private void setPostincludeForClass(SciClass clazz, String postURL) {
        setTagForClass(clazz, POSTINCLUDE_TAG, postURL);
    }

    private String getTagForClass(SciClass clazz, String tagName) {
        SciTagList tagList = clazz.getTagList();
        return tagList.getTagValue(tagName);
    }

    private void setTagForClass(SciClass clazz, String tagName, String tagValue) {
        SciTagList tagList = clazz.getTagList();
        tagList.setTagValue(tagName, tagValue);
    }

    // -------------------------
    // -- File helper utility --
    // -------------------------

    private String makeDefaultFileName(String className) {
        if (className == null || className.length() == 0) {
            return null;
        }
        String filePath = IdeAccess.getIdeManager().getHomeDirectory() +
            File.separator + "out" + File.separator + JSP + File.separator +
            className.toLowerCase() + "." + JSP;
        return filePath;
    }

    private void mkdirhier(String fileName) throws NullPointerException {
        File target = new File(fileName);
        File path = new File(target.getParent());
        path.mkdirs();
    }

    /**
     *  Get the output writer for given file name. Create all the necessary
     *  directory hierarchy if needed.
     *
     *  @param  fileName the name of the file to be
     *  @return output stream writer
     */
    private OutputStreamWriter getOutputStreamWriter(String fileName) {
        if (fileName == null || fileName.length() == 0) {
            return null;
        }

        mkdirhier(fileName);
        FileOutputStream fos = null;
        try {
            //System.err.println("Gotta make a file: " + new File(fileName));
            fos = new FileOutputStream(new File(fileName));
        }
        catch(IOException ioe) {
            ioe.printStackTrace();
            return null;
        }

        return new OutputStreamWriter(fos);
    }

    protected void initializeInspector() {
        try {
            myUIBuilder = null;
            myUIBuilder = createUIBuilder(true);

            Vector inspectorPropertiesVector = new Vector();

            // Create page
            String pageName1 = "Pattern properties";
            if (myUIBuilder.addInspectorPage(pageName1, PatternUIBuilder.UIPresentation.Table) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }

            // Add  property
            if (myUIBuilder.addPropertyToPage(pageName1, NAME, NAME,
                PatternUIBuilder.PropertyType.String) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(NAME);

            boolean allowInterface = false;
            for(int i = 0; i < myBeanScopeProperties.length; i++) {
                // Add bean scope properties
                if (myUIBuilder.addClassPropertyToPage(pageName1, myBeanScopeProperties[i], myBeanScopeProperties[i],
                    PatternUIBuilder.PropertyType.MultiValue, allowInterface) == null) {
                    throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
                }
                inspectorPropertiesVector.addElement(myBeanScopeProperties[i]);
            }

            // Add OUTPUT_FORMAT property
            if (myUIBuilder.addPropertyToPage(pageName1, OUTPUT_FORMAT, OUTPUT_FORMAT,
                PatternUIBuilder.PropertyType.StringChooser, myOutputFormats) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(OUTPUT_FORMAT);

            // Add  OUTPUT_FILE property
            if (myUIBuilder.addPropertyToPage(pageName1, OUTPUT_FILE, OUTPUT_FILE,
                PatternUIBuilder.PropertyType.FileName) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(OUTPUT_FILE);

            // Add STYLESHEET_URL property
            if (myUIBuilder.addPropertyToPage(pageName1, STYLESHEET_URL, STYLESHEET_URL,
                PatternUIBuilder.PropertyType.FileName) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(STYLESHEET_URL);

            // Add PREINCLUDE_URL property
            if (myUIBuilder.addPropertyToPage(pageName1, PREINCLUDE_URL, PREINCLUDE_URL,
                PatternUIBuilder.PropertyType.FileName) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(PREINCLUDE_URL);

            // Add POSTINCLUDE_URL property
            if (myUIBuilder.addPropertyToPage(pageName1, POSTINCLUDE_URL, POSTINCLUDE_URL,
                PatternUIBuilder.PropertyType.FileName) == null) {
                throw new PatternBaseException(CANNOT_CREATE_PATTERN_UI);
            }
            inspectorPropertiesVector.addElement(POSTINCLUDE_URL);

            myUIBuilder.setCreatedPatternUI();
            myUIBuilder.assignListenedInspectorProperties(inspectorPropertiesVector);
        }
        catch (PatternBaseException ex) {
            myUIBuilder = null;
            getIdeInspector().clear();
            return;
        }
    }

    protected void assignActivityResponse() {
    }

    private PatternUIBuilder myUIBuilder = null;

    // tags used by script
    private static final String FILENAME_TAG    = "jspFileName";
    private static final String STYLESHEET_TAG  = "jspStylesheet";
    private static final String PREINCLUDE_TAG  = "jspPreinclude";
    private static final String POSTINCLUDE_TAG = "jspPostinclude";

    /** property field names */
    private static final String NAME = "Name";
    private static final String SCOPE_SESSION_PROPERTY  = "Session scope bean";
    private static final String SCOPE_REQUEST_PROPERTY  = "Request scope bean";
    private static final String SCOPE_PAGE_PROPERTY     = "Page scope bean";
    private static final String OUTPUT_FILE             = "Output file name";
    private static final String OUTPUT_FORMAT           = "Output file format";
    private static final String PREINCLUDE_URL          = "Pre-include URL";
    private static final String POSTINCLUDE_URL         = "Post-include URL";
    private static final String STYLESHEET_URL          = "Stylesheet URL";

    /** output file formats */
    private static final String[] myOutputFormats = {"HTML", "XML"};

    /** session scope for beans */
    private static final String SCOPE_SESSION = "session";

    /** request scope for beans */
    private static final String SCOPE_REQUEST = "request";

    /** page scope for beans */
    private static final String SCOPE_PAGE = "page";

    /** All the scopes enumerated */
    private static final String[] myBeanScopes = {SCOPE_SESSION, SCOPE_REQUEST, SCOPE_PAGE};

    /** Scope names enumerated */
    private static final String[] myBeanScopeProperties = {SCOPE_SESSION_PROPERTY, SCOPE_REQUEST_PROPERTY, SCOPE_PAGE_PROPERTY};

    /** The class we werk with */
    private SciClass aClass = null;

    /** The HTML helper utility */
    private final HTML myHTML = new HTML();

    private String myStylesheet = null;
    private String myPreinclude = null;
    private String myPostinclude = null;

    private static final String JSP = "jsp";
}



