/*
 * Copyright 2004-2014 ICEsoft Technologies Canada Corp.
 *
 * 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.icefaces.impl.push;

import org.icefaces.application.ResourceRegistry;
import org.icefaces.impl.push.http.DynamicResource;
import org.icefaces.impl.push.http.DynamicResourceLinker;

import javax.faces.application.Resource;
import javax.faces.application.ResourceHandler;
import javax.faces.application.ResourceHandlerWrapper;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * This class is now just a wrapper around org.icefaces.application.ResourceRegistry. In the past there were two separate implementations for
 * creating dynamic resources but DynamicResourceDispatcher was not creating resources with JSF compliant request URLs.
 */
public class DynamicResourceDispatcher extends ResourceHandlerWrapper implements DynamicResourceRegistry {
    private static Logger log = Logger.getLogger("org.icefaces.resourcedispatcher");
    private final static DateFormat DATE_FORMAT = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
    static {
        DATE_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
    }

    private static final DynamicResourceLinker.Handler NOOPHandler = new DynamicResourceLinker.Handler() {
        public void linkWith(DynamicResourceLinker linker) {
            //do nothing!
        }
    };

    private ResourceHandler wrapped;

    public DynamicResourceDispatcher(ResourceHandler wrapped) {
        this.wrapped = wrapped;
        FacesContext facesContext = FacesContext.getCurrentInstance();
        ExternalContext externalContext = facesContext.getExternalContext();
        externalContext.getApplicationMap().put(DynamicResourceDispatcher.class.getName(), this);
    }

    public ResourceHandler getWrapped() {
        return wrapped;
    }

    public URI registerResource(DynamicResource resource) {
        return registerResource(resource, NOOPHandler);
    }

	private String escapeSpecialCharacters(ProxyResource resourceProxy) {
		String originalPath = resourceProxy.getRequestPath();
		String originalFileName = resourceProxy.getResourceName();
		originalFileName = originalFileName != null ? originalFileName : "";

		String escapedFileName = originalFileName;
		try {
			escapedFileName = URLEncoder.encode(escapedFileName, "UTF-8").replaceAll("\\+", "%20");
		} catch (UnsupportedEncodingException e) {
			log.log(Level.FINE, "Unknown encoding", e);
		}

		int fileNameIndex = originalPath.indexOf(originalFileName);
		int fileNameLength = originalFileName.length();

		return originalPath.substring(0, fileNameIndex) + escapedFileName + originalPath.substring(fileNameIndex + fileNameLength);
	}

    public URI registerResource(DynamicResource resource, DynamicResourceLinker.Handler linkerHandler) {
        try {
            ProxyResource resourceProxy = new ProxyResource(resource);
            return new URI(escapeSpecialCharacters(resourceProxy));
        } catch (IOException e) {
            throw new RuntimeException("Failed to create resource.", e);
        } catch (URISyntaxException e) {
            throw new RuntimeException("Failed to create resource.", e);
        }
    }

    private static class ProxyResource extends Resource {
        private DynamicResource resource;
        private HashMap responseHeaders = new HashMap();
        private Date lastModified = new Date();
        private String requestPath;
        private String fileName;
        private boolean attachment;
        private String contentType;

        public ProxyResource(DynamicResource resource) throws IOException {
            this.resource = resource;
            resource.withOptions(new DynamicResource.Options() {
                public void setMimeType(String mimeType) {
                    contentType = mimeType;
                }

                public void setLastModified(Date date) {
                    responseHeaders.put("Last-Modified", DATE_FORMAT.format(lastModified));
                }

                public void setFileName(String name) {
                    fileName = name;
					try {
						fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
					} catch (UnsupportedEncodingException e) {
						log.log(Level.FINE, "Unknown encoding", e);
					}
                    if (attachment) {
                        setAttachmentFileName(fileName);
                    }
                }

                public void setExpiresBy(Date date) {
                    responseHeaders.put("Expires-By", DATE_FORMAT.format(lastModified));
                }

                public void setAsAttachement() {
                    attachment = true;
                    if (fileName != null) {
                        setAttachmentFileName(fileName);
                    }
                }
            });

            requestPath = ResourceRegistry.addSessionResource(this);
        }

        public InputStream getInputStream() throws IOException {
            return resource.open();
        }

        public Map<String, String> getResponseHeaders() {
            return responseHeaders;
        }

        public String getRequestPath() {
            return requestPath;
        }

        public URL getURL() {
            return null;
        }

        public boolean userAgentNeedsUpdate(FacesContext context) {
            return false;
        }

        public String getContentType() {
            return contentType;
        }

        public void setContentType(String contentType) {
        }

        public String getLibraryName() {
            return "";
        }

        public void setLibraryName(String libraryName) {
        }

        public String getResourceName() {
            return fileName;
        }

        public void setResourceName(String resourceName) {
        }

        public void setAttachmentFileName(String fileName) {
            String name = encodeContentDispositionFilename(fileName);
            if (name != null) {
                responseHeaders.put("Content-Disposition", "attachment; filename" + name);
            }
        }
    }

    // Encode filename for Content-Disposition header; to be used in save file dialog;
    // See http://greenbytes.de/tech/tc2231/
    // Some code suggested by Deryk Sinotte
    public static String encodeContentDispositionFilename(String fileName) {
        if (fileName == null || fileName.trim().length() == 0) return null;
        String userAgent = getUserAgent();
        String defaultFileName = "=\"" + fileName + "\"";

        //WebLogic does not provide the user-agent for some reason
        //System.out.println("RegisteredResource.encodeContentDispositionFilename: user-agent = " + userAgent);
        if (userAgent == null || userAgent.trim().length() == 0) {
            return defaultFileName;
        }
        userAgent = userAgent.toLowerCase();
        try {
            if (userAgent.indexOf("msie") > -1) return encodeForIE(fileName);
            if (userAgent.indexOf("firefox") > -1 || userAgent.indexOf("opera") > -1) return encodeForFirefox(fileName);
        } catch (UnsupportedEncodingException e) {
            log.log(Level.FINE, "Unknown encoding", e);
        }

        return defaultFileName;
    }

    // contributed by Robert Vojta
    private static String encodeForIE(String fileName)
            throws UnsupportedEncodingException {
        /*
         * http://greenbytes.de/tech/tc2231/#attwithfnrawpctenca
         *
         * IE decodes %XY to characters and than if it detects
         * UTF-8 stream (after decoding of %XY), than it creates
         * UTF-8 string.
         *
         * We use this behavior to offer correct file name
         * for download.
         */
        StringBuffer encodedFileName = new StringBuffer();
        encodedFileName.append("=\""); // ICEfaces 1.7.2 bug
        encodedFileName.append(fileName);
        encodedFileName.append("\""); // ICEfaces 1.7.2 bug

        return encodedFileName.toString();
    }

    // contributed by Robert Vojta
    private static String encodeForFirefox(String fileName) throws UnsupportedEncodingException {
        //http://greenbytes.de/tech/tc2231/#attwithfn2231utf8
        StringBuffer encodedFileName = new StringBuffer();
        encodedFileName.append("*=UTF-8''");
        encodedFileName.append(fileName);
        return encodedFileName.toString();
    }

    private static String getUserAgent() {
        Map<String, String> headerMap = FacesContext.getCurrentInstance().getExternalContext().getRequestHeaderMap();
        return headerMap.get("user-agent");
    }
}
