/*
 * Copyright (C) 2005-2010 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Alfresco 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see 
Provides the custom I/O control code handling used by the CIFS client interface application.
 * 
 * @author gkspencer
 */
public class ContentIOControlHandler implements IOControlHandler
{
    // Logging
    private static final Log logger = LogFactory.getLog(ContentIOControlHandler.class);
    
    /**
     * Default constructor
     */
    public ContentIOControlHandler()
    {
    }
    
    private CifsHelper cifsHelper;
    private NodeService nodeService;
    private AuthenticationService authService;
    private CheckOutCheckInService checkOutCheckInService;
    private TransactionService transactionService;
    
    
    public void init()
    {
        PropertyCheck.mandatory(this, "nodeService", nodeService);
        PropertyCheck.mandatory(this, "cifsHelper", cifsHelper);
        PropertyCheck.mandatory(this, "authService", authService);
        PropertyCheck.mandatory(this, "checkOutCheckInService", authService);
        PropertyCheck.mandatory(this, "transactionService", getTransactionService());
    }
        
    public final void setNodeService( NodeService nodeService)
    {
        this.nodeService = nodeService;
    }
    
    /**
     * Return the node service
     * 
     * @return NodeService
     */
    public final NodeService getNodeService()
    {
    	return nodeService;
    }
    
    
    /**
     * Process a filesystem I/O control request
     * 
     * @param sess Server session
     * @param tree Tree connection.
     * @param ctrlCode I/O control code
     * @param fid File id
     * @param dataBuf I/O control specific input data
     * @param isFSCtrl true if this is a filesystem control, or false for a device control
     * @param filter if bit0 is set indicates that the control applies to the share root handle
     * @return DataBuffer or null if there is no response buffer.
     * 
     * @exception IOControlNotImplementedException
     * @exception SMBException
     */
    public org.alfresco.jlan.util.DataBuffer processIOControl(SrvSession sess, TreeConnection tree, int ctrlCode, int fid, DataBuffer dataBuf,
            boolean isFSCtrl, int filter, Object contentDriver, ContentContext contentContext)
        throws IOControlNotImplementedException, SMBException
    {
        // Validate the file id
        NetworkFile netFile = tree.findFile(fid);
        if ( netFile == null )
        {
            logger.debug("IO Control Handler called with missing file");
            throw new SMBException(SMBStatus.NTErr, SMBStatus.NTInvalidParameter);
        }
        
        // Split the control code
        
        int devType = NTIOCtl.getDeviceType(ctrlCode);
        int ioFunc  = NTIOCtl.getFunctionCode(ctrlCode);
        
        // Check for I/O controls that require a success status
        
        if ( devType == NTIOCtl.DeviceFileSystem)
        {
	        // I/O control requests that require a success status
	        //
	        // Create or get object id
	        	
	        if ( ioFunc == NTIOCtl.FsCtlCreateOrGetObjectId)
	        {
	            logger.debug("Create or Get Object Id - return null");
	            return null;
//	            
//	            logger.debug("Create or Get Object Id - throw not implemented exception");
//	            throw new IOControlNotImplementedException("Create or Get Object Id not implemented");
//	        	//return null;
	        }
        }
        
        // Check if the I/O control looks like a custom I/O control request
        
        if ( devType != NTIOCtl.DeviceFileSystem || dataBuf == null)
        {
            throw new IOControlNotImplementedException("Custom IO control request not implemented");
        }
        
        // Check if the request has a valid signature for an Alfresco CIFS server I/O control
        
        if ( dataBuf.getLength() < IOControl.Signature.length())
        {
            throw new IOControlNotImplementedException("Bad request length");
        }
        
        String sig = dataBuf.getFixedString(IOControl.Signature.length(), false);
        
        if ( sig == null || sig.compareTo(IOControl.Signature) != 0)
        {
            throw new IOControlNotImplementedException("Bad request signature");
        }
        
        // Get the node for the parent folder, make sure it is a folder
        
        NodeRef folderNode = null;
        
        try
        {
            folderNode = getNodeForPath(tree, netFile.getFullName());
            
            if ( getCifsHelper().isDirectory( folderNode) == false)
            {
                folderNode = null;
            }
        }
        catch ( FileNotFoundException ex)
        {
            folderNode = null;
        }
        // If the folder node is not valid return an error
        
        if ( folderNode == null)
        {
            logger.debug("unable to get parent folder - return access denied");
            throw new SMBException(SMBStatus.NTErr, SMBStatus.NTAccessDenied);
        }
        
        // Debug
        
        if ( logger.isDebugEnabled()) 
        {
            logger.debug("IO control func=0x" + Integer.toHexString(ioFunc) + ", fid=" + fid + ", buffer=" + dataBuf + 
               "  Folder nodeRef=" + folderNode);
        }
        // Check if the I/O control code is one of our custom codes
        DataBuffer retBuffer = null;
        
        switch ( ioFunc)
        {
	        // Probe to check if this is an Alfresco CIFS server
	        
	        case IOControl.CmdProbe:
	            
	            if(logger.isDebugEnabled())
	            {
	                logger.debug("CmdProbe");
	            }
	            
	            // Return a buffer with the signature and protocol version
	            
	            retBuffer = new DataBuffer(IOControl.Signature.length());
	            retBuffer.putFixedString(IOControl.Signature, IOControl.Signature.length());
	            retBuffer.putInt(DesktopAction.StsSuccess);
	            retBuffer.putInt(IOControl.Version);
	            break;
	            
	        // Get file information for a file within the current folder
	            
	        case IOControl.CmdFileStatus:
	            
	            if(logger.isDebugEnabled())
	            {
	               logger.debug("CmdFileStatus");
	            }
	            // Process the file status request
	            
	            retBuffer = procIOFileStatus( sess, tree, dataBuf, folderNode, contentDriver, contentContext);
	            break;
	
	        // Get action information for the specified executable path
	            
	        case IOControl.CmdGetActionInfo:
	        	
	        	// Process the get action information request
	            if(logger.isDebugEnabled())
	            {
	                logger.debug("GetActionInfo");
	            }
	        	
	        	retBuffer = procGetActionInfo(sess, tree, dataBuf, folderNode, netFile, contentDriver, contentContext);
	        	break;
	        	
	        // Run the named action
	        	
	        case IOControl.CmdRunAction:
	
	        	// Process the run action request
	            if(logger.isDebugEnabled())
	            {
	                logger.debug("RunAction");
	            }
	        	
	        	retBuffer = procRunActionInTransaction(sess, tree, dataBuf, folderNode, netFile, contentDriver, contentContext);
	        	break;
	        // Return the authentication ticket
	        	
	        case IOControl.CmdGetAuthTicket:
	        	
	        	// Process the get auth ticket request
	            if(logger.isDebugEnabled())
	            {
	                logger.debug("GetAuthTicket");
	            }
	        	
	        	retBuffer = procGetAuthTicket(sess, tree, dataBuf, folderNode, netFile, contentDriver, contentContext);
	        	break;
	        	
	        // Unknown I/O control code
	            
	        default:
	            if(logger.isDebugEnabled())
	            {
	                logger.debug("throwing IOControl not implemented exception :iofunc" + Integer.toHexString(ioFunc));
	            }
	            throw new IOControlNotImplementedException("IO func not implemented" + Integer.toHexString(ioFunc));
        }
        
        // Return the reply buffer, may be null
        
        return retBuffer;
    }
    
    /**
     * Process the file status I/O request
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param reqBuf Request buffer
     * @param folderNode NodeRef of parent folder
     * @return DataBuffer
     */
    private final DataBuffer procIOFileStatus( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, Object contentDriver, AlfrescoContext contentContext)
    {
        // Start a transaction
        if(contentDriver instanceof TransactionalFilesystemInterface)
        {
            TransactionalFilesystemInterface tx = (TransactionalFilesystemInterface)contentDriver;
            tx.beginReadTransaction( sess);
        }
        
        // Get the file name from the request
        
        String fName = reqBuf.getString( true);
        if ( logger.isDebugEnabled())
        	logger.debug("  File status, fname=" + fName);
        // Create a response buffer
        
        DataBuffer respBuf = new DataBuffer(256);
        respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length());
        
        // Get the node for the file/folder
        
        NodeRef childNode = null;
        
        try
        {
            childNode = getCifsHelper().getNodeRef( folderNode, fName);
        }
        catch (FileNotFoundException ex)
        {
        }
        // Check if the file/folder was found
        
        if ( childNode == null)
        {
            // Return an error response
            if(logger.isDebugEnabled())
            {
                logger.debug("FileStatusProbe StsFileNotFound");
            }
            
            respBuf.putInt(DesktopAction.StsFileNotFound);
            return respBuf;
        }
        // Check if this is a file or folder node
        
        if ( getCifsHelper().isDirectory( childNode))
        {
            // Only return the status and node type for folders
            if(logger.isDebugEnabled())
            {
                logger.debug("FileStatusProbe StsSuccess type folder");
            }
            
            respBuf.putInt(DesktopAction.StsSuccess);
            respBuf.putInt(IOControl.TypeFolder);
        }
        else
        {
            // Indicate that this is a file node
            
            if(logger.isDebugEnabled())
            {
                logger.debug("FileStatusProbe StsSuccess type file");
            }
            
            respBuf.putInt(DesktopAction.StsSuccess);
            respBuf.putInt(IOControl.TypeFile);
            // Check if this file is a working copy
            
            if ( getNodeService().hasAspect( childNode, ContentModel.ASPECT_WORKING_COPY))
            {
                // Indicate that this is a working copy
                
                respBuf.putInt(IOControl.True);
                
                // Get the owner username and file it was copied from
                
                String owner = (String) getNodeService().getProperty( childNode, ContentModel.PROP_WORKING_COPY_OWNER);
                String copiedFrom = null;
                
                // Get the path of the file the working copy was generated from
                NodeRef fromNode = getCheckOutCheckInService().getCheckedOut(childNode);
                if ( fromNode != null)
                    copiedFrom = (String) getNodeService().getProperty( fromNode, ContentModel.PROP_NAME);
                
                // Pack the owner and copied from values
                
                respBuf.putString(owner != null ? owner : "", true, true);
                respBuf.putString(copiedFrom != null ? copiedFrom : "", true, true);
            }
            else
            {
                // Not a working copy
                
                respBuf.putInt(IOControl.False);
            }
            
            // Check the lock status of the file
            
            if ( getNodeService().hasAspect( childNode, ContentModel.ASPECT_LOCKABLE))
            {
                // Get the lock type and owner
                
                String lockTypeStr = (String) getNodeService().getProperty( childNode, ContentModel.PROP_LOCK_TYPE);
                String lockOwner = null;
                
                if ( lockTypeStr != null)
                    lockOwner = (String) getNodeService().getProperty( childNode, ContentModel.PROP_LOCK_OWNER);
                
                // Pack the lock type, and owner if there is a lock on the file
                
                if ( lockTypeStr == null)
                    respBuf.putInt(IOControl.LockNone);
                else
                {
                    LockType lockType = LockType.valueOf( lockTypeStr);
                    
                    respBuf.putInt(lockType == LockType.READ_ONLY_LOCK ? IOControl.LockRead : IOControl.LockWrite);
                    respBuf.putString(lockOwner != null ? lockOwner : "", true, true);
                }
            }
            else
            {
                // File is not lockable
                
                respBuf.putInt(IOControl.LockNone);
            }
            
            // Get the content data details for the file
            
            ContentData contentData = (ContentData) getNodeService().getProperty( childNode, ContentModel.PROP_CONTENT);
            
            if ( contentData != null)
            {
                // Get the content mime-type
                
                String mimeType = contentData.getMimetype();
                
                // Pack the content length and mime-type
                respBuf.putInt( IOControl.True);
                respBuf.putLong( contentData.getSize());
                respBuf.putString( mimeType != null ? mimeType : "", true, true);
            }
            else
            {
                // File does not have any content
                
                respBuf.putInt( IOControl.False);
            }
        }
        
        // Return the response
        
        return respBuf;
    }
    /**
     * Process the get action information request
     * 
     * @param sess Server session
     * @param tree Tree connection
     * @param reqBuf Request buffer
     * @param folderNode NodeRef of parent folder
     * @param netFile NetworkFile for the folder
     * @return DataBuffer
     */
    private final DataBuffer procGetActionInfo( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode,
            NetworkFile netFile, Object contentDriver, AlfrescoContext contentContext)
    {
        // Get the executable file name from the request
        
        String exeName = reqBuf.getString( true);
        if ( logger.isDebugEnabled())
        	logger.debug("  Get action info, exe=" + exeName);
        // Create a response buffer
        
        DataBuffer respBuf = new DataBuffer(256);
        respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length());
        
        // Get the desktop actions list
        
        DesktopActionTable deskActions = contentContext.getDesktopActions();
        if ( deskActions == null)
        {
        	respBuf.putInt(DesktopAction.StsNoSuchAction);
        	return respBuf;
        }
        
        // Convert the executable name to an action name
        
        DesktopAction deskAction = deskActions.getActionViaPseudoName(exeName);
        if ( deskAction == null)
        {
        	respBuf.putInt(DesktopAction.StsNoSuchAction);
        	return respBuf;
        }
        
        // Return the desktop action details
        
        respBuf.putInt(DesktopAction.StsSuccess);
        respBuf.putString(deskAction.getName(), true);
        respBuf.putInt(deskAction.getAttributes());
        respBuf.putInt(deskAction.getPreProcessActions());
        
        String confirmStr = deskAction.getConfirmationString();
        respBuf.putString(confirmStr != null ? confirmStr : "", true);
        
        // Return the response
        
        return respBuf;
    }
    
    private final DataBuffer procRunActionInTransaction( final SrvSession sess, final TreeConnection tree, final DataBuffer reqBuf, final NodeRef folderNode,
            final NetworkFile netFile, final Object contentDriver, final ContentContext contentContext)
    {
    
        RetryingTransactionHelper helper = transactionService.getRetryingTransactionHelper();
        
        RetryingTransactionCallback