/*
 * Copyright (C) 2005-2007 Alfresco Software Limited.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * As a special exception to the terms and conditions of version 2.0 of
 * the GPL, you may redistribute this Program in connection with Free/Libre
 * and Open Source Software ("FLOSS") applications as described in Alfresco's
 * FLOSS exception.  You should have recieved a copy of the text describing
 * the FLOSS exception, and it is also available here:
 * http://www.alfresco.com/legal/licensing" */

package org.alfresco.filesys.repo.desk;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import org.alfresco.config.ConfigElement;
import org.alfresco.filesys.alfresco.DesktopAction;
import org.alfresco.filesys.alfresco.DesktopActionException;
import org.alfresco.filesys.alfresco.DesktopParams;
import org.alfresco.filesys.alfresco.DesktopResponse;
import org.alfresco.jlan.server.filesys.DiskSharedDevice;
import org.alfresco.service.cmr.repository.ScriptException;
import org.alfresco.service.cmr.repository.ScriptService;
import org.alfresco.service.transaction.TransactionService;

/**
 * Javascript Desktop Action Class
 *
 * <p>Run a server-side script against the target node(s).
 * 
 * @author gkspencer
 */
public class JavaScriptDesktopAction extends DesktopAction {

	// Script service
	
	private ScriptService m_scriptService;
	
	// Script name
	
	private String m_scriptName;
	
	// Script file details
	
	private String m_scriptPath;
	private long m_lastModified;
	
	// Script string
	
	private String m_script;
	
	/**
	 * Class constructor
	 */
	public JavaScriptDesktopAction()
	{
		super( 0, 0);
	}

	/**
	 * Return the confirmation string to be displayed by the client
	 * 
	 * @return String
	 */
	@Override
	public String getConfirmationString()
	{
		return "Run Javascript action";
	}

	/**
	 * Initialize the action
	 * 
	 * @param global ConfigElement
	 * @param config ConfigElement
	 * @param fileSys DiskSharedDevice
	 * @exception DesktopActionException
	 */
	@Override
	public void initializeAction(ConfigElement global, ConfigElement config, DiskSharedDevice fileSys)
		throws DesktopActionException
	{

		// Perform standard initialization
		
		super.initializeAction(global, config, fileSys);
		
		// Get the script file name and check that it exists
		
		ConfigElement elem = config.getChild("script");
		if ( elem != null && elem.getValue().length() > 0)
		{
			// Set the script name
			
			setScriptName(elem.getValue());
			
			// Check if the script exists on the classpath
			
	        URL scriptURL = this.getClass().getClassLoader().getResource(getScriptName());
	        if ( scriptURL == null)
	            throw new DesktopActionException("Failed to find script on classpath, " + getScriptName());

	        // Decode the URL path, it might contain escaped characters
	        
	        String scriptURLPath = null;
	        try
	        {
	        	scriptURLPath = URLDecoder.decode( scriptURL.getFile(), "UTF-8");
	        }
	        catch ( UnsupportedEncodingException ex)
	        {
	        	throw new DesktopActionException("Failed to decode script path, " + ex.getMessage());
	        }

	        // Check that the script file exists
	        
	        File scriptFile = new File(scriptURLPath);
	        if ( scriptFile.exists() == false)
	            throw new DesktopActionException("Script file not found, " + elem.getValue());
	        
	        m_scriptPath = scriptFile.getAbsolutePath();
	        m_lastModified =scriptFile.lastModified();
	        
	        // Load the script

	        try
	        {
	        	loadScript( scriptFile);
	        }
	        catch ( IOException ex)
	        {
	        	throw new DesktopActionException( "Failed to load script, " + ex.getMessage());
	        }
		}
		else
			throw new DesktopActionException("Script name not specified");
		
		// check if the desktop action attributes have been specified
		
		elem = config.getChild("attributes");
		if ( elem != null)
		{
			// Check if the attribute string is empty
			
			if ( elem.getValue().length() == 0)
				throw new DesktopActionException("Empty desktop action attributes");
			
			// Parse the attribute string
			
			int attr = 0;
			StringTokenizer tokens = new StringTokenizer( elem.getValue(), ",");
			
			while ( tokens.hasMoreTokens())
			{
				// Get the current attribute token and validate
				
				String token = tokens.nextToken().trim();
				
				if ( token.equalsIgnoreCase( "targetFiles"))
					attr |= AttrTargetFiles;
				else if ( token.equalsIgnoreCase( "targetFolders"))
					attr |= AttrTargetFolders;
				else if ( token.equalsIgnoreCase( "clientFiles"))
					attr |= AttrClientFiles;
				else if ( token.equalsIgnoreCase( "clientFolders"))
					attr |= AttrClientFolders;
				else if ( token.equalsIgnoreCase( "alfrescoFiles"))
					attr |= AttrAlfrescoFiles;
				else if ( token.equalsIgnoreCase( "alfrescoFolders"))
					attr |= AttrAlfrescoFolders;
				else if ( token.equalsIgnoreCase( "multiplePaths"))
					attr |= AttrMultiplePaths;
				else if ( token.equalsIgnoreCase( "allowNoParams"))
					attr |= AttrAllowNoParams;
				else if ( token.equalsIgnoreCase( "anyFiles"))
					attr |= AttrAnyFiles;
				else if ( token.equalsIgnoreCase( "anyFolders"))
					attr |= AttrAnyFolders;
				else if ( token.equalsIgnoreCase( "anyFilesFolders"))
					attr |= AttrAnyFilesFolders;
				else
					throw new DesktopActionException("Unknown attribute, " + token);
			}

			// Set the action attributes
			
			setAttributes( attr);
		}
		
		// Check if the desktop action pre-processing options have been specified
		
		elem = config.getChild("preprocess");
		if ( elem != null)
		{
			// Check if the pre-process string is empty
			
			int pre = 0;
			
			if ( elem.getValue() != null && elem.getValue().length() > 0)
			{
				// Parse the pre-process string
				
				StringTokenizer tokens = new StringTokenizer( elem.getValue(), ",");
				
				while ( tokens.hasMoreTokens())
				{
					// Get the current pre-process token and validate
					
					String token = tokens.nextToken().trim();
					
					if ( token.equalsIgnoreCase( "copyToTarget"))
						pre |= PreCopyToTarget;
					else if ( token.equalsIgnoreCase( "confirm"))
						pre |= PreConfirmAction;
					else if ( token.equalsIgnoreCase( "localToWorkingCopy"))
						pre |= PreLocalToWorkingCopy;
					else
						throw new DesktopActionException("Unknown pre-processing flag, " + token);
				}
			}
			
			// Set the action pre-processing flags
			
			setPreProcessActions( pre);
		}
	}

	/**
	 * Run the desktop action
	 * 
	 * @param params DesktopParams
	 * @return DesktopResponse 
	 */
	@Override
	public DesktopResponse runAction(DesktopParams params)
		throws DesktopActionException
	{
		// Check if the script file has been changed
		
		DesktopResponse response = new DesktopResponse(StsSuccess);
		
		File scriptFile = new File(m_scriptPath);
		if ( scriptFile.lastModified() != m_lastModified)
		{
			// Reload the script

			m_lastModified = scriptFile.lastModified();
			
			try
			{
				loadScript( scriptFile);
			}
			catch ( IOException ex)
			{
				response.setStatus(StsError, "Failed to reload script file, " + getScriptName());
				return response;
			}
		}
			
		// Start a transaction
		
		params.getDriver().beginWriteTransaction( params.getSession());

		// Access the script service
		
		if ( getScriptService() != null)
		{
			// Create the objects to be passed to the script
			
            Map<String, Object> model = new HashMap<String, Object>();
            model.put("deskParams", params);
            model.put("out", System.out);
			
            // Add the webapp URL, if valid
            
            if ( hasWebappURL())
            	model.put("webURL", getWebappURL());
            
            // Start a transaction
            
    		params.getDriver().beginWriteTransaction( params.getSession());

    		// Run the script
    		
    		Object result = null;
    		
    		try
    		{
    			// Run the script
    			
    			result = getScriptService().executeScriptString( getScript(), model);
    			
    			// Check the result
    			
    			if ( result != null)
    			{
    				// Check for a full response object
    				
    				if ( result instanceof DesktopResponse)
    				{
    					response = (DesktopResponse) result;
    				}
    				
    				// Status code only response
    				
    				else if ( result instanceof Double)
    				{
    					Double jsSts = (Double) result;
    					response.setStatus( jsSts.intValue(), "");
    				}
    				
    				// Encoded response in the format '<stsCode>,<stsMessage>'
    				
    				else if ( result instanceof String)
    				{
    					String responseMsg = (String) result;
    					
    					// Parse the status message
    					
    					StringTokenizer token = new StringTokenizer( responseMsg, ",");
    					String stsToken = token.nextToken();
    					String msgToken = token.nextToken();
    					
    					int sts = -1;
    					try
    					{
    						sts = Integer.parseInt( stsToken);
    					}
    					catch ( NumberFormatException ex)
    					{
    						response.setStatus( StsError, "Bad response from script");
    					}
    					
    					// Set the response
    					
    					response.setStatus( sts, msgToken != null ? msgToken : "");
    				}
    			}
    		}
    		catch (ScriptException ex)
    		{
    			// Set the error response for the client
    			
    			response.setStatus( StsError, ex.getMessage());
    		}
		}
		else
		{
			// Return an error response, script service not available
			
			response.setStatus( StsError, "Script service not available");
		}
		
		// Return the response
		
		return response;
	}
	
	/**
	 * Get the script service
	 * 
	 * @return ScriptService
	 */
	protected final ScriptService getScriptService()
	{
		// Check if the script service has been initialized
		
		if ( m_scriptService == null)
		{
			// Get the script service
			
			m_scriptService = getServiceRegistry().getScriptService();
		}
		
		// Return the script service
		
		return m_scriptService;
	}
	
	/**
	 * Get the script name
	 * 
	 * @return String
	 */
	public final String getScriptName()
	{
		return m_scriptName;
	}

	/**
	 * Return the script data
	 * 
	 * @return String
	 */
	public final String getScript()
	{
		return m_script;
	}
	
	/**
	 * Set the script name
	 * 
	 * @param name String
	 */
	protected final void setScriptName(String name)
	{
		m_scriptName = name;
	}
	
	/**
	 * Load, or reload, the script
	 * 
	 * @param scriptFile File
	 */
	private final void loadScript(File scriptFile)
		throws IOException
	{
		// Open the script file
		
		BufferedReader scriptIn = new BufferedReader(new FileReader( scriptFile));
		StringBuilder scriptStr = new StringBuilder((int) scriptFile.length() + 256);
		
		String inRec = scriptIn.readLine();
		
		while ( inRec != null)
		{
			scriptStr.append( inRec);
			scriptStr.append( "\n");
			inRec = scriptIn.readLine();
		}
		
		// Close the script file
		
		scriptIn.close();
		
		// Update the script string
		
		m_script = scriptStr.toString();
	}
}