package jp.sourceforge.shovel.logic.impl;

import static jp.sourceforge.shovel.DelayExecutionType.*;
import static jp.sourceforge.shovel.SortOrderType.*;
import static jp.sourceforge.shovel.SortType.*;
import static jp.sourceforge.shovel.StatusType.*;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.seasar.framework.container.S2Container;

import jp.sourceforge.shovel.RepliesType;
import jp.sourceforge.shovel.SortOrderType;
import jp.sourceforge.shovel.SortType;
import jp.sourceforge.shovel.dao.IDedicatedClientDao;
import jp.sourceforge.shovel.dao.IDeviceDao;
import jp.sourceforge.shovel.dao.IDirectMessageDao;
import jp.sourceforge.shovel.dao.IFavoriteDao;
import jp.sourceforge.shovel.dao.IFriendshipCustomDao;
import jp.sourceforge.shovel.dao.IFriendshipDao;
import jp.sourceforge.shovel.dao.IStatusDao;
import jp.sourceforge.shovel.device.IDelayExecutorContext;
import jp.sourceforge.shovel.entity.IDedicatedClient;
import jp.sourceforge.shovel.entity.IDevice;
import jp.sourceforge.shovel.entity.IDirectMessage;
import jp.sourceforge.shovel.entity.IFavorite;
import jp.sourceforge.shovel.entity.IFriendship;
import jp.sourceforge.shovel.entity.IStatus;
import jp.sourceforge.shovel.entity.IStatusWrapper;
import jp.sourceforge.shovel.exception.ApplicationException;
import jp.sourceforge.shovel.logic.IShovelLogic;
import jp.sourceforge.shovel.thread.DelayExecutorQueue;
import jp.sourceforge.shovel.util.ShovelUtil;

public class ShovelLogicImpl implements IShovelLogic {
    ///////////////
    //status
    
    public enum StatusTokenType {
        URL             (0, "https?://[a-zA-Z0-9/_.?#&;=$+:@%~,\\-]+"),
        REPLY           (1, "^\\s*@([^\\s]+)(\\s+.*)?$"),
        OPEN_REPLY      (2, "^[^\\s]@([^\\s]+)(\\s+.*)?$"),
        //MURLTI_REPLY    (3, "\\s@([\\w\\-]+)(\\s+[^\\s]+)?$"),
        DIRECT_MESSAGE1 (4, "^\\s*d\\s+([^\\s]+)(\\s+(.*))?$"),
        DIRECT_MESSAGE2 (5, "^\\s*m\\s+([^\\s]+)(\\s+(.*))?$"),
        DIRECT_MESSAGE3 (6, "^\\s*t\\s+([^\\s]+)(\\s+(.*))?$"),
        FAVORITES       (7, "^\\s*fav\\s+([^\\s]+)\\s*$"),
        FOLLOW1         (9, "^\\s*follow\\s+([^\\s]+)\\s*$"),
        FOLLOW2         (10, "^\\s*f\\s+([^\\s]+)\\s*$"),
        LEAVE1          (12, "^\\s*leave\\s+([^\\s]+)\\s*$"),
        LEAVE2          (13, "^\\s*l\\s+([^\\s]+)\\s*$"),
        NOTIFY_ON       (8, "^\\s*on\\s+([^\\s]+)\\s*$"),
        NOTIFY_OFF      (11, "^\\s*off\\s+([^\\s]+)\\s*$"),
        SLEEP1          (14, "^\\s*off\\s*$"),
        SLEEP2          (15, "^\\s*sleep\\s*$"),
        SLEEP3          (16, "^\\s*stop\\s*$"),
        WAKE1           (17, "^\\s*on\\s*$"),
        WAKE2           (18, "^\\s*wake\\s*$");
        
        int id_;
        String pattern_;
        StatusTokenType(int id, String pattern) {
            id_ = id;
            pattern_ = pattern;
        }
        public int getId() {
            return id_;
        }
        public String getPattern() {
            return pattern_;
        }
    }
    
    class StatusToken {
        StatusTokenType type_;
        int start_;
        int end_;
        String[] regs_;
        
        public void setType(StatusTokenType type) {
            type_ = type;
        }
        public StatusTokenType getType() {
            return type_;
        }
        public void setStart(int start) {
            start_ = start;
        }
        public int getStart() {
            return start_;
        }
        public void setEnd(int end) {
            end_ = end;
        }
        public int getEnd() {
            return end_;
        }
        public int getLength() {
            return end_ - start_;
        }
        public void setRegs(List<String> regList) {
            regs_ = regList.toArray(new String[regList.size()]);
        }
        public String[] getRegs() {
            return regs_;
        }
    }
    
    SortedMap<Long, StatusToken> parseStatus(String data, StatusTokenType type, String pattern, SortedMap<Long, StatusToken> tokenMap) {
        if (tokenMap == null) {
            tokenMap = new TreeMap<Long, StatusToken>();
        }
        
        Matcher matcher = Pattern.compile(pattern).matcher(data);
        while (matcher.find()) {
            StatusToken token = new StatusToken();
            token.setType(type);
            token.setStart(matcher.start());
            token.setEnd(matcher.end());
            
            int groupCount = matcher.groupCount();
            List<String> regs = new ArrayList<String>();
            for(int i = 0; i <= groupCount; i++) {
                regs.add(matcher.group(i));
            }
            token.setRegs(regs);
            tokenMap.put((long)matcher.start(), token);
        }
        return tokenMap;
    }
    public IStatusWrapper parseStatus(String status) {
        if (status != null && status.length() > 0) {
            SortedMap<Long, StatusToken> tokenMap = new TreeMap<Long, StatusToken>();
            for (StatusTokenType statusTokenType : StatusTokenType.values()) {
                String pattern = statusTokenType.getPattern();
                parseStatus(status, statusTokenType, pattern, tokenMap);
            }
            
            IStatusWrapper statusWrapper = (IStatusWrapper)getContainer().getComponent(IStatusWrapper.class);
            statusWrapper.setStatusType(STATUS);
            statusWrapper.setText(status);
            StatusToken[] tokens = tokenMap.values().toArray(new StatusToken[tokenMap.size()]);
            if (tokens != null && tokens.length > 0) {
                for (StatusToken token : tokens) {
                    switch (token.getType()) {
                    case OPEN_REPLY:
                        statusWrapper.setOpen(true);
                    case REPLY:
                        statusWrapper.setForeignKey(tokens[0].getRegs()[1]);
                        break;
                    case DIRECT_MESSAGE1:
                    case DIRECT_MESSAGE2:
                    case DIRECT_MESSAGE3:
                        statusWrapper.setForeignKey(tokens[0].getRegs()[1]);
                        statusWrapper.setText(tokens[0].getRegs()[3]);
                        statusWrapper.setStatusType(DIRECT_MESSAGE);
                        break;
                    case FAVORITES:
                        statusWrapper.setForeignKey(tokens[0].getRegs()[1]);
                        statusWrapper.setStatusType(FAVORITE);
                        break;
                    case FOLLOW1:
                    case FOLLOW2:
                        statusWrapper.setForeignKey(tokens[0].getRegs()[1]);
                        statusWrapper.setStatusType(FOLLOW);
                        break;
                    case LEAVE1:
                    case LEAVE2:
                        statusWrapper.setForeignKey(tokens[0].getRegs()[1]);
                        statusWrapper.setStatusType(LEAVE);
                        break;
                    case NOTIFY_ON:
                        statusWrapper.setForeignKey(tokens[0].getRegs()[1]);
                        statusWrapper.setStatusType(NOTIFY_ON);
                        break;
                    case NOTIFY_OFF:
                        statusWrapper.setForeignKey(tokens[0].getRegs()[1]);
                        statusWrapper.setStatusType(NOTIFY_OFF);
                        break;
                    case SLEEP1:
                    case SLEEP2:
                    case SLEEP3:
                        statusWrapper.setStatusType(SLEEP);
                        break;
                    case WAKE1:
                    case WAKE2:
                        statusWrapper.setStatusType(WAKE);
                        break;
                    default:
                        break;
                    }
                }
            }
            return statusWrapper;
        }
        return null;
    }
    public IStatus getStatus(long statusId) {
        return getStatusDao().find(statusId);
    }
    public IStatus getRecent(long userId) {
        return getStatusDao().findRecent(userId);
    }
    public IStatus[] getRecents(long[] userIds) {
        IStatus[] statuses = getStatusDao().findAllRecent(userIds, 0, userIds.length);
        if (statuses == null || statuses.length <= 0) {
            return statuses;
        }
        long[] statusIds = new long[statuses.length];
        int i = 0;
        for (IStatus status : statuses) {
            statusIds[i++] = status.getStatusId();
        }
        return getStatusDao().findAll(statusIds, STATUS_ID, DESC, 0, statusIds.length);
    }
    public IStatus[] getFavorites(long userId, int page, int limit) {
        return getStatusDao().findAllFavorite(userId, page, limit);
    }
    public IStatus[] getStatuses(int offset, int limit) {
        return getStatusDao().findAll(null, STATUS_ID, DESC, offset, limit);
    }
    public IStatus[] getStatuses(long sinceId, int limit) {
        return getStatusDao().findAllBySinceId(sinceId, limit);
    }
    public IStatus[] getStatuses(String since, boolean useRemoved, int limit) throws ApplicationException {
        long sinceTime = ShovelUtil.parseRFC2822DateTime(since);
        return this.getStatusDao().findAllBySinceTime( sinceTime, useRemoved, limit );
    }
    public IStatus[] getStatuses(SortType sortType, SortOrderType sortOrderType, int limit) {
        return getStatusDao().findAll(null, sortType, sortOrderType, 0, limit);
    }
    public IStatus[] getStatuses(long userId, boolean withFriends, RepliesType repliesType, int offset, int limit) {
        if (withFriends) {
            return getStatusDao().findAllWithFriends(userId, repliesType, offset, limit);
        } else {
            return getStatusDao().findAllSingle(userId, offset, limit);
        }
    }
    public IStatus[] getStatuses(long userId, boolean withFriends, RepliesType repliesType, String since, int limit) throws ApplicationException {
        long sentTime = ShovelUtil.parseRFC2822DateTime(since);
        if (withFriends) {
            return getStatusDao().findAllWithFriendsSinceTime(userId, repliesType, sentTime, limit);
        } else {
            return getStatusDao().findAllSingleSinceTime(userId, sentTime, limit);
        }
    }
    public IStatus[] getReplies(long userId, int offset, int limit) {
        return getStatusDao().findAllReply(userId, offset, limit);
    }
    public IStatus updateStatus(String body, String location, String source, long referenceSenderId, boolean open, long senderId, boolean delayCommit) throws ApplicationException {
        //まあ、まずないと思うけど
        if (senderId <= 0) {
            throw new ApplicationException("");
        }
        //空POST禁止
        if (body == null || body.length() <= 0) {
            //基本、クライアント側で防ぐ。最終防衛線。
            throw new ApplicationException("");
        }
        //空sourceはwebからのPOST扱い
        if (source == null || source.length() <= 0) {
            source = "web";
        }
        
        long referenceId = 0;
        if (referenceSenderId > 0) {
            IStatus reference = getStatusDao().findRecent(referenceSenderId);
            if (reference != null) {
                referenceId = reference.getStatusId();
            }
        }
        
        //Plaggerでエラーになるのは改行があるから？
        body = body.replaceAll("\\r?\\n", "");
        
        long sentTime = Calendar.getInstance().getTimeInMillis();
        IStatus status = (IStatus)getContainer().getComponent(IStatus.class);
        status.setBody(body);
        status.setLocation(location);
        status.setSource(source);
        status.setReferenceId(referenceId);
        status.setReferenceSenderId(referenceSenderId);
        status.setSenderId(senderId);
        status.setSentTime(sentTime);
        status.setOpen(open);
        
        IDelayExecutorContext context = (IDelayExecutorContext)getContainer().getComponent(IDelayExecutorContext.class);
        context.setDelayExecutionType(UPDATE_STATUS);
        context.setStatus(status);
        if (delayCommit) {
            context.initServerName();
            context.setCommit(delayCommit);
        } else {
            getStatusDao().insert(status);
        }
        DelayExecutorQueue queue = (DelayExecutorQueue)getContainer().getComponent(DelayExecutorQueue.class);
        queue.pushContext(context);
        
        return status;
    }
    public void receiveStatus(String body, String location, String source, long referenceSenderId, boolean open, long senderId) throws ApplicationException {
        updateStatus(body, location, source, referenceSenderId, open, senderId, false);
    }
    int incrementGivenFavorites(long statusId) {
        return getStatusDao().updateIncrementGivenFavorites(statusId);
    }
    public int decrementGivenFavorites(long statusId) {
        return getStatusDao().updateDecrementGivenFavorites(statusId);
    }
    public int destroyStatus(long statusId, boolean delayCommit) throws ApplicationException {
        long removedTime = Calendar.getInstance().getTimeInMillis();

        int result = 1;
        IStatus status = getStatusDao().find(statusId);
        if (status != null) {
            IDelayExecutorContext context = (IDelayExecutorContext)getContainer().getComponent(IDelayExecutorContext.class);
            context.setDelayExecutionType(DESTROY_STATUS);
            context.setStatus(status);
            if (delayCommit) {
                context.initServerName();
                context.setCommit(delayCommit);
            } else {
                result = getStatusDao().updateRemovedTime(statusId, removedTime);
            }
            DelayExecutorQueue queue = (DelayExecutorQueue)getContainer().getComponent(DelayExecutorQueue.class);
            queue.pushContext(context);
        }
        return result;
    }
    public int clearStatusReference(long referenceId) {
        return getStatusDao().updateReference(referenceId);
    }
    
    /////
    //direct message
    public IDirectMessage getDirectMessage(long directMessageId) {
        return getDirectMessageDao().find(directMessageId);
    }
    public IDirectMessage[] getDirectMessages(long userId, boolean sent, int offset, int limit) {
        return getDirectMessageDao().findAll(userId, sent, offset, limit);
    }
    public IDirectMessage[] getDirectMessages(long userId, boolean sent, long sinceId, int limit) {
        return getDirectMessageDao().findAllSinceId(userId, sent, sinceId, limit);
    }
    public IDirectMessage[] getDirectMessages(long userId, boolean sent, String since, int limit) throws ApplicationException {
        long sentTime = ShovelUtil.parseRFC2822DateTime(since);
        return getDirectMessageDao().findAllSinceTime(userId, sent, sentTime, limit);
    }
    public IDirectMessage createDirectMessage(String text, String source, long senderId, long recipientId, boolean delayCommit) throws ApplicationException {
        //まあ、まずないと思うが
        if (senderId <= 0 || recipientId <= 0) {
            //TODO
            throw new ApplicationException("");
        }
        //空POST禁止
        if (text == null || text.length() <= 0) {
            //TOOD
            throw new ApplicationException("");
        }
        //空sourceはwebからのPOST扱い
        if (source == null || source.length() <= 0) {
            source = "web";
        }
        long sentTime = Calendar.getInstance().getTimeInMillis();
        
        IDirectMessage directMessage = (IDirectMessage)getContainer().getComponent(IDirectMessage.class);
        directMessage.setText(text);
        directMessage.setRecipientId(recipientId);
        directMessage.setSenderId(senderId);
        directMessage.setSentTime(sentTime);
        directMessage.setSource(source);
        
        IDelayExecutorContext context = (IDelayExecutorContext)getContainer().getComponent(IDelayExecutorContext.class);
        context.setDelayExecutionType(SEND_DIRECT_MESSAGE);
        context.setDirectMessage(directMessage);
        if (delayCommit) {
            context.initServerName();
            context.setCommit(delayCommit);
        } else {
            getDirectMessageDao().insert(directMessage);
        }
        DelayExecutorQueue queue = (DelayExecutorQueue)getContainer().getComponent(DelayExecutorQueue.class);
        queue.pushContext(context);
        
        return directMessage;
    }
    public int destroyDirectMessage(long directMessageId, boolean sent, boolean delayCommit) throws ApplicationException {
        long removedTime = Calendar.getInstance().getTimeInMillis();

        int result = 1;
        IDirectMessage directMessage = getDirectMessageDao().find(directMessageId);
        if (directMessage != null) {
            IDelayExecutorContext context = (IDelayExecutorContext)getContainer().getComponent(IDelayExecutorContext.class);
            context.setDelayExecutionType(sent ? DESTROY_SENT : DESTROY_INBOX);
            context.setDirectMessage(directMessage);
            if (delayCommit) {
                context.initServerName();
                context.setCommit(delayCommit);
            } else {
                result = getDirectMessageDao().updateRemovedTime(directMessageId, sent, removedTime);
            }
            DelayExecutorQueue queue = (DelayExecutorQueue)getContainer().getComponent(DelayExecutorQueue.class);
            queue.pushContext(context);
        }
        
        return result;
    }
    
    /////
    //friendship
    public IFriendship getFriendship(long activeId, long passiveId) {
        return getFriendshipDao().find(activeId, passiveId);
    }
    public IFriendship getFriendship(long activeId, String passiveId) {
        return getFriendshipDao().findByPassiveForeignKey(activeId, passiveId);
    }
    public IFriendship getFriendship(String activeId, long passiveId) {
        return getFriendshipDao().findByActiveForeignKey(activeId, passiveId);
    }
    public IFriendship[] getFriends(long activeId, int offset, int limit) {
        return getFriendshipDao().findAllFriends(activeId, offset, limit);
    }
    public IFriendship[] getFriends(long activeId, long[] passiveIds) {
        if (passiveIds == null || passiveIds.length <= 0) {
            return new IFriendship[0];
        }
        return getFriendshipDao().findAllFriendsBy(activeId, passiveIds, 0, passiveIds.length);
    }
    public IFriendship[] getFollowers(long passiveId, boolean growl, int offset, int limit) {
        if (growl) {
            return getFriendshipDao().findAllFollowersByGrowl(passiveId, offset, limit);
        } else {
            return getFriendshipDao().findAllFollowers(passiveId, offset, limit);
        }
    }
    public IFriendship[] getFollowers(long passiveId, String deviceType, int offset, int limit) {
        return getFriendshipDao().findAllFollowersByDevice(passiveId, deviceType, offset, limit);
    }
    public IFriendship[] getFollowers(long passiveId, long[] activeIds) {
        return getFriendshipDao().findAllFollowersBy(passiveId, activeIds, 0, activeIds.length);
    }
    public IFriendship[] searchFollowing(long activeId, String[] keywords, int offset, int limit) {
        return getFriendshipCustomDao().searchFollowing(activeId, keywords, offset, limit);
    }
    public IFriendship[] searchReciprocalFollow(long userId, String[] keywords, int offset, int limit) {
        return getFriendshipCustomDao().searchReciprocalFollow(userId, keywords, offset, limit);
    }
    public IFriendship[] searchFollowingOnly(long activeId, String[] keywords, int offset, int limit) {
        return getFriendshipCustomDao().searchFollowingOnly(activeId, keywords, offset, limit);
    }
    public IFriendship[] searchFollowersOnly(long passiveId, String[] keywords, int offset, int limit) {
        return getFriendshipCustomDao().searchFollowersOnly(passiveId, keywords, offset, limit);
    }
    public IFriendship[] getRequests(long activeId, long passiveId) {
        return getFriendshipDao().find2WayRequests(activeId, passiveId);
    }
    public IFriendship[] getRequests(String activeId, long passiveId) {
        return getFriendshipDao().find2WayRequestsByForeignKey(activeId, passiveId);
    }
    public IFriendship[] getRequests(long passiveId, int offset, int limit) {
        return getFriendshipDao().findAllRequests(passiveId, offset, limit);
    }
    public int countPublicFriends(long activeId) {
        return getFriendshipDao().countPublicFriends(activeId);
    }
    public int countProtectFriends(long activeId) {
        return getFriendshipDao().countProtectFriends(activeId);
    }
    public int countAllFollowers(long passiveId) {
        return getFriendshipDao().countAllFollowers(passiveId);
    }
    public int countAcceptFollowers(long passiveId) {
        return getFriendshipDao().countAcceptFollowers(passiveId);
    }
    public int countRequestFollowers(long activeId) {
        return getFriendshipDao().countRequestFollowers(activeId);
    }
    public IFriendship createFriendship(long activeId, long passiveId, boolean protect, boolean delayCommit) throws ApplicationException {
        //自分へのフォローは禁止（パフォーマンスのため）
        if (activeId == passiveId) {
            //TODO
            throw new ApplicationException("");
        }

        IFriendship[] friendships = getRequests(activeId, passiveId);
        
        IFriendship friend = null, follower = null;
        for (IFriendship tmp : friendships) {
            if (tmp.getPassiveId() == passiveId) {
                friend = tmp;
            } else {
                follower = tmp;
            }
        }
        
        long time = Calendar.getInstance().getTimeInMillis();
        
        boolean accept = false;
        //すでにフォローさせてる、あるいは許可の申し込みがある
        if (follower != null && follower.getRemovedTime() == 0) {
            accept = true;
            //フォローする側がステータスの更新を保護
            if (protect) {
                if (follower == null) {
                    follower = createFriendship(activeId, passiveId, accept, time, delayCommit);
                    if (follower == null) {
                        throw new ApplicationException("");
                    }
                }
            }
        }
        
        if (friend == null) {
            friend = createFriendship(activeId, passiveId, accept, time, delayCommit);
            if (friend == null) {
                //TODO
                throw new ApplicationException("");
            }
        } else {
            long[] friendshipIds;
            if (follower != null && follower.getRemovedTime() == 0){
                friendshipIds = new long[] {friend.getFriendshipId(), follower.getFriendshipId()};
            } else {
                friendshipIds = new long[] {friend.getFriendshipId()};
            }
            
            int result = updateFriendships(friendshipIds, accept, time, true, 0, delayCommit);
            if (result != friendshipIds.length) {
                //TODO
                throw new ApplicationException("");
            }
            friend.setAccept(accept);
            friend.setCreatedTime(time);
            friend.setNotify(true);
            friend.setRemovedTime(0);
        }

        IDelayExecutorContext context = (IDelayExecutorContext)getContainer().getComponent(IDelayExecutorContext.class);
        context.setDelayExecutionType(FOLLOW_FRIEND);
        context.setFriend(friend);
        if (delayCommit) {
            context.initServerName();
            context.setCommit(delayCommit);
        }
        DelayExecutorQueue queue = (DelayExecutorQueue)getContainer().getComponent(DelayExecutorQueue.class);
        queue.pushContext(context);

        return friend;
    }
    IFriendship createFriendship(long activeId, long passiveId, boolean accept, long createdTime, boolean delayCommit) throws ApplicationException {
        //TODO 自分へのフォローは禁止（パフォーマンスのため）
        if (activeId == passiveId) {
            throw new ApplicationException("");
        }
        IFriendship friendship = (IFriendship)getContainer().getComponent(IFriendship.class);
        friendship.setAccept(accept);
        friendship.setActiveId(activeId);
        friendship.setCreatedTime(createdTime);
        //お知らせ機能の初期値はOn
        friendship.setNotify(true);
        friendship.setPassiveId(passiveId);
        if (!delayCommit) {
            getFriendshipDao().insert(friendship);
        }
        return friendship;
    }
    public IFriendship destroyFriendship(long activeId, long passiveId, boolean protect, boolean delayCommit) throws ApplicationException {
        IFriendship[] friendships = getRequests(passiveId, activeId);
        if (friendships == null || friendships.length > 2) {
            //TODO
            throw new ApplicationException("");
        }
        IFriendship friend = null, follower = null;
        for (IFriendship tmp : friendships) {
            if (tmp.getPassiveId() == passiveId) {
                friend = tmp;
            } else {
                follower = tmp;
            }
        }
        if (friend == null) {
            //TODO
            throw new ApplicationException("");
        }
        //削除する側がステータスの更新を保護してる
        long time = Calendar.getInstance().getTimeInMillis();
        int result = friendships.length;
        if (follower != null && protect) {
            long[] friendshipIds = new long[] {friend.getFriendshipId(), follower.getFriendshipId()};
            result = updateFriendships(friendshipIds, false, 0, false, time, delayCommit);
        } else {
            friend.setAccept(false);
            friend.setCreatedTime(0);
            friend.setNotify(false);
            friend.setRemovedTime(time);
            if (!delayCommit) {
                result = getFriendshipDao().update(friend);
                if (follower != null) {
                    follower.setAccept(false);
                    result += getFriendshipDao().update(follower);
                }
            }
        }
        if (result != friendships.length) {
            //TODO
            throw new ApplicationException("");
        }
        
        IDelayExecutorContext context = (IDelayExecutorContext)getContainer().getComponent(IDelayExecutorContext.class);
        context.setDelayExecutionType(LEAVE_FRIEND);
        context.setFriend(friend);
        if (delayCommit) {
            context.initServerName();
            context.setCommit(delayCommit);
        }
        DelayExecutorQueue queue = (DelayExecutorQueue)getContainer().getComponent(DelayExecutorQueue.class);
        queue.pushContext(context);
        
        return friend;
    }
    int updateFriendships(long[] friendshipIds, boolean accept, long createdTime, boolean notify, long removedTime, boolean delayCommit) {
        if (!delayCommit) {
            return getFriendshipDao().updateAll(friendshipIds, accept, createdTime, notify, removedTime);
        }
        return friendshipIds.length;
    }
    public int updateFriendshipNotify(long activeId, long passiveId, boolean notify) {
        return getFriendshipDao().updateNotifyByUserId(activeId, passiveId, notify);
    }
    public int updateFriendshipNotify(long activeId, String passiveId, boolean notify) {
        return getFriendshipDao().updateNotifyByForeignKey(activeId, passiveId, notify);
    }
    
    public IFriendship updateNotification(long activeId, long passiveId, boolean notify) throws ApplicationException {
        IFriendship friendship = getFriendship(activeId, passiveId);
        if (friendship == null) {
            //TODO
            throw new ApplicationException("");
        }
        int result = updateFriendshipNotify(activeId, passiveId, notify);
        if (result != 1) {
            //TODO
            throw new ApplicationException("");
        }
        return friendship;
    }
    public IFriendship updateNotification(long activeId, String passiveId, boolean notify) throws ApplicationException {
        IFriendship friendship = getFriendship(activeId, passiveId);
        if (friendship == null) {
            //TODO
            throw new ApplicationException("");
        }
        int result = updateFriendshipNotify(activeId, passiveId, notify);
        if (result != 1) {
            //TODO
            throw new ApplicationException("");
        }
        return friendship;
    }
    
    public IFriendship acceptFriendship(long activeId, long passiveId, boolean delayCommit) throws ApplicationException {
        IFriendship[] friendships = getRequests(activeId, passiveId);
        if (friendships == null) {
            //TODO
            throw new ApplicationException("");
        }
        IFriendship friend = null, follower = null;
        for (IFriendship tmp : friendships) {
            if (tmp.getActiveId() == activeId) {
                follower = tmp;
            } else {
                friend = tmp;
            }
        }
        if (follower ==  null) {
            //TODO
            throw new ApplicationException("");
        }
        
        long time = Calendar.getInstance().getTimeInMillis();
        
        long[] friendshipIds = new long[] {follower.getFriendshipId()};
        if (friend == null) {
            friend = createFriendship(passiveId, activeId, true, time, delayCommit);
            if (friend == null) {
                //TODO
                throw new ApplicationException("");
            }
        }/* else {
            //20090309フォローを要求した側だけのフォローに仕様を変更
            friendshipIds = new long[] {follower.getFriendshipId(), friend.getFriendshipId()};
        }*/
        
        int result = updateFriendships(friendshipIds, true, time, true, 0, delayCommit);
        if (result != friendshipIds.length) {
            //TODO
            throw new ApplicationException("");
        }
        return follower;
    }
    public IFriendship denyFriendship(long activeId, long passiveId, boolean denyCommit) throws ApplicationException {
        IFriendship[] friendships = getRequests(passiveId, activeId);
        if (friendships == null || friendships.length > 2) {
            //TODO
            throw new ApplicationException("");
        }
        IFriendship friend = null, follower = null;
        for (IFriendship tmp : friendships) {
            if (tmp.getActiveId() == passiveId) {
                follower = tmp;
            } else {
                friend = tmp;
            }
        }
        if (follower == null) {
            //TODO
            throw new ApplicationException("");
        }
        long[] friendshipIds;
        if (friend == null) {
            friendshipIds = new long[] {follower.getFriendshipId()};
        } else {
            friendshipIds = new long[] {follower.getFriendshipId(), friend.getFriendshipId()};
        }
        long time = Calendar.getInstance().getTimeInMillis();
        int result = updateFriendships(friendshipIds, false, 0, false, time, denyCommit);
        if (result != friendshipIds.length) {
            //TODO
            throw new ApplicationException("");
        }
        return follower;
    }
    
    /////
    //favorite
    public IFavorite getFavorite(long statusId, long userId) {
        return getFavoriteDao().findFavorite(statusId, userId);
    }
    public IFavorite[] getFavorites(long[] statusIds, long userId) {
        return getFavoriteDao().findAllFavorite(statusIds, userId, statusIds.length);
    }
    public IFavorite[] getFavoritesByStatus(long statusId, int limit) {
        return getFavoriteDao().findAllFavoriteByStatus(statusId, limit);
    }
    public IFavorite[] getFavoritesByStatuses(long[] statusIds, int limit) {
        return getFavoriteDao().findAllFavoriteByStatuses(statusIds, limit);
    }
    public IFavorite createFavorite(long statusId, long userId, boolean delayCommit) throws ApplicationException {
        long createdTime = Calendar.getInstance().getTimeInMillis();
        IFavorite favorite = (IFavorite)getContainer().getComponent(IFavorite.class);
        favorite.setCreatedTime(createdTime);
        favorite.setStatusId(statusId);
        favorite.setUserId(userId);
        
        IDelayExecutorContext context = (IDelayExecutorContext)getContainer().getComponent(IDelayExecutorContext.class);
        context.setDelayExecutionType(CREATE_FAVORITE);
        context.setFavorite(favorite);
        if (delayCommit) {
            context.initServerName();
            context.setCommit(delayCommit);
        } else {
            incrementGivenFavorites(statusId);
            getFavoriteDao().insert(favorite);
        }
        DelayExecutorQueue queue = (DelayExecutorQueue)getContainer().getComponent(DelayExecutorQueue.class);
        queue.pushContext(context);
        
        return favorite;
    }
    public int destroyFavorite(long statusId, long userId, boolean delayCommit) throws ApplicationException {
        int result = 1;
        IFavorite favorite = getFavoriteDao().findFavorite(statusId, userId);
        if (favorite != null) {
            IDelayExecutorContext context = (IDelayExecutorContext)getContainer().getComponent(IDelayExecutorContext.class);
            context.setDelayExecutionType(DESTROY_FAVORITE);
            context.setFavorite(favorite);
            if (delayCommit) {
                context.initServerName();
                context.setCommit(delayCommit);
            } else {
                decrementGivenFavorites(statusId);
                result = getFavoriteDao().delete(statusId, userId);
            }
            DelayExecutorQueue queue = (DelayExecutorQueue)getContainer().getComponent(DelayExecutorQueue.class);
            queue.pushContext(context);
        }
        return result;
    }
    
    /////
    //device
    
    public IDevice createDevice(IDevice device) {
        getDeviceDao().insert(device);
        return device;
    }
    public int updateDevice(IDevice device) {
        return getDeviceDao().update(device);
    }
    
    /////
    //client
    
    Map<String, IDedicatedClient> clientMap_ = new HashMap<String, IDedicatedClient>();
    
    public IDedicatedClient createClient(String key, String url, String version) {
        IDedicatedClient client = (IDedicatedClient)getContainer().getComponent(IDedicatedClient.class);
        client.setText(key);
        client.setUrl(url);
        client.setVersion(version);
        getDedicatedClientDao().insert(client);
        clientMap_.put(key, client);
        return client;
    }
    public int updateClient(IDedicatedClient client) {
        clientMap_.put(client.getText(), client);
        return getDedicatedClientDao().update(client);
    }
    public IDedicatedClient getClient(String key) {
        IDedicatedClient client = clientMap_.get(key);
        if (client != null) {
            return client;
        }
        return getDedicatedClientDao().findByKey(key);
    }
    public IDedicatedClient[] getClients(String[] texts) {
        List<IDedicatedClient> clientList = new ArrayList<IDedicatedClient>();
        List<String> textList = new ArrayList<String>();
        for (String text : texts) {
            IDedicatedClient client = clientMap_.get(text);
            if (client == null) {
                textList.add(text);
            } else {
                clientList.add(client);
            }
        }
        if (textList.size() > 0) {
            texts = textList.toArray(new String[textList.size()]);
            IDedicatedClient[] clients = getDedicatedClientDao().findAllByText(texts, 0, texts.length);
            for (IDedicatedClient client : clients) {
                clientMap_.put(client.getText(), client);
                clientList.add(client);
            }
        }
        return clientList.toArray(new IDedicatedClient[clientList.size()]);
    }
    public int removeClient(String text) {
        clientMap_.remove(text);
        return getDedicatedClientDao().deleteByText(text);
    }
    
    /////
    
    S2Container container_;
    
    public void setContainer(S2Container container) {
        container_ = container;
    }
    S2Container getContainer() {
        return container_;
    }
    public IStatusDao getStatusDao() {
        return (IStatusDao)getContainer().getComponent(IStatusDao.class);
    }
    public IDirectMessageDao getDirectMessageDao() {
        return (IDirectMessageDao)getContainer().getComponent(IDirectMessageDao.class);
    }
    public IFavoriteDao getFavoriteDao() {
        return (IFavoriteDao)getContainer().getComponent(IFavoriteDao.class);
    }
    public IFriendshipDao getFriendshipDao() {
        return (IFriendshipDao)getContainer().getComponent(IFriendshipDao.class);
    }
    public IDeviceDao getDeviceDao() {
        return (IDeviceDao)getContainer().getComponent(IDeviceDao.class);
    }
    public IDedicatedClientDao getDedicatedClientDao() {
        return (IDedicatedClientDao)getContainer().getComponent(IDedicatedClientDao.class);
    }
    IFriendshipCustomDao getFriendshipCustomDao() {
        return (IFriendshipCustomDao)getContainer().getComponent(IFriendshipCustomDao.class);
    }
}
