/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed under the Apache License, Version 2.0 (the \"License\");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an \"AS IS\" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

====================================================================== */
package org.bodington.spring;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.bodington.server.BuildingServerException;
import org.bodington.server.BuildingSession;
import org.bodington.server.PermissionDeniedException;
import org.bodington.server.UploadedFileSession;
import org.bodington.server.resources.UploadedFileSummary;
import org.bodington.servlet.Request;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValues;
import org.springframework.validation.BindException;
import org.springframework.validation.DataBinder;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.AbstractCommandController;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

/**
 * A rather evil hack at a FCKEditor connector.
 * @author buckett
 */
public class ConnectorController extends AbstractCommandController
{
    private static Logger log = Logger.getLogger(ConnectorController.class);
    private TransformerFactory transformerFactory;
    private DocumentBuilderFactory documentFactory;
    private String uploadedView;

    public String getUploadedView()
    {
        return uploadedView;
    }

    public void setUploadedView(String uploadedView)
    {
        this.uploadedView = uploadedView;
    }

    public ConnectorController()
    {
        setCommandClass(ConnectorCommand.class);
        documentFactory = DocumentBuilderFactory.newInstance();
        transformerFactory = TransformerFactory.newInstance();
    }

    public void onBind(HttpServletRequest request, Object command)
        throws Exception
    {
        new ConnectorBinder(request).bind(command);
    }

    protected ModelAndView handle(HttpServletRequest request,
        HttpServletResponse response, Object command, BindException errors)
        throws Exception
    {

        ConnectorCommand connectorCommand = (ConnectorCommand) command;
        BuildingSession session = Utils.getSession(request);
        UploadedFileSession fileSession = session.getUploadedFileSession();

        Document document = documentFactory.newDocumentBuilder().newDocument();
        Element connector = addConnector(connectorCommand, document);
        
        response.setHeader("Cache-Control", "no-cache");

        if (connectorCommand.getCommand().startsWith("GetFolders"))
        {
            UploadedFileSummary requestedFolder = fileSession
                .getFileSummary(connectorCommand.getCurrentFolder());
            UploadedFileSummary[] summaries = fileSession
                .getFileAndDescendentSummaries(connectorCommand
                    .getCurrentFolder(), true);
            List folderList = new ArrayList();
            List fileList = new ArrayList();
            for (int i = 0; i < summaries.length; i++)
            {
                if (summaries[i].getParentUploadedFileId() != null
                    && summaries[i].getParentUploadedFileId().equals(
                        requestedFolder.getPrimaryKey()))
                {
                    if (summaries[i].isFolder())
                        folderList.add(summaries[i]);
                    else
                        fileList.add(summaries[i]);
                }
            }

            addFolders(document, connector, folderList);

            if (connectorCommand.getCommand().endsWith("AndFiles"))
            {
                addFiles(document, connector, fileList);
            }
        }
        else if (connectorCommand.getCommand().startsWith("CreateFolder"))
        {
            String folderName = connectorCommand.getCurrentFolder()
                + connectorCommand.getNewFolderName();
            UploadedFileSummary folder = fileSession.getFileSummary(folderName);
            if (folder == null)
            {
                try
                {
                    fileSession.createFolder(folderName);
                    addError(document, connector, 0);
                }
                catch (PermissionDeniedException pde)
                {
                    addError(document, connector, 103);
                }
                catch (BuildingServerException bse)
                {
                    addError(document, connector, 110);
                }

            }
            else
            {
                addError(document, connector, 101);
            }
        }
        else if (connectorCommand.getCommand().startsWith("FileUpload"))
        {
            Request breq = (Request) request;
            String folder = connectorCommand.getCurrentFolder();
            String fileName = breq.getParameterFileName("NewFile");
            String fileLocation = breq.getParameterFileLocation("NewFile");

            ModelAndView modelView = new ModelAndView(getUploadedView());

            try
            {
                fileSession.transferFile(fileLocation, folder + fileName, null);
                modelView.addObject("error", "0");
            }
            catch (PermissionDeniedException pde)
            {
                modelView.addObject("error", "1");
                modelView.addObject("errorMsg", "You do not have permission.");
            }
            catch (BuildingServerException bse)
            {
                log.error(bse);
                modelView.addObject("error", "1");
                modelView.addObject("errorMsg", "Upload failed: "
                    + bse.getMessage());
            }
            return modelView;
        }

        response.setContentType("text/xml");
        Transformer transformer = transformerFactory.newTransformer();
        transformer.transform(new DOMSource(document), new StreamResult(
            response.getOutputStream()));
        response.getOutputStream().flush();
        return null;

    }

    private Element addConnector(ConnectorCommand connectorCommand, Document document)
    {
        Element connector = document.createElement("Connector");
        connector.setAttribute("command", connectorCommand.getCommand());
        connector.setAttribute("resourceType", connectorCommand.getType());

        Element currentFolder = document.createElement("CurrentFolder");
        currentFolder.setAttribute("path", connectorCommand.getCurrentFolder());
        // Make all the outputted URLs relative.
        currentFolder.setAttribute("url", connectorCommand.getCurrentFolder()
            .substring(1));

        document.appendChild(connector);
        connector.appendChild(currentFolder);
        return connector;
    }

    private void addFiles(Document document, Element connector, List fileList)
    {
        Element files = document.createElement("Files");
        connector.appendChild(files);
        for (Iterator it = fileList.iterator(); it.hasNext();)
        {
            UploadedFileSummary fileSummary = (UploadedFileSummary) it.next();
            Element file = document.createElement("File");
            file.setAttribute("name", fileSummary.getName());
            file.setAttribute("size", Long
                .toString(fileSummary.getSize() / 1024));
            files.appendChild(file);
        }
    }

    private void addFolders(Document document, Element connector,
        List folderList)
    {
        Element folders = document.createElement("Folders");
        connector.appendChild(folders);

        for (Iterator it = folderList.iterator(); it.hasNext();)
        {
            Element folder = document.createElement("Folder");
            folder.setAttribute("name", ((UploadedFileSummary) it.next())
                .getName());
            folders.appendChild(folder);
        }
    }

    private void addError(Document document, Element connector, int i)
    {
        Element error = document.createElement("Error");
        error.setAttribute("number", Integer.toString(i));
        connector.appendChild(error);
    }

    /**
     * Lowercases all the keys before binding is done.
     * @author buckett
     */
    public static class ConnectorBinder
    {
        private Map values = new HashMap();

        public ConnectorBinder(HttpServletRequest request)
        {
            for (Enumeration en = request.getParameterNames(); en
                .hasMoreElements();)
            {
                String key = (String) en.nextElement();
                String newKey = key.substring(0, 1).toLowerCase()
                    + key.substring(1);
                values.put(newKey, request.getParameter(key));
            }
        }

        public void bind(Object command)
        {

            PropertyValues pv = new MutablePropertyValues(values);
            DataBinder binder = new DataBinder(command, null);
            binder.bind(pv);
        }

    }

    public static class ConnectorCommand
    {
        private String command;
        private String type;
        private String currentFolder;
        private String newFolderName;
        private String newFile;

        /**
         * @param command the command to set
         */
        public void setCommand(String command)
        {
            this.command = command;
        }

        /**
         * @return the command
         */
        public String getCommand()
        {
            return command;
        }

        /**
         * @param type the type to set
         */
        public void setType(String type)
        {
            this.type = type;
        }

        /**
         * @return the type
         */
        public String getType()
        {
            return type;
        }

        /**
         * @param currentFolder the currentFolder to set
         */
        public void setCurrentFolder(String currentFolder)
        {
            this.currentFolder = currentFolder;
        }

        /**
         * @return the currentFolder
         */
        public String getCurrentFolder()
        {
            return currentFolder;
        }

        public String getNewFile()
        {
            return newFile;
        }

        public void setNewFile(String newFile)
        {
            this.newFile = newFile;
        }

        public String getNewFolderName()
        {
            return newFolderName;
        }

        public void setNewFolderName(String newFolder)
        {
            this.newFolderName = newFolder;
        }

    }

}
