/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (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.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * 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 com.queplix.core.modules.web;

import com.queplix.core.error.GenericSystemException;
import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.dao.AbstractPropertyFactory;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;

/**
 * <p>Web Helper</p>
 *
 * @author [ALB] Baranov Andrey
 * @version $Revision: 1.3 $ $Date: 2006/07/05 16:54:22 $
 */

public final class WebHelper {

    // ----------------------------------------------------- constants

    // Current class
    private static final Class CLASS = WebHelper.class;

    // Separator between JS group and JS in URL
    private static final String JSG_JS_SEPARATOR = "#";

    // Property file name and property names
    private static final String FILE_NAME = "web.properties";
    private static final String PROP_JS_CORE = "js_core";
    private static final String PROP_JS_PROD_CORE = "js_prod_core";
    private static final String PROP_JS_EXTENSION = "js_extension";
    private static final String PROP_JS_INDEX_EXTENSION = "js_index_extension";
    private static final String PROP_JS_IMPL_DIR = "js_impl_dir";

    private static final String PROP_JS_IE_TYPE = "js_ie_type";
    private static final String PROP_JS_MOZ_TYPE = "js_moz_type";
    private static final String PROP_JS_OPERA_TYPE = "js_opera_type";

    private static final String PROP_JS_SUPPORTED_TYPES = "js_supported_types";

    // Property loading.
    private static final Properties props = new Properties();

    static {
        InputStream is = AbstractPropertyFactory.loadSysPropertiesAsStream(
                CLASS, FILE_NAME);
        try {
            props.load(is);
        } catch (IOException ex) {
            ex.printStackTrace();
            throw new GenericSystemException(ex);
        }
    }

    /**
     * Script core path
     */
    public final static String SCRIPT_CORE = props.getProperty(PROP_JS_CORE);

    /**
     * Script core production path
     */
    public final static String SCRIPT_PROD_CORE = props.getProperty(
            PROP_JS_PROD_CORE);

    /**
     * Script file extension and suffix
     */
    public final static String SCRIPT_EXTENSION = props.getProperty(
            PROP_JS_EXTENSION);
    public final static String SCRIPT_SUFFIX = getScriptSuffix(
            SCRIPT_EXTENSION);

    /**
     * Index file extension and suffix
     */
    public final static String INDEX_EXTENSION = props.getProperty(
            PROP_JS_INDEX_EXTENSION);
    public final static String INDEX_SUFFIX = getScriptSuffix(INDEX_EXTENSION);

    /**
     * Script implementation subfolder name
     */
    public static final String SCRIPT_IMPL_DIR = props.getProperty(
            PROP_JS_IMPL_DIR);

    /**
     * Script IE type and suffix
     */
    public static final String SCRIPT_IE_TYPE = props.getProperty(
            PROP_JS_IE_TYPE);
    public static final String SCRIPT_IE_SUFFIX = getBrowserSuffix(
            SCRIPT_IE_TYPE);

    /**
     * Script Mozilla type and suffix
     */
    public static final String SCRIPT_MOZ_TYPE = props.getProperty(
            PROP_JS_MOZ_TYPE);
    public static final String SCRIPT_MOZ_SUFFIX = getBrowserSuffix(
            SCRIPT_MOZ_TYPE);

    /**
     * Script Opera type and suffix
     */
    public static final String SCRIPT_OPERA_TYPE = props.getProperty(
            PROP_JS_OPERA_TYPE);
    public static final String SCRIPT_OPERA_SUFFIX = getBrowserSuffix(
            SCRIPT_OPERA_TYPE);

    /**
     * Supported Browser type
     */
    public static final List SCRIPT_SUPPORTED_TYPES = parseBroserTypes(
            props.getProperty(PROP_JS_SUPPORTED_TYPES));

    /**
     * All browser types
     */
    public static final String[] SCRIPT_BROWSER_TYPES = new String[]{
            SCRIPT_IE_TYPE,
            SCRIPT_MOZ_TYPE,
            SCRIPT_OPERA_TYPE
    };

    // ----------------------------------------------------- constructor

    private WebHelper() {
    }

    // ----------------------------------------------------- public static methods

    /**
     * Determines browser type by HTTP request <code>req</code>.
     *
     * @param req HTTP request
     * @return browser type
     * @see WebHelper#SCRIPT_BROWSER_TYPES
     */
    public static final String getBrowserType(HttpServletRequest req) {

        String browserType = null;
        String userAgent = req.getHeader("User-Agent");
        if(userAgent != null) {
            userAgent = userAgent.toLowerCase();
            if(userAgent.indexOf("msie") != -1) {
                browserType = SCRIPT_IE_TYPE;
            } else if(userAgent.indexOf("mozilla") != -1) {
                browserType = SCRIPT_MOZ_TYPE;
            }
        }

        if(browserType == null || !isBrowserSupported(browserType)) {
            // if browser type not defined or not supported -
            // return first from supported list
            browserType = (String) SCRIPT_SUPPORTED_TYPES.get(0);
        }

        return browserType;
    }

    /**
     * Checks if <code>browserType</code> currently supported.
     *
     * @param browserType broser type ('ie', 'moz', ..)
     * @return true if supported
     */
    public static final boolean isBrowserSupported(String browserType) {
        if(browserType == null) {
            throw new NullPointerException("Broser type is NULL");
        }
        return SCRIPT_SUPPORTED_TYPES.contains(browserType);
    }

    /**
     * Checks if file <code>f</code> valid Java Script file.
     *
     * @param f given file
     * @return true if file JS
     */
    public static final boolean isJSFile(File f) {
        String path = f.getAbsolutePath();
        return isJSFile(path);
    }

    /**
     * Checks if file path <code>path</code> valid Java Script path.
     *
     * @param path given file path
     * @return true if file JS
     */
    public static final boolean isJSFile(String path) {
        return path.endsWith(SCRIPT_SUFFIX);
    }

    /**
     * Parse URL on some Java Script.
     *
     * @param url given URL
     * @return special structure
     */
    public static final JSURLStructure parseURL(String url) {
        if(url == null) {
            throw new NullPointerException("URL is NULL");
        }

        int pos = url.indexOf(JSG_JS_SEPARATOR);
        String jsgName;
        String jsPath;

        if(pos >= 0) {
            jsgName = url.substring(0, pos);
            jsPath = url.substring(pos + 1);

        } else if(isJSFile(url)) {
            jsgName = null;
            jsPath = url;

        } else {
            jsgName = url;
            jsPath = null;
        }

        return new JSURLStructure(jsgName, jsPath);
    }

    /**
     * Get JS path.
     *
     * @param jsgName     JS group name
     * @param browserType browser type
     * @param jsDir       optional parent dir
     * @return File
     */
    public static final File getJSPath(String jsgName,
                                       String browserType,
                                       String jsDir) {
        if(jsgName == null) {
            throw new NullPointerException("JS group is NULL");
        }
        if(browserType == null) {
            throw new NullPointerException("Browser type is NULL");
        }

        if(jsDir != null) {
            return new File(jsDir,
                    SCRIPT_PROD_CORE + "/" +
                            browserType + "/" +
                            jsgName + SCRIPT_SUFFIX);
        } else {
            return new File(SCRIPT_PROD_CORE + "/" +
                    browserType + "/" +
                    jsgName + SCRIPT_SUFFIX);
        }
    }

    /**
     * Get JS path.
     *
     * @param jsgName not-compiled JS group name
     * @param jsFile  JS file inside JS group
     * @param jsDir   optional parent dir
     * @return File
     */
    public static final File getNCJSPath(String jsgName,
                                         File jsFile,
                                         File jsDir) {
        if(jsgName == null) {
            throw new NullPointerException("JS group is NULL");
        }
        if(jsFile == null) {
            throw new NullPointerException("JS file is NULL");
        }

        if(jsDir != null) {
            File jsGroupDir = new File(jsDir,
                    SCRIPT_PROD_CORE + "/" +
                            jsgName);

            String jsFileUrl = getJSURL(jsFile, jsDir);
            return new File(jsGroupDir, jsFileUrl);

        } else {
            return new File(SCRIPT_PROD_CORE + "/" +
                    jsgName);
        }
    }

    /**
     * Get JS index path.
     *
     * @param jsgName     JS group name
     * @param browserType browser type
     * @param jsDir       optional parent dir
     * @return File
     */
    public static final File getJSIndexPath(String jsgName,
                                            String browserType,
                                            String jsDir) {
        if(jsgName == null) {
            throw new NullPointerException("JS group is NULL");
        }
        if(browserType == null) {
            throw new NullPointerException("Browser type is NULL");
        }

        if(jsDir != null) {
            return new File(jsDir,
                    SCRIPT_PROD_CORE + "/" +
                            browserType + "/" +
                            jsgName + INDEX_SUFFIX);
        } else {
            return new File(SCRIPT_PROD_CORE + "/" +
                    browserType + "/" +
                    jsgName + INDEX_SUFFIX);
        }
    }

    /**
     * Get JS URL.
     *
     * @param jsgName     JS group name
     * @param browserType browser type
     * @return String
     */
    public static final String getJSURL(String jsgName, String browserType) {
        return
                SCRIPT_PROD_CORE + "/" +
                        browserType + "/" +
                        jsgName +
                        SCRIPT_SUFFIX;
    }

    /**
     * Get JS absolute URL.
     *
     * @param jsgName not-compiled JS group name
     * @param jsPath  JS relative path
     * @return String
     */
    public static final String getNCJSURL(String jsgName, String jsPath) {
        return
                SCRIPT_PROD_CORE + "/" +
                        jsgName + "/" +
                        jsPath;
    }

    /**
     * Get JS relative URL.
     *
     * @param jsgName not-compiled JS group name
     * @param jsPath  JS relative path
     * @return String
     */
    public static final String getNCJSRelativeURL(String jsgName,
                                                  String jsPath) {
        // Need to insert SCRIPT_PROD_CORE value into the jsPath string 
        // Example: 
        // ../js/modules/focus/mainmenu.js should become  ../jsclasses/2.6.0.45/all/js/modules/focus/mainmenu.js
        if(jsPath != null) {
            int insertPoint = jsPath.lastIndexOf("../");
            if(insertPoint == -1) {
                return
                        SCRIPT_PROD_CORE + "/" +
                                jsgName + "/" +
                                jsPath;
            } else {
                return
                        jsPath.substring(0, insertPoint + 2) +
                                SCRIPT_PROD_CORE + "/" +
                                jsgName + "/" +
                                jsPath.substring(insertPoint + 3,
                                        jsPath.length());
            }
        } else {
            return
                    SCRIPT_PROD_CORE + "/" +
                            jsgName + "/";
        }
    }

    /**
     * Get JS index file relative URL.
     *
     * @param jsgName     JS group name
     * @param browserType browser type
     * @return String
     */
    public static final String getJSIndexURL(String jsgName,
                                             String browserType) {
        return
                SCRIPT_PROD_CORE + "/" +
                        browserType + "/" +
                        jsgName +
                        INDEX_SUFFIX;
    }

    /**
     * Get JS original URL.
     *
     * @param file  JS file
     * @param jsDir optional parent dir
     * @return String
     */
    public static final String getJSURL(File file, File jsDir) {
        if(file == null) {
            throw new NullPointerException("File is NULL");
        }

        // Get file postfix.
        String filePostfix;
        if(jsDir != null) {
            try {
                String fileName = file.getCanonicalPath();
                String dirPath = jsDir.getCanonicalPath();

                int pos = fileName.indexOf(dirPath);
                if(pos == 0) {
                    filePostfix = fileName.substring(dirPath.length());
                } else {
                    filePostfix = fileName;
                }

            } catch (IOException ex) {
                throw new GenericSystemException(
                        "IO exception: " + ex.getMessage(), ex);
            }

        } else {
            filePostfix = file.toString();
        }

        // Replace file separator.
        String url = filePostfix.replace(File.separatorChar, '/');

        // Ok.
        return url;
    }

    /**
     * Get implementation of the file <code>f</code> for the browser type
     * <code>browserType</code>.
     *
     * @param f           File object
     * @param browserType browser type
     * @return File object
     */
    public static final File getImplementation(File f, String browserType) {

        if(f == null || browserType == null) {
            throw new IllegalStateException();
        }

        // Get impl URL for file name only.
        File dir = f.getParentFile();
        String fileName = f.getName();
        String implFileName = getImplUrl(fileName, browserType);

        // Construct impl file.
        return new File(dir, implFileName);
    }

    /**
     * Get relative implementation URL of the file name <code>fileName</code>
     * for the browser type <code>browserType</code>.
     *
     * @param filePath    path to file
     * @param browserType browser type
     * @return String
     */
    public static final String getImplUrl(String filePath, String browserType) {

        if(filePath == null || browserType == null) {
            throw new IllegalStateException();
        }

        File file = new File(filePath);
        File dir = file.getParentFile();
        String fileName = file.getName();

        int scriptSuffixPos = fileName.indexOf(SCRIPT_SUFFIX);
        if(scriptSuffixPos <= 0) {
            throw new IllegalStateException("Invalid file: " + filePath);
        }

        // Build impl dir object.
        File implDir;
        if(dir == null) {
            implDir = new File(SCRIPT_IMPL_DIR);
        } else {
            implDir = new File(dir, SCRIPT_IMPL_DIR);
        }

        // Construct implementation name.
        String implFileName = fileName.substring(0, scriptSuffixPos) +
                getBrowserSuffix(browserType) + SCRIPT_SUFFIX;

        // Build impl file object.
        File implFile = new File(implDir, implFileName);

        // Replace file separator.
        String implUrl = implFile.toString().replace(File.separatorChar, '/');

        // Ok.
        return implUrl;
    }

    // ----------------------------------------------------- private static methods

    // Get script suffix.

    private static final String getScriptSuffix(String scriptExt) {
        return "." + scriptExt;
    }

    // Get browser suffix.
    private static final String getBrowserSuffix(String browserType) {
        return "." + browserType;
    }

    // Parse coma-separated broser types.
    private static final List parseBroserTypes(String s) {
        List ret = new ArrayList();
        if(StringHelper.isEmpty(s)) {
            return ret;
        }

        StringTokenizer st = new StringTokenizer(s, ",;");
        while(st.hasMoreTokens()) {
            ret.add(st.nextToken().trim());
        }

        return ret;
    }

    // ----------------------------------------------------- inner class

    /**
     * <p>JS URL structure</p>
     *
     * @author [ALB] Baranov Andrey
     * @version $Revision: 1.3 $ $Date: 2006/07/05 16:54:22 $
     */
    public static final class JSURLStructure {
        private String jsgName;
        private String jsPath;

        JSURLStructure(String jsgName, String jsPath) {
            this.jsgName = jsgName;
            this.jsPath = jsPath;
        }

        public String getJsgName() {
            return jsgName;
        }

        public String getJsPath() {
            return jsPath;
        }
    }
}
