/*
 * Copyright 1999,2004 The Apache Software Foundation.
 *
 * 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.modeshape.webdav.methods;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.modeshape.common.i18n.TextI18n;
import org.modeshape.webdav.IMimeTyper;
import org.modeshape.webdav.ITransaction;
import org.modeshape.webdav.IWebdavStore;
import org.modeshape.webdav.StoredObject;
import org.modeshape.webdav.WebdavStatus;
import org.modeshape.webdav.locking.ResourceLocks;

public class DoGet extends DoHead {

    public DoGet( IWebdavStore store,
                  String dftIndexFile,
                  String insteadOf404,
                  ResourceLocks resourceLocks,
                  IMimeTyper mimeTyper,
                  int contentLengthHeader ) {
        super(store, dftIndexFile, insteadOf404, resourceLocks, mimeTyper, contentLengthHeader);

    }

    @Override
    protected void doBody( ITransaction transaction,
                           HttpServletResponse resp,
                           String path ) {

        try {
            StoredObject so = store.getStoredObject(transaction, path);
            if (so == null) {
                resp.sendError(HttpServletResponse.SC_NOT_FOUND);
                return;
            }
            if (so.isNullResource()) {
                String methodsAllowed = DeterminableMethod.determineMethodsAllowed(so);
                resp.addHeader("Allow", methodsAllowed);
                resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
                return;
            }
            OutputStream out = resp.getOutputStream();
            InputStream in = store.getResourceContent(transaction, path);
            try {
                int read = -1;
                byte[] copyBuffer = new byte[BUF_SIZE];

                while ((read = in.read(copyBuffer, 0, copyBuffer.length)) != -1) {
                    out.write(copyBuffer, 0, read);
                }
            } finally {
                // flushing causes a IOE if a file is opened on the webserver
                // client disconnected before server finished sending response
                try {
                    in.close();
                } catch (Exception e) {
                    logger.warn(e, new TextI18n("Closing InputStream causes Exception!"));
                }
                try {
                    out.flush();
                    out.close();
                } catch (Exception e) {
                    logger.warn(e, new TextI18n("Flushing OutputStream causes Exception!"));
                }
            }
        } catch (Exception e) {
            logger.trace(e.toString());
        }
    }

    @Override
    protected void folderBody( ITransaction transaction,
                               String path,
                               HttpServletResponse resp,
                               HttpServletRequest req ) throws IOException {

        StoredObject so = store.getStoredObject(transaction, path);
        if (so == null) {
            resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
        } else {

            if (so.isNullResource()) {
                String methodsAllowed = DeterminableMethod.determineMethodsAllowed(so);
                resp.addHeader("Allow", methodsAllowed);
                resp.sendError(WebdavStatus.SC_METHOD_NOT_ALLOWED);
                return;
            }

            if (so.isFolder()) {
                // TODO some folder response (for browsers, DAV tools use propfind) in html?
                DateFormat shortDF = getDateTimeFormat(req.getLocale());
                resp.setContentType("text/html");
                resp.setCharacterEncoding("UTF-8");
                OutputStream out = resp.getOutputStream();
                String[] children = store.getChildrenNames(transaction, path);
                // Make sure it's not null
                children = children == null ? new String[] {} : children;
                // Sort by name
                Arrays.sort(children);
                StringBuilder childrenTemp = new StringBuilder();
                childrenTemp.append("<html><head><title>Content of folder");
                childrenTemp.append(path);
                childrenTemp.append("</title><style type=\"text/css\">");
                childrenTemp.append(getCSS());
                childrenTemp.append("</style></head>");
                childrenTemp.append("<body>");
                childrenTemp.append(getHeader(transaction, path, resp, req));
                childrenTemp.append("<table>");
                childrenTemp.append("<tr><th>Name</th><th>Size</th><th>Created</th><th>Modified</th></tr>");
                childrenTemp.append("<tr>");
                childrenTemp.append("<td colspan=\"4\"><a href=\"../\">Parent</a></td></tr>");
                boolean isEven = false;
                for (String child : children) {
                    isEven = !isEven;
                    childrenTemp.append("<tr class=\"");
                    childrenTemp.append(isEven ? "even" : "odd");
                    childrenTemp.append("\">");
                    childrenTemp.append("<td>");
                    childrenTemp.append("<a href=\"");

                    // CHECKSTYLE IGNORE check FOR NEXT 1 LINES
                    StringBuffer childURL = req.getRequestURL();
                    if (!(childURL.charAt(childURL.length() - 1) == '/')) {
                        childURL.append("/");
                    }

                    // we need to URL encode the child, but just the special chars, UTF-8 encoding is done at the end of this
                    // method
                    childURL.append(URL_ENCODER.encode(child));

                    StoredObject obj = store.getStoredObject(transaction, path + "/" + child);
                    if (obj == null) {
                        logger.error(new TextI18n("Should not return null for " + path + "/" + child));
                    }
                    if (obj != null && obj.isFolder()) {
                        childURL.append("/");
                    }

                    childrenTemp.append(childURL);

                    childrenTemp.append("\">");
                    childrenTemp.append(child);
                    childrenTemp.append("</a></td>");
                    if (obj != null && obj.isFolder()) {
                        childrenTemp.append("<td>Folder</td>");
                    } else {
                        childrenTemp.append("<td>");
                        if (obj != null) {
                            childrenTemp.append(obj.getResourceLength());
                        } else {
                            childrenTemp.append("Unknown");
                        }
                        childrenTemp.append(" Bytes</td>");
                    }
                    if (obj != null && obj.getCreationDate() != null) {
                        childrenTemp.append("<td>");
                        childrenTemp.append(shortDF.format(obj.getCreationDate()));
                        childrenTemp.append("</td>");
                    } else {
                        childrenTemp.append("<td></td>");
                    }
                    if (obj != null && obj.getLastModified() != null) {
                        childrenTemp.append("<td>");
                        childrenTemp.append(shortDF.format(obj.getLastModified()));
                        childrenTemp.append("</td>");
                    } else {
                        childrenTemp.append("<td></td>");
                    }
                    childrenTemp.append("</tr>");
                }
                childrenTemp.append("</table>");
                childrenTemp.append(getFooter(transaction, path, resp, req));
                childrenTemp.append("</body></html>");
                String response = childrenTemp.toString();
                if (logger.isTraceEnabled()) {
                    logger.trace("Sending response {0}", response);
                }
                out.write(response.getBytes("UTF-8"));
            }
        }
    }

    /**
     * Return the CSS styles used to display the HTML representation of the webdav content.
     * 
     * @return the HTML body
     */
    protected String getCSS() {
        // The default styles to use
        String retVal = "body {\n" + "    font-family: Arial, Helvetica, sans-serif;\n" + "}\n" + "h1 {\n" + "    font-size: 1.5em;\n"
                        + "}\n" + "th {\n" + "    background-color: #9DACBF;\n" + "}\n" + "table {\n"
                        + "    border-top-style: solid;\n" + "    border-right-style: solid;\n" + "    border-bottom-style: solid;\n"
                        + "    border-left-style: solid;\n" + "}\n" + "td {\n" + "    margin: 0px;\n" + "    padding-top: 2px;\n"
                        + "    padding-right: 5px;\n" + "    padding-bottom: 2px;\n" + "    padding-left: 5px;\n" + "}\n" + "tr.even {\n"
                        + "    background-color: #CCCCCC;\n" + "}\n" + "tr.odd {\n" + "    background-color: #FFFFFF;\n" + "}\n" + "";
        try {
            // Try loading one via class loader and use that one instead
            ClassLoader cl = getClass().getClassLoader();
            InputStream iStream = cl.getResourceAsStream("webdav.css");
            if (iStream != null) {
                // Found css via class loader, use that one
                StringBuilder out = new StringBuilder();
                byte[] b = new byte[4096];
                for (int n; (n = iStream.read(b)) != -1;) {
                    out.append(new String(b, 0, n));
                }
                retVal = out.toString();
            }
        } catch (Exception ex) {
            logger.error(ex, new TextI18n("Error in reading webdav.css"));
        }

        return retVal;
    }

    /**
     * Return the header to be displayed in front of the folder content
     * 
     * @param transaction
     * @param path
     * @param resp
     * @param req
     * @return the header string
     */
    protected String getHeader( ITransaction transaction,
                                String path,
                                HttpServletResponse resp,
                                HttpServletRequest req ) {
        return "<h1>Content of folder " + path + "</h1>";
    }

    /**
     * Return the footer to be displayed after the folder content
     * 
     * @param transaction
     * @param path
     * @param resp
     * @param req
     * @return the footer string
     */
    protected String getFooter( ITransaction transaction,
                                String path,
                                HttpServletResponse resp,
                                HttpServletRequest req ) {
        return "";
    }

    /**
     * Return this as the Date/Time format for displaying Creation + Modification dates
     * 
     * @param browserLocale
     * @return DateFormat used to display creation and modification dates
     */
    protected DateFormat getDateTimeFormat( Locale browserLocale ) {
        return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, browserLocale);
    }
}
