/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" and
 *     "Apache Jetspeed" must not be used to endorse or promote products
 *    derived from this software without prior written permission. For
 *    written permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache" or
 *    "Apache Jetspeed", nor may "Apache" appear in their name, without
 *    prior written permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.apache.jetspeed.services.portaltoolkit;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import junit.framework.Test;
import junit.framework.TestSuite;

import org.apache.commons.lang.exception.NestableException;
import org.apache.jetspeed.om.profile.IdentityElement;
import org.apache.jetspeed.om.profile.PSMLDocument;
import org.apache.jetspeed.om.profile.Portlets;
import org.apache.jetspeed.om.profile.Skin;
import org.apache.jetspeed.om.profile.psml.PsmlEntry;
import org.apache.jetspeed.om.profile.psml.PsmlSkin;
import org.apache.jetspeed.portal.Portlet;
import org.apache.jetspeed.portal.PortletSet;
import org.apache.jetspeed.services.PortalToolkit;
import org.apache.jetspeed.test.HeadlessBaseTest;
import org.apache.turbine.services.TurbineServices;
import org.apache.turbine.services.resources.ResourceService;

/**
 * @author <a href="mailto:sweaver@rippe.com">Scott Weaver</a>
 *	Tests different use cases of what happens when skins are requested
 *  /applied. 
 *  
 */
public class TestSkinAggregation extends HeadlessBaseTest
{

    public static String SKIN_TEST_PATH = "skin.test";
    public static String SKIN_TEST2_PATH = "skin.test2";
    public static String SKIN_TEST3_PATH = "skin.test3";
    public static String SKIN_TEST4_PATH = "skin.test4";

    private File prof1;
    private File prof2;
    private File prof3;
    private File prof4;


    private String defaultSkin;

    public TestSkinAggregation(String name)
    {
        super(name);
    }

    /**
     * 1) no skin ref in PSML
     * RESULT: uses default skin
     * 
     * This tests to see that, when no skin is defined, the default skin is
     * used.
     */
    public void testCase1()
    {
        printDivider();
        print("Description: No skin set in PSML ");
        print("RESULT: All portlets use the system default skin.");
        print("Checking test case 1...");
        File file = createTestProfile(prof1);
        PSMLDocument doc = getDocumentFromPath(file.getPath());
        Portlets portlets = doc.getPortlets();
        print("Portlets loaded as: " + portlets);

        // checkSkinPresence(portlets, null);

        print("Using the PortalToolkit.  All Portlets and PsmlEntries (individual portlets) ");
        print("should now be using the system default skin, " + this.defaultSkin);

        PortletSet pSet = PortalToolkit.getSet(portlets);
        assertNotNull(pSet);

        // Checking if we are at the root
        isRoot(pSet);

        checkPortletSkinValues(pSet, new HashMap(), doc);

        print("Saving test document...");
        saveDocument(doc);
        printOk();
        print("Re-run check to make sure that the default setting did not get written...");
        PSMLDocument doc2 = getDocumentFromPath(file.getPath());
        Portlets portlets2 = doc.getPortlets();
        PortletSet pSet2 = PortalToolkit.getSet(portlets2);

        checkPortletSkinValues(pSet2, new HashMap(), doc2);

        printDivider();
    }

    /**
     * Checks if the file path of skin.test exists and is accessable
     */
    public void testLoadSkinPath()
    {

        printDivider();
        print("Checking skin test profile...");
        assertTrue(prof1.exists());
        assertTrue(prof2.exists());
        print("Found skin test profile at: " + prof1.getPath());
        printDivider();
    }

    /**
     * 1) no skin ref in PSML
     * RESULT: uses default skin
     * 
     * This tests to see that, when no skin is defined, the default skin is
     * used.
     */
    public void testCase2()
    {
        printDivider();
        print("Description: Skin set at root level, skin set in any children.");
        print("RESULT: All portlets use the root level skin");
        print("Checking test case 2...");
        File file = createTestProfile(prof2);
        PSMLDocument doc = getDocumentFromPath(file.getPath());
        Portlets portlets = doc.getPortlets();
        print("Portlets loaded as: " + portlets);

        print("Save and re-open document as if it were being loaded for the first time...");
        saveDocument(doc);
        // re-load everything...
        doc = null;
        doc = getDocumentFromPath(file.getPath());
        portlets = doc.getPortlets();

        print("Using the PortalToolkit.  All Portlets and PsmlEntries (individual portlets) ");

        PortletSet pSet = PortalToolkit.getSet(portlets);
        assertNotNull(pSet);

        // Checking if we are at the root
        isRoot(pSet);
        HashMap settingsMap = new HashMap();
        settingsMap.put(portlets.getId(), new MatchSettings(false, false, "grey", true));
        settingsMap.put("02", new MatchSettings(false, true, null, false));
        settingsMap.put("03", new MatchSettings(false, true, null, false));
        settingsMap.put("04", new MatchSettings(false, true, null, false));
        settingsMap.put("05", new MatchSettings(false, true, null, false));
        settingsMap.put("06", new MatchSettings(false, true, null, false));
        settingsMap.put("07", new MatchSettings(false, true, null, false));
        settingsMap.put("08", new MatchSettings(false, true, null, false));

        checkPortletSkinValues(pSet, settingsMap, doc);
        printOk();

        printDivider();
    }
    
    
    /**
     * Case 3
      */
    public void testCase3()
    {
        printDivider();
        print("Description: Skin set at top level, and skin set in a child portlet ");
        print("and in a child PortletSet.");
        print("RESULT: Un-assigned portlets get the top skin.  Assigned portlets get there assigned skin. ");
        print("Assigned PortletSets use assigned skin and so do their children.");
        print("");
        print("Checking test case 3...");
        File file = createTestProfile(prof3);
        PSMLDocument doc = getDocumentFromPath(file.getPath());
        Portlets portlets = doc.getPortlets();
        print("Portlets loaded as: " + portlets);

        print("Save and re-open document as if it were being loaded for the first time...");
        saveDocument(doc);
        // re-load everything...
        doc = null;
        doc = getDocumentFromPath(file.getPath());
        portlets = doc.getPortlets();

        print("Using the PortalToolkit.  All Portlets and PsmlEntries (individual portlets) ");

        PortletSet pSet = PortalToolkit.getSet(portlets);
        assertNotNull(pSet);

        // Checking if we are at the root
        isRoot(pSet);
        HashMap settingsMap = new HashMap();
        settingsMap.put("01", new MatchSettings(false, false, "grey", true));
        settingsMap.put("02", new MatchSettings(false, true, null, false));
        settingsMap.put("03", new MatchSettings(false, false, "BorderedTitleStylesOnly", true));
        settingsMap.put("04", new MatchSettings(false, false, "StylesOnly", true));
        settingsMap.put("05", new MatchSettings(false, true, null, false));
        settingsMap.put("06", new MatchSettings(false, true, null, false));
        settingsMap.put("07", new MatchSettings(false, true, null, false));
        settingsMap.put("08", new MatchSettings(false, true, null, false));

        checkPortletSkinValues(pSet, settingsMap, doc);
        printOk();

        printDivider();
    }
    
    /**
     * Case 4
      */
    public void testCase4()
    {
        printDivider();
        print("Description: Skin NOT set at top level, and skin set in a child portlet ");
        print("and in a child PortletSet.");
        print("RESULT: Un-assigned portlets use the system default skin.  Assigned portlets get there assigned skin. ");
        print("Assigned PortletSets use assigned skin and so do their children.");
        print("");
        print("Checking test case 4...");
        File file = createTestProfile(prof4);
        PSMLDocument doc = getDocumentFromPath(file.getPath());
        Portlets portlets = doc.getPortlets();
        print("Portlets loaded as: " + portlets);


        print("Save and re-open document as if it were being loaded for the first time...");
        saveDocument(doc);
        // re-load everything...
        doc = null;
        doc = getDocumentFromPath(file.getPath());
        portlets = doc.getPortlets();

        print("Using the PortalToolkit.  All Portlets and PsmlEntries (individual portlets) ");

        PortletSet pSet = PortalToolkit.getSet(portlets);
        assertNotNull(pSet);

        // Checking if we are at the root
        isRoot(pSet);
        HashMap settingsMap = new HashMap();
        settingsMap.put("01", new MatchSettings(true, false, null, false));
        settingsMap.put("02", new MatchSettings(false, false, "grey", true));
        settingsMap.put("03", new MatchSettings(false, true, null, false));
        settingsMap.put("04", new MatchSettings(false, false, null, false));
        settingsMap.put("05", new MatchSettings(true, true, null, false));
        settingsMap.put("06", new MatchSettings(false, false, "StylesOnly", true));
        settingsMap.put("07", new MatchSettings(true, true, null, false));
        settingsMap.put("08", new MatchSettings(true, true, null, false));

        checkPortletSkinValues(pSet, settingsMap, doc);
        printOk();

        printDivider();
    }
    
     /**
     * Case 5
      */
    public void testCase5()
    {
        printDivider();
        print("Description: Test that, when setting the root skin and saving that no other ");
        print("Portlets get a skin written.");
        print("RESULT: Only the root is assigned the selected skin");

        print("");
        print("Checking test case 5...");
        File file = createTestProfile(prof1);
        PSMLDocument doc = getDocumentFromPath(file.getPath());
        Portlets portlets = doc.getPortlets();
        print("Portlets loaded as: " + portlets);

        print("Directly setting the rootSet to a \"grey\"...");
        Skin rootSkin = new PsmlSkin();
        rootSkin.setName("grey");
        portlets.setSkin(rootSkin);
        print("Save and re-open document as if it were being loaded for the first time...");
        saveDocument(doc);
        // re-load everything...
        doc = null;
        doc = getDocumentFromPath(file.getPath());
        portlets = doc.getPortlets();

        print("Using the PortalToolkit.  All Portlets and PsmlEntries (individual portlets) ");

        PortletSet pSet = PortalToolkit.getSet(portlets);
        assertNotNull(pSet);

        // Checking if we are at the root
        isRoot(pSet);
        HashMap settingsMap = new HashMap();
        settingsMap.put("01", new MatchSettings(true, false, "grey", true));
        
        checkPortletSkinValues(pSet, settingsMap, doc);
        printOk();

        printDivider();
    }

    /**
     * Walks the PSML document tree and checks each Portlet/PortletSet
     * for correct skin settings based on the MatchSettings provided.
     */
    protected void checkPortletSkinValues(PortletSet pSet, HashMap settingsMap, PSMLDocument doc)
    {
        // Check that we are using the default skin as defined by the system

        performCheck(pSet, doc, null, (MatchSettings) settingsMap.get(pSet.getID()), null);

        Enumeration enum = pSet.getPortlets();
        while (enum.hasMoreElements())
        {
            PortletSet subSet = (PortletSet) enum.nextElement();
            performCheck(subSet, doc, pSet, (MatchSettings) settingsMap.get(subSet.getID()), "  |--- ");

            Enumeration singleEnum = subSet.getPortlets();
            print("  |--- Checking individual portlets for PortletSet " + getNameTag(subSet) + "...");
            while (singleEnum.hasMoreElements())
            {
                Portlet portlet = (Portlet) singleEnum.nextElement();
                performCheck(portlet, doc, subSet, (MatchSettings) settingsMap.get(portlet.getID()), "  |    |--- ");
            }
        }
    }

    protected void isRoot(PortletSet pSet)
    {
        print("Checking PortletSet is root...");
        print("PortletSet ID: " + pSet.getID());
        print("PortletSet name: " + pSet.getName());
        assertTrue(pSet.getID().equals("01"));
        assertTrue(pSet.getName().equals("theRootSet"));
        printOk();
    }

    /**
     * Checks for the physical presence of skins.
     * portlet id's within  the list should have skins
     * while those not in the list should NOT have skins.
     * A null list means that ALL skins should be unset.
     * 
     */
    protected void checkSkinPresence(Portlets portlets, List idsWithSkins)
    {

        assertNotNull(portlets);
        printOk();

        print("Verify all skins presence for this profile...");
        if (idsWithSkins == null || !idsWithSkins.contains(portlets.getId()))
        {
            print("Verify that root *DOES NOT* have a skin set...");
            assertNull(portlets.getSkin());
        }
        else
        {
            print("Verify that root *DOES* have a skin set...");
            assertNotNull(portlets.getSkin());
        }
        printOk();

        Iterator itr = portlets.getPortletsIterator();
        while (itr.hasNext())
        {
            Portlets childPortlets = (Portlets) itr.next();
            if (idsWithSkins == null || !idsWithSkins.contains(childPortlets.getId()))
            {
                print("Verify skin *IS NOT* set for Portlets " + getNameTag(childPortlets) + "...");
                assertNull(childPortlets.getSkin());
            }
            else
            {
                print("Verify skin *IS* set for Portlets " + getNameTag(childPortlets) + "...");
                assertNotNull(childPortlets.getSkin());
            }
            printOk();

            Iterator pItr = childPortlets.getEntriesIterator();
            while (pItr.hasNext())
            {
                PsmlEntry entry = (PsmlEntry) pItr.next();

                if (idsWithSkins == null || !idsWithSkins.contains(entry.getId()))
                {
                    print("Verify skin *IS NOT* set for portlet " + getNameTag(entry) + "...");
                    assertNull(entry.getSkin());
                }
                else
                {
                    print("Verify skin *IS* set for portlet " + getNameTag(entry) + "...");
                    assertNotNull(entry.getSkin());
                }
                printOk();
            }
        }
    }

    protected boolean matchesParentSkin(Portlet portlet, Portlet parent)
    {
        String parentSkin = parent.getPortletConfig().getPortletSkin().getName();
        return matchesSkin(portlet, parentSkin);
    }

    protected boolean matchesDefaultSkin(Portlet portlet)
    {
        return matchesSkin(portlet, this.defaultSkin);
    }

    protected boolean matchesSkin(Portlet portlet, String skinName)
    {
        String mySkin = portlet.getPortletConfig().getPortletSkin().getName();
        return mySkin.equals(skinName);
    }

	/**
	 * Performs skin checks for a portlet based on the MatchSettings 
	 * associated with it
	 */
    protected void performCheck(Portlet portlet, PSMLDocument doc, Portlet parent, MatchSettings settings, String linePrefix)
    {

        boolean isRoot = (parent == null);
        if (linePrefix == null)
            linePrefix = "";

        IdentityElement element = doc.getPortletsById(portlet.getID());
        if (element == null)
            element = doc.getEntryById(portlet.getID());

        print(linePrefix + "Verify a corresponding IdentitiyElement for " + getNameTag(portlet));
        assertNotNull(element);
        printOk(linePrefix);

        // automatic settings for a "virgin" skin settings
        if (settings == null)
            settings = new MatchSettings(true, true, null, false);

        if (settings.isSkinPresent())
        {
            print(linePrefix + "Verify skin *IS* set for  IdentitiyElement " + getNameTag(element) + "...");
            assertNotNull(element.getSkin());
            printOk(linePrefix);
        }
        else
        {
            print(linePrefix + "Verify skin *IS NOT* set for  IdentitiyElement " + getNameTag(element) + "...");
            Skin showSkin = element.getSkin();
            assertNull(element.getSkin());
            printOk(linePrefix);
        }

        if (!isRoot && settings.matchParentSkin())
        {
            print(linePrefix + "Verify that Portlet " + getNameTag(portlet) + "'s skin matches it's parent's, " + getNameTag(parent) + " skin...");
            assertNotNull(parent);
            matchesParentSkin(portlet, parent);
            printOk(linePrefix);
        }

        if (settings.matchDefaultSkin())
        {
            print(linePrefix + "Verify that Portlet " + getNameTag(portlet) + "'s skin  matches the default skin...");
            matchesDefaultSkin(portlet);
            printOk(linePrefix);
        }

        if (settings.getSkin() != null)
        {
            print(linePrefix + "Verify that Portlet " + getNameTag(portlet) + "'s skin  uses the skin " + settings.getSkin() + "...");
            matchesSkin(portlet, settings.getSkin());
            printOk(linePrefix);
        }

    }

    /**
    * Creates the test suite.
    *
    * @return a test suite (<code>TestSuite</code>) that includes all methods
    *         starting with "test"
    */
    public static Test suite()
    {
        // All methods starting with "test" will be executed in the test suite.
        return new TestSuite(TestSkinAggregation.class);
    }

    public static void main(String[] args)
    {
        junit.awtui.TestRunner.main(new String[] { TestSkinAggregation.class.getName()});
    }

    protected File createTestProfile(File base)
    {
        File testFile = null;
        BufferedReader reader = null;
        BufferedWriter writer = null;

        try
        {
            String testDir = base.getParent();
            String testFilePath = testDir + File.separator + "working_" + base.getName();
            testFile = new File(testFilePath);
            if (testFile.exists())
                testFile.delete();
            else
                testFile.createNewFile();

            reader = new BufferedReader(new FileReader(base));
            writer = new BufferedWriter(new FileWriter(testFile));
            String line;
            while ((line = reader.readLine()) != null)
            {
                writer.write(line);
            }
            // make sure we write the file
            writer.flush();
        }
        catch (Exception e)
        {
            e.printStackTrace();
            testResult.addError(this, new NestableException("Unable to build workin file for skin test.", e));
        }
        finally
        {
            try
            {
                if (reader != null)
                    reader.close();

                if (writer != null)
                    writer.close();
            }
            catch (IOException e)
            {
                // let it go
            }
        }

        return testFile;

    }

    /**
     * @see junit.framework.TestCase#setUp()
     */
    protected void setUp() throws Exception
    {
        super.setUp();

        ResourceService serviceConf = ((TurbineServices) TurbineServices.getInstance()).getResources(PortalToolkitService.SERVICE_NAME);

        this.defaultSkin = serviceConf.getString("default.skin");
        String path = getTestConfig().getString(SKIN_TEST_PATH);
        String path2 = getTestConfig().getString(SKIN_TEST2_PATH);
        String path3 = getTestConfig().getString(SKIN_TEST3_PATH);
        String path4 = getTestConfig().getString(SKIN_TEST4_PATH);
        prof1 = new File(path);
        prof2 = new File(path2);
        prof3 = new File(path3);
        prof4 = new File(path4);
    }

    protected boolean usesDefaultSkin(Portlet portlet)
    {

        return portlet.getPortletConfig().getPortletSkin().getName().equals(this.defaultSkin);
    }

    protected String getNameTag(Portlet portlet)
    {
        return "Id: " + portlet.getID() + " Name: " + portlet.getName();
    }

    protected String getNameTag(IdentityElement el)
    {
        return "Id: " + el.getId() + " Name: " + el.getName();
    }

	/**
	 * Allows setting of matching criteria for individual portlet
	 * entries
	 */	
    class MatchSettings
    {
        private boolean defaultSkin;
        private boolean parentSkin;
        private String skin;
        private boolean skinPresent;

        public MatchSettings(boolean defaultSkin, boolean parentSkin, String skin, boolean skinPresent)
        {
            this.defaultSkin = defaultSkin;
            this.parentSkin = parentSkin;
            this.skin = skin;
            this.skinPresent = skinPresent;
        }

        /**
         * Returns the defaultSkin.
         * @return boolean
         */
        public boolean matchDefaultSkin()
        {
            return defaultSkin;
        }

        /**
         * Returns the parentSkin.
         * @return boolean
         */
        public boolean matchParentSkin()
        {
            return parentSkin;
        }

        /**
         * Returns the skin.
         * @return String
         */
        public String getSkin()
        {
            return skin;
        }

        /**
         * Sets the defaultSkin.
         * @param defaultSkin The defaultSkin to set
         */
        public void setDefaultSkin(boolean defaultSkin)
        {
            this.defaultSkin = defaultSkin;
        }

        /**
         * Sets the parentSkin.
         * @param parentSkin The parentSkin to set
         */
        public void setParentSkin(boolean parentSkin)
        {
            this.parentSkin = parentSkin;
        }

        /**
         * Sets the skin.
         * @param skin The skin to set
         */
        public void setSkin(String skin)
        {
            this.skin = skin;
        }
        /**
         * Returns the skinPresent.
         * @return boolean
         */
        public boolean isSkinPresent()
        {
            return skinPresent;
        }

        /**
         * Sets the skinPresent. Specifies whether or not this
         * Portlet's PsmlDocument Entry should have a skin
         * assigned to it.
         * @param skinPresent The skinPresent to set
         */
        public void setSkinPresent(boolean skinPresent)
        {
            this.skinPresent = skinPresent;
        }

    }

}
