package jp.sourceforge.shovel.interceptor;

import static jp.sourceforge.shovel.ThumbnailType.*;
import static jp.sourceforge.shovel.CommonConst.*;
import static jp.sourceforge.shovel.SessionConst.*;
import static org.seasar.framework.container.ContainerConstants.*;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

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

import jp.sourceforge.shovel.ThumbnailType;
import jp.sourceforge.shovel.ErrorPageType;
import jp.sourceforge.shovel.FormatType;
import jp.sourceforge.shovel.annotation.Perform;
import jp.sourceforge.shovel.device.IDelayExecutor;
import jp.sourceforge.shovel.entity.IDedicatedClient;
import jp.sourceforge.shovel.entity.IUser;
import jp.sourceforge.shovel.exception.ApplicationException;
import jp.sourceforge.shovel.mobilephone.IMobilePhone;
import jp.sourceforge.shovel.service.IDirectoryService;
import jp.sourceforge.shovel.service.IShovelService;
import jp.sourceforge.shovel.thread.DelayExecutorQueue;
import jp.sourceforge.shovel.util.HttpUtil;

import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.codec.binary.Base64;
import org.seasar.framework.aop.interceptors.AbstractInterceptor;
import org.seasar.framework.container.S2Container;

public class AuthenticateInterceptor extends AbstractInterceptor {
    static final long serialVersionUID = 1L;
    S2Container container_;
    
    public void setContainer(S2Container container) {
        container_ = container;
    }
    S2Container getContainer() {
        return container_;
    }
    HttpServletRequest getRequest() {
        return (HttpServletRequest)getContainer().getComponent(REQUEST_NAME);
    }
    HttpServletResponse getResponse() {
        return (HttpServletResponse)getContainer().getComponent(RESPONSE_NAME);
    }
    HttpSession getSession() {
        return getRequest().getSession(true);
    }
    ServletContext getApplication() {
        return (ServletContext)getContainer().getComponent(SERVLET_CONTEXT_NAME);
    }
    DelayExecutorQueue getDelayExecutorQueue() {
        return (DelayExecutorQueue)getContainer().getComponent(DelayExecutorQueue.class);
    }
    IShovelService getShovelService() {
        HttpServletRequest request = getRequest();
        //TODO
        IShovelService shovelService = (IShovelService)request.getAttribute("shovelService");
        if (shovelService == null) {
            shovelService = (IShovelService)getContainer().getComponent(IShovelService.class);
            //TODO
            request.setAttribute("shovelService", shovelService);
        }
        return shovelService;
    }
    IDirectoryService getDirectoryService() {
        return getShovelService().getDirectoryService();
    }
    
    public Object invoke(MethodInvocation invocation) throws Throwable {
        HttpServletRequest request = getRequest();
        HttpServletResponse response = getResponse();
        HttpSession session = getSession();
        ServletContext context = getApplication();
        
        Perform annotation = invocation.getMethod().getAnnotation(Perform.class);
        ErrorPageType errorPageType = ErrorPageType.HTML;
        boolean login = true;
        boolean administrator = false;
        if (annotation != null) {
            login = annotation.login();
            administrator = annotation.administrator();
        }
        
        //ログインの要、不要が同じコントローラの場合、UrlRewriteFilterで振り分け
        //アドホックだけどね。。
        login |= request.getAttribute("requireLogin") != null;
        request.removeAttribute("requireLogin");
        
        FormatType formatType = FormatType.find(request.getParameter("format"));
        if (!formatType.isHtml()) {
            //ここの意味ないな。。
            errorPageType = ErrorPageType.find(formatType.getId());
            request.setAttribute(OUTPUT_MIMETYPE, formatType.getMimeType());
        }
        IMobilePhone mobilePhone = getShovelService().getMobilePhone();
        if (mobilePhone == null) {
            String header = request.getHeader("X-Requested-With");
            if (header != null && header.compareToIgnoreCase("XMLHttpRequest") == 0) {
                errorPageType = ErrorPageType.XHR;
            }
            String parameter = request.getParameter("iframe");
            if (parameter != null) {
                errorPageType = ErrorPageType.IFRAME;
            }
        } else {
            errorPageType = ErrorPageType.MOBILE_PHONE;
            request.setAttribute(OUTPUT_ENCODING, "Shift_JIS");
            request.setAttribute(MOBILE_PHONE, mobilePhone);
        }
        
        //プロフィールのサムネイル画像
        if (context.getAttribute("thumbnail") == null) {
            Properties props = (Properties)getContainer().getComponent(COMMON_PROPERTIES);
            String type = props.getProperty("thumbnail.creator");
            ThumbnailType thumbnailType = ThumbnailType.find(type);
            if (thumbnailType.isImageMagick()) {
                String bindir = props.getProperty("thumbnail.image_magick.bin");
                boolean image_magick = false;
                if (bindir != null) {
                    File file = new File(bindir);
                    image_magick = file.exists();
                }
                if (!image_magick) {
                    thumbnailType = SHOVEL;
                }
            }
            context.setAttribute("thumbnail", thumbnailType);
        }
        //IMなどのデバイス
        if (context.getAttribute("device") == null) {
            IDelayExecutor[] executors = getDelayExecutorQueue().getDelayExecutors();
            boolean device = false;
            for (IDelayExecutor executor : executors) {
                if (executor.getAddress() == null) {
                    context.setAttribute(executor.getExecutorId(), true);
                    continue;
                }
                device |= executor.isConnected();
            }
            context.setAttribute("executors", executors);
            context.setAttribute("device", device);
        }
        
        String format = request.getParameter("format");
        if (format != null && format.length() > 0) {
            //APIのエラーはすべてXMLでええんか？
            errorPageType = ErrorPageType.XML;
        } else {
            //ちと躊躇われるが。。
            ErrorPageType forward = (ErrorPageType)request.getAttribute(ERROR_PAGE_TYPE);
            //フォワード前の画面に従う
            if (forward != null) {
                errorPageType = forward;
            }
        }
        request.setAttribute(ERROR_PAGE_TYPE, errorPageType);
        
        //URLRewriteFilterで要Basic認証かどうか振り分ける
        IUser user = null;
        if (request.getAttribute("basicAuthentication") != null) {
            String header = request.getHeader("Authorization");
            if (header != null && header.length() > 0) {
                //ログインを試みる
                int i = header.indexOf("Basic");
                String base64 = header.substring(i + 6);
                byte[] authInfo = Base64.decodeBase64(base64.getBytes("UTF-8"));
                String auth = new String(authInfo, "UTF-8");
                String[] tokens = auth.split(":");
                String foreignKey, password = null;
                switch (tokens.length) {
                case 2:
                    password = tokens[1];
                case 1:
                    foreignKey = tokens[0];
                    break;
                default:
                    //TODO
                    throw new ApplicationException("");
                }
                //基本、Basic認証はログイン情報をセッションに積まない
                user = getDirectoryService().login(foreignKey, password, true);
            }
            if (user == null) {
                //Authorization Required
                response.addHeader("WWW-Authenticate", "Basic realm=\"Shovel API\"");
                response.setStatus(401);
                session.removeAttribute(S_LOGIN_REDIRECT_URL);
                //TODO
                throw new ApplicationException("");
            }
        }
        user = getDirectoryService().getLoginUser();
        
        //専用クライアント
        String header = request.getHeader("X-Twitter-Client");
        if (header != null && header.length() > 0) {
            String key = header;
            String url = request.getHeader("X-Twitter-Client-URL");
            String version = request.getHeader("X-Twitter-Client-Version");
            
            IDedicatedClient client = getShovelService().getClient(key);
            if (client == null) {
                getShovelService().createClient(key, url, version);
            } else if (version != null && client.getVersion() != null) {
                try {
                    if (Float.parseFloat(version) > Float.parseFloat(client.getVersion())) {
                        client.setVersion(version);
                        client.setUrl(url);
                        getShovelService().updateClient(client);
                    }
                } catch (NumberFormatException e) {
                    //バージョンがパース不可なら握り潰す
                }
            }
        }
        
        //ログアウトでフォワードチェーンするためステータスの伝播にセッションを使う
        if (request.getParameter("logout") != null && session.getAttribute(S_LOGOUT) == null) {
            session.setAttribute(S_LOGOUT, true);
            login = true;
            user = null;
        }
        
        if (user != null && !user.isAdministrator() && administrator) {
            //TODO
            throw new ApplicationException("");
        }
        
        if (login && user == null) {
            String path = request.getRequestURI();
            Map parameterMap = new HashMap();
            parameterMap.putAll(request.getParameterMap());
            parameterMap.remove("logout");
            path = HttpUtil.toRequestFullPath(path, parameterMap);
            path = path.substring(request.getContextPath().length());
            RequestDispatcher rd = request.getRequestDispatcher("/root.do");
            response.setHeader(LOGIN_BEFORE_HEADER, "1");
            rd.forward(request, response);
            return null;
        }
        return invocation.proceed();
    }
    
}
