package org.nutz.mvc;

import java.io.IOException;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.net.URLDecoder;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.nutz.Nutz;
import org.nutz.conf.NutConf;
import org.nutz.ioc.Ioc;
import org.nutz.ioc.IocContext;
import org.nutz.json.Json;
import org.nutz.json.JsonFormat;
import org.nutz.lang.Strings;
import org.nutz.lang.util.Context;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.config.AtMap;
import org.nutz.mvc.i18n.LocalizationManager;
import org.nutz.mvc.impl.NutMessageMap;
import org.nutz.mvc.ioc.SessionIocContext;

/**
 * Mvc 相关帮助函数
 *
 * @author zozoh(zozohtnt@gmail.com)
 * @author wendal(wendal1985@gmail.com)
 */
public abstract class Mvcs {

    // TODO 这个变量应该在 1.b.46 之后的某一个版本删掉
    public static final String DEFAULT_MSGS = "$default";

    public static final String MSG = "msg";
    public static final String LOCALE_KEY = "nutz_mvc_localization_key";

    // PS: 如果这个修改导致异常,请报issue,并将这个变量设置为true
    public static boolean disableFastClassInvoker = false;
    // 实现显示行号, 如果禁用, 轻微加速启动
    public static boolean DISPLAY_METHOD_LINENUMBER = true;
    // 如果一个Resp已经commit过了,那么是否跳过渲染呢
    public static boolean SKIP_COMMITTED = false;

    public static boolean DISABLE_X_POWERED_BY = false;

    public static String X_POWERED_BY = "nutz/"+Nutz.version()+" <nutzam.com>";
    
    public static LocalizationManager localizationManager;
    
    public static void setLocalizationManager(LocalizationManager localizationManager) {
        Mvcs.localizationManager = localizationManager;
    }

    // ====================================================================

    public static Map<String, Object> getLocaleMessage(String local) {
        if (localizationManager != null) {
            return localizationManager.getMessageMap(local);
        }
        else {
            Map<String, Map<String, Object>> msgss = getMessageSet();
            if (null != msgss)
                return msgss.get(local);
            return null;
        }
    }

    /**
     * 获取当前请求对象的字符串表
     *
     * @param req
     *            请求对象
     * @return 字符串表
     */
    @SuppressWarnings("unchecked")
    public static Map<String, String> getMessages(ServletRequest req) {
        return (Map<String, String>) req.getAttribute(MSG);
    }

    /**
     * 获取当前请求对象的字符串表（NutMessageMap 封装）
     *
     * @param req
     *            请求对象
     * @return 字符串表
     */
    public static NutMessageMap getMessageMap(ServletRequest req) {
        return (NutMessageMap) req.getAttribute(MSG);
    }

    /**
     * 获取当前请求对象的字符串表中的某一个字符串
     *
     * @param req
     *            请求对象
     * @param key
     *            字符串键值
     * @return 字符串内容
     */
    public static String getMessage(ServletRequest req, String key) {
        Map<String, String> map = getMessages(req);
        if (null != map)
            return map.get(key);
        return null;
    }

    /**
     * 获取当前会话的本地字符串集合的键值；如果当前 HTTP 会话不存在，则返回 null
     *
     * @return 当前会话的本地字符串集合的键值；如果当前 HTTP 会话不存在，则返回 null
     */
    public static String getLocalizationKey() {
        return (String) getSessionAttrSafe(LOCALE_KEY);
    }

    /**
     * 设置本地化字符串的键值
     * <p>
     * 如果你用的是 Nutz.Mvc 默认的本地化机制，那么你的本地字符串键值，相当于一个你目录名。 <br>
     * 比如 "zh_CN" 等
     *
     * @param key
     *            键值
     * @return 是否设置成功
     */
    public static boolean setLocalizationKey(String key) {
        HttpSession sess = getHttpSession();
        if (null == sess)
            return false;
        sess.setAttribute(LOCALE_KEY, key);
        return true;
    }

    /**
     * 返回当前加载了的本地化字符串的键值
     *
     * @return 当前都加载了哪些种字符串的 KEY
     */
    public static Set<String> getLocalizationKeySet() {
        Map<String, Map<String, Object>> msgss = getMessageSet();
        if (null == msgss)
            return new HashSet<String>();
        return msgss.keySet();
    }

    /**
     * 默认的本地化字符串 KEY，当为 NULL 时，Nutz.Mvc 会随便用一个
     */
    private static String default_localization_key = null;

    /**
     * 设置默认的多国语言
     *
     * @param key
     *            默认的多国语言 KEY
     */
    public static void setDefaultLocalizationKey(String key) {
        default_localization_key = key;
    }

    /**
     * 返回默认的本地化字符串 KEY
     *
     * @return 默认的本地化字符串 KEY
     */
    public static String getDefaultLocalizationKey() {
        return default_localization_key;
    }

    /**
     * 为当前的 HTTP 请求对象设置一些必要的属性。包括：
     * <ul>
     * <li>本地化子字符串 => ${msg}
     * <li>应用的路径名 => ${base}
     * </ul>
     *
     * @param req
     *            HTTP 请求对象
     */
    public static void updateRequestAttributes(HttpServletRequest req) {
        // 初始化本次请求的多国语言字符串
        if (localizationManager == null) {
            Map<String, Map<String, Object>> msgss = getMessageSet();
            if (msgss == null && !ctx().localizations.isEmpty())
                msgss = ctx().localizations.values().iterator().next();
            if (null != msgss) {
                Map<String, Object> msgs = null;

                String lKey = Strings.sBlank(Mvcs.getLocalizationKey(), getDefaultLocalizationKey());

                if (!Strings.isBlank(lKey))
                    msgs = msgss.get(lKey);

                // 没有设定特殊的 Local 名字，随便取一个
                if (null == msgs) {
                    if (msgss.size() > 0)
                        msgs = msgss.values().iterator().next();
                }
                // 记录到请求中
                req.setAttribute(MSG, msgs);
            }
        }
        else {
            String lKey = Mvcs.getLocalizationKey();
            if (Strings.isBlank(lKey)) {
                lKey = localizationManager.getDefaultLocal();
                if (Strings.isBlank(lKey))
                    lKey = getDefaultLocalizationKey();
            }
            NutMessageMap msg = localizationManager.getMessageMap(lKey);
            if (msg != null)
                req.setAttribute(MSG, msg);
        }

        // 记录一些数据到请求对象中
        req.setAttribute("base", req.getContextPath());
        if (NutConf.MVC_ADD_ATTR_$REQUEST)
            req.setAttribute("$request", req);
    }

    /**
     * 获取当前请求的路径，并去掉后缀
     *
     * @param req
     *            HTTP 请求对象
     */
    public static String getRequestPath(HttpServletRequest req) {
        return getRequestPathObject(req).getPath();
    }

    /**
     * 获取当前请求的路径，并去掉后缀
     *
     * @param req
     *            HTTP 请求对象
     */
    public static RequestPath getRequestPathObject(HttpServletRequest req) {
        Object includeUrl = req.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
        if (includeUrl != null) {
            return getRequestPathObject(includeUrl.toString());
        }

        includeUrl = req.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
        if (includeUrl != null) {
            return getRequestPathObject(includeUrl.toString());
        }

        String url = req.getPathInfo();
        if (null == url)
            url = req.getServletPath();
        return getRequestPathObject(url);
    }

    /**
     * 获取当前请求的路径，并去掉后缀
     *
     * @param url
     *            请求路径的URL
     */
    public static RequestPath getRequestPathObject(String url) {
        RequestPath rr = new RequestPath();
        rr.setUrl(url);
        if (null != url) {
            int lio = 0;
            if (!url.endsWith("/")) {
                int ll = url.lastIndexOf('/');
                lio = url.lastIndexOf('.');
                if (lio < ll)
                    lio = -1;
            }
            if (lio > 0) {
                rr.setPath(url.substring(0, lio));
                rr.setSuffix(url.substring(lio + 1));
            } else {
                rr.setPath(url);
                rr.setSuffix("");
            }
        } else {
            rr.setPath("");
            rr.setSuffix("");
        }
        return rr;
    }

    /**
     * 注销当前 HTTP 会话。所有 Ioc 容器存入的对象都会被注销
     *
     * @param session
     *            HTTP 会话对象
     */
    public static void deposeSession(HttpSession session) {
        if (session != null)
            new SessionIocContext(session).depose();
    }

    /**
     * 它将对象序列化成 JSON 字符串，并写入 HTTP 响应
     *
     * @param resp
     *            响应对象
     * @param obj
     *            数据对象
     * @param format
     *            JSON 的格式化方式
     * @throws IOException
     *             写入失败
     */
    public static void write(HttpServletResponse resp, Object obj, JsonFormat format)
            throws IOException {
        write(resp, resp.getWriter(), obj, format);
    }

    public static void write(HttpServletResponse resp, Writer writer, Object obj, JsonFormat format)
            throws IOException {
        resp.setHeader("Cache-Control", "no-cache");
        if (resp.getContentType() == null)
            resp.setContentType("text/plain");

        // by mawm 改为直接采用resp.getWriter()的方式直接输出!
        Json.toJson(writer, obj, format);

        resp.flushBuffer();
    }

    // ==================================================================
    private static final ThreadLocal<String> NAME = new ThreadLocal<String>();

    /**
     * NutMvc的上下文
     */
    @Deprecated
    public static NutMvcContext ctx;

    public static NutMvcContext ctx() {
        ServletContext sc = getServletContext();
        if (sc == null) {
            if (ctx == null)
                ctx = new NutMvcContext();
            return ctx;
        }
        NutMvcContext c = (NutMvcContext) getServletContext().getAttribute("__nutz__mvc__ctx");
        if (c == null) {
            c = new NutMvcContext();
            getServletContext().setAttribute("__nutz__mvc__ctx", c);
            ctx = c;
        }
        return c;
    }

    private static ServletContext def_servletContext;
    private static ThreadLocal<ServletContext> servletContext = new ThreadLocal<ServletContext>();

    /**
     * 获取 HTTP 请求对象
     *
     * @return HTTP 请求对象
     */
    public static final HttpServletRequest getReq() {
        return reqt().getAs(HttpServletRequest.class, "req");
    }

    /**
     * 获取 HTTP 响应对象
     *
     * @return HTTP 响应对象
     */
    public static final HttpServletResponse getResp() {
        return reqt().getAs(HttpServletResponse.class, "resp");
    }

    public static final String getName() {
        return NAME.get();
    }

    /**
     * 获取 Action 执行的上下文
     *
     * @return Action 执行的上下文
     */
    public static final ActionContext getActionContext() {
        return reqt().getAs(ActionContext.class, "ActionContext");
    }

    public static void set(String name, HttpServletRequest req, HttpServletResponse resp) {
        NAME.set(name);
        reqt().set("req", req);
        reqt().set("resp", resp);
    }

    /**
     * 设置 Servlet 执行的上下文
     *
     * @param servletContext
     *            Servlet 执行的上下文
     */
    public static void setServletContext(ServletContext servletContext) {
        if (servletContext == null) {
            Mvcs.servletContext.remove();
        }
        if (def_servletContext == null)
            def_servletContext = servletContext;
        Mvcs.servletContext.set(servletContext);
    }

    /**
     * 设置 Action 执行的上下文
     *
     * @param actionContext
     *            Action 执行的上下文
     */
    public static void setActionContext(ActionContext actionContext) {
        reqt().set("ActionContext", actionContext);
    }

    /**
     * 获取 Servlet 执行的上下文
     *
     * @return Servlet 执行的上下文
     */
    public static ServletContext getServletContext() {
        ServletContext cnt = servletContext.get();
        if (cnt != null)
            return cnt;
        return def_servletContext;
    }

    /**
     * 设置对象装配的上下文环境
     *
     * @param iocContext
     *            对象装配的上下文环境
     */
    public static void setIocContext(IocContext iocContext) {
        reqt().set("IocContext", iocContext);
    }

    /**
     * 获取对象装配的上下文环境
     *
     * @return 进行对象装配的上下文环境
     */
    public static IocContext getIocContext() {
        return reqt().getAs(IocContext.class, "IocContext");
    }

    // 新的,基于ThreadLoacl改造过的Mvc辅助方法
    // ====================================================================

    /**
     * 获取全局的Ioc对象
     *
     * @return 全局的Ioc对象
     */
    public static Ioc getIoc() {
        return ctx().iocs.get(getName());
    }

    public static void setIoc(Ioc ioc) {
        ctx().iocs.put(getName(), ioc);
    }

    public static AtMap getAtMap() {
        return ctx().atMaps.get(getName());
    }

    public static void setAtMap(AtMap atmap) {
        ctx().atMaps.put(getName(), atmap);
    }

    public static Map<String, Map<String, Object>> getMessageSet() {
        return ctx().localizations.get(getName());
    }

    public static void setMessageSet(Map<String, Map<String, Object>> messageSet) {
        ctx().localizations.put(getName(), messageSet);
    }

    public static void setNutConfig(NutConfig config) {
        ctx().nutConfigs.put(getName(), config);
    }

    public static NutConfig getNutConfig() {
        return ctx().nutConfigs.get(getName());
    }

    // ==================================================================

    /**
     * 重置当前线程所持有的对象
     */
    public static Context resetALL() {
        Context context = reqt();
        NAME.set(null);
        ctx().removeReqCtx();
        return context;
    }

    public static HttpSession getHttpSession() {
        return getHttpSession(true);
    }

    public static HttpSession getHttpSession(boolean createNew) {
        HttpServletRequest req = getReq();
        if (null == req)
            return null;
        return req.getSession(createNew);
    }

    public static void close() {
        ctx().clear();
        ctx().close();
        ctx = new NutMvcContext();
    }

    public static Context reqt() {
        return ctx().reqCtx();
    }

    public static Object getSessionAttrSafe(String key) {
        try {
            HttpSession session = getHttpSession(false);
            return session != null ? session.getAttribute(key) : null;
        }
        catch (Throwable e) {
            return null;
        }
    }

    public static void setSessionAttrSafe(String key, Object val, boolean sessionCreate) {
        try {
            HttpSession session = getHttpSession(sessionCreate);
            if (session != null)
                session.setAttribute(key, val);
        }
        catch (Exception e) {
        }
    }

    public static NutMap toParamMap(Reader r, String enc) throws IOException {
        try {
            NutMap map = new NutMap();
            char[] buf = new char[1];
            StringBuilder sb = new StringBuilder();
            while (true) {
                int len = r.read(buf);
                if (len == 0)
                    continue;
                if (buf[0] == '&' || len < 0) {
                    String[] tmp = sb.toString().split("=");
                    if (tmp != null && tmp.length == 2) {
                        map.put(URLDecoder.decode(tmp[0], enc), URLDecoder.decode(tmp[1], enc));
                    }
                    if (len < 0)
                        break;
                    sb.setLength(0);
                } else {
                    sb.append(buf[0]);
                }
            }
            return map;
        }
        catch (UnsupportedEncodingException e) {
            throw new IOException(e);
        }
    }


}
