package jp.sourceforge.shovel.service.impl;

import static jp.sourceforge.shovel.AvailabilityType.*;
import static jp.sourceforge.shovel.DeviceType.*;
import static org.seasar.framework.container.ContainerConstants.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.ArrayUtils;
import org.apache.struts.Globals;
import org.apache.struts.util.MessageResources;
import org.seasar.framework.container.S2Container;

import com.sun.syndication.feed.synd.SyndContent;
import com.sun.syndication.feed.synd.SyndContentImpl;
import com.sun.syndication.feed.synd.SyndEntry;
import com.sun.syndication.feed.synd.SyndEntryImpl;
import com.sun.syndication.feed.synd.SyndFeed;
import com.sun.syndication.feed.synd.SyndFeedImpl;
import com.sun.syndication.io.FeedException;
import com.sun.syndication.io.SyndFeedOutput;

import jp.sourceforge.shovel.AvailabilityType;
import jp.sourceforge.shovel.FormatType;
import jp.sourceforge.shovel.SearchFriendsType;
import jp.sourceforge.shovel.SortOrderType;
import jp.sourceforge.shovel.SortType;
import jp.sourceforge.shovel.StatusType;
import jp.sourceforge.shovel.ViewType;
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.IServerFile;
import jp.sourceforge.shovel.entity.IStatus;
import jp.sourceforge.shovel.entity.IStatusWrapper;
import jp.sourceforge.shovel.entity.IUser;
import jp.sourceforge.shovel.exception.ApplicationException;
import jp.sourceforge.shovel.logic.IMobilePhoneLogic;
import jp.sourceforge.shovel.logic.IShovelLogic;
import jp.sourceforge.shovel.mobilephone.IMobilePhone;
import jp.sourceforge.shovel.service.IDirectoryService;
import jp.sourceforge.shovel.service.IServerFileService;
import jp.sourceforge.shovel.service.IShovelService;

public class ShovelServiceImpl implements IShovelService {
    Map<Long, IStatus> statusMap_;

    public ShovelServiceImpl() {
        statusMap_ = new HashMap<Long, IStatus>();
    }
    void cacheStatus(IStatus status) {
        if (status != null) {
            statusMap_.put(status.getStatusId(), status);
        }
    }
    void cacheStatuses(IStatus[] statuses) {
        for (IStatus status : statuses) {
            cacheStatus(status);
        }
    }
    void purgeStatuses() {
        statusMap_.clear();
    }
    IStatus fetchStatus(long statusId) {
        return statusMap_.get(statusId);
    }
    
    int correctLimit(int limit) {
        IUser user = getDirectoryService().getLoginUser();
        if (user != null && limit <= 0) {
            limit = user.getViewLines();
            //TODO
            if (limit <= 0 || limit > 20) {
                limit = 20;
            }
        } else {
            limit = 20;
        }
        return limit;
    }
    
    /////
    public IStatus[] getFavorites(String foreignKey, int page, int limit) throws ApplicationException {
        IUser user = getDirectoryService().getUser(foreignKey);
        if (user == null) {
            throw new ApplicationException("");
        }
        limit = correctLimit(limit);
        IStatus[] statuses = getShovelLogic().getFavorites(user.getUserId(), page * limit, limit + 1);
        cacheStatuses(statuses);
        return statuses;
    }
    public IStatus[] getReplies(String foreignKey, int page, int limit) throws ApplicationException {
        if (foreignKey == null) {
            //TODO
            throw new ApplicationException("");
        }
        IUser user = getDirectoryService().getLoginUser();
        //1つ多く検索して次ページの有無の判断に
        if (user == null || foreignKey.compareTo(user.getForeignKey()) != 0) {
            user = getDirectoryService().getUser(foreignKey);
        }
        limit = correctLimit(limit);
        IStatus[] statuses = getShovelLogic().getReplies(
                user.getUserId(), page * limit, limit + 1);
        cacheStatuses(statuses);
        return statuses;
    }
    public IStatus getRecent() {
        IUser user = getDirectoryService().getLoginUser();
        IStatus status = getShovelLogic().getRecent(user.getUserId());
        cacheStatus(status);
        return status;
    }
    public IStatus getRecent(String foreignKey) throws ApplicationException {
        IUser user = getDirectoryService().getUser(foreignKey);
        if (user == null) {
            //TODO
            throw new ApplicationException("");
        }
        IStatus status = getShovelLogic().getRecent(user.getUserId());
        cacheStatus(status);
        return status;
    }
    public IStatus getRecent(long userId) throws ApplicationException {
        IStatus status = getShovelLogic().getRecent(userId);
        cacheStatus(status);
        return status;
    }
    public IStatus getStatus(long statusId) {
        IStatus status = fetchStatus(statusId);
        if (status == null) {
            status = getShovelLogic().getStatus(statusId);
            cacheStatus(status);
        }
        return status;
    }
    public IStatus[] getRecents(long[] userIds) {
        IStatus[] statuses = getShovelLogic().getRecents(userIds);
        cacheStatuses(statuses);
        return statuses;
    }
    public IStatus[] getStatuses(int page, int limit) {
        limit = correctLimit(limit);
        //1つ多く検索して次ページの有無の判断に
        IStatus[] statuses = getShovelLogic().getStatuses(page * limit, limit + 1);
        cacheStatuses(statuses);
        return statuses;
    }
    public IStatus[] getStatuses( String since, boolean useRemoved, int limit ) throws ApplicationException
    {
        IStatus[] statuses = getShovelLogic().getStatuses(since, useRemoved, limit);
        List<IStatus> lists = new ArrayList<IStatus>();
        if( useRemoved )
        {
            for( IStatus status : statuses )
            {
                if( !status.isRemove() )
                {
                    lists.add( status );
                }
            }
            IStatus[] cacheStatuses = lists.toArray( new IStatus[lists.size()] );
            this.cacheStatuses( cacheStatuses );
        }
        else
        {
            this.cacheStatuses( statuses );
        }
        return statuses;
    }
    public IStatus[] getStatuses(long sinceId, int limit) {
        limit = correctLimit(limit);
        IStatus[] statuses = getShovelLogic().getStatuses(sinceId, limit);
        cacheStatuses(statuses);
        return statuses;
    }
    public IStatus[] getStatuses(SortType sortType, SortOrderType sortOrderType, int limit) {
        limit = correctLimit(limit);
        IStatus[] statuses = getShovelLogic().getStatuses(sortType, sortOrderType, limit);
        cacheStatuses(statuses);
        return statuses;
    }
    public IStatus[] getStatuses(String foreignKey, boolean withFriends, String since, int limit) throws ApplicationException {
        if (foreignKey == null) {
            //TODO
            throw new ApplicationException("");
        }
        limit = correctLimit(limit);
        
        IUser user = getDirectoryService().getLoginUser();
        if (user == null || foreignKey.compareTo(user.getForeignKey()) != 0) {
            user = getDirectoryService().getUser(foreignKey);
        }
        IStatus[] statuses = getShovelLogic().getStatuses(user.getUserId(),
                withFriends, user.getRepliesType(), since, limit);
        cacheStatuses(statuses);
        return statuses;
    }
    public IStatus[] getStatuses(String foreignKey, boolean withFriends, int page, int limit, int correctedOffset, int correctedLimit) throws ApplicationException {
        if (foreignKey == null) {
            //TODO
            throw new ApplicationException("");
        }
        limit = correctLimit(limit);
        int offset = page * limit + correctedOffset;
        limit += correctedLimit;
        
        //1つ多く検索して次ページの有無の判断に
        IUser user = getDirectoryService().getLoginUser();
        if (user == null || foreignKey.compareTo(user.getForeignKey()) != 0) {
            user = getDirectoryService().getUser(foreignKey);
        }
        IStatus[] statuses = getShovelLogic().getStatuses(user.getUserId(),
                withFriends, user.getRepliesType(), offset, limit);
        cacheStatuses(statuses);
        return statuses;
    }
    public int destroyStatus(long statusId) throws ApplicationException {
        IUser user = getDirectoryService().getLoginUser();
        IStatus status = getStatus(statusId);
        if (status == null) {
            //TODO
            throw new ApplicationException("");
        }
        if (user.getUserId() != status.getSenderId()) {
            //TODO
            throw new ApplicationException("");
        }
        
        if (!isMobilePhone()) {
            getDirectoryService().decrementStatuses(user.getUserId());
            getDirectoryService().decrementFavoritesAll(statusId);
            getShovelLogic().clearStatusReference(statusId);
        }
        int result = getShovelLogic().destroyStatus(statusId, isMobilePhone());
        if (result <= 0) {
            //TODO
            throw new ApplicationException("");
        }
        return result;
    }
    
    public IStatusWrapper updateStatus(String text, String source) throws ApplicationException {
        IDirectoryService directoryService = getDirectoryService();
        if (isMobilePhone()) {
            IUser sender = directoryService.getLoginUser();
            getShovelLogic().updateStatus(text, sender.getLocation(), "mobile web", 0,
                    false, sender.getUserId(), true);
            return null;
        }

        IStatusWrapper statusWrapper = getShovelLogic().parseStatus(text);
        String foreignKey = statusWrapper.getForeignKey();
        switch (statusWrapper.getStatusType()) {
        case STATUS:
            long referenceSenderId = 0;
            if (foreignKey != null) {
                IUser referenceSender = directoryService.getUser(foreignKey);
                if (referenceSender != null) {
                    referenceSenderId = referenceSender.getUserId();
                }
            }
            IUser sender = directoryService.getLoginUser();
            directoryService.incrementStatuses(sender.getUserId());
            IStatus status = getShovelLogic().updateStatus(text, sender.getLocation(), source,
                    referenceSenderId, statusWrapper.isOpen(), sender.getUserId(), false);
            statusWrapper.setStatus(status);
            break;
        case DIRECT_MESSAGE:
            text = statusWrapper.getText();
            IDirectMessage directMessage = createDirectMessage(foreignKey, text, source);
            statusWrapper.setDirectMessage(directMessage);
            break;
        case FAVORITE:
            IFavorite favorite = createFavorite(foreignKey);
            statusWrapper.setFavorite(favorite);
            break;
        case FOLLOW:
            IFriendship friend = createFriendship(foreignKey);
            IUser passive = directoryService.getUser(foreignKey);
            if (passive.isProtect() && !friend.isAccept()) {
                statusWrapper.setStatusType(StatusType.FRIEND_REQUEST);
            }
            statusWrapper.setFriend(friend);
            break;
        case LEAVE:
            friend = destroyFriendship(foreignKey);
            statusWrapper.setFriend(friend);
            break;
        case NOTIFY_ON:
            updateNotification(foreignKey, true);
            break;
        case NOTIFY_OFF:
            updateNotification(foreignKey, false);
            break;
        case SLEEP:
            updateDevice(OFF);
            break;
        case WAKE:
            updateDevice(ON);
            break;
        default:
            throw new ApplicationException("");
        }
        return statusWrapper;
    }
    public IStatus[] truncate(IStatus[] statuses, int offset, int limit) {
        limit = correctLimit(limit);
        return (IStatus[])ArrayUtils.subarray(statuses, offset, offset + limit);
    }
    public IUser[] truncate(IUser[] users, int limit) {
        limit = correctLimit(limit);
        if (users.length > limit) {
            users = (IUser[])ArrayUtils.subarray(users, 0, limit);
        }
        return users;
    }
    public IFriendship[] truncate(IFriendship[] friends, int limit) {
        limit = correctLimit(limit);
        if (friends.length > limit) {
            friends = (IFriendship[])ArrayUtils.subarray(friends, 0, limit);
        }
        return friends;
    }
    public void prepareForView(IStatus status) throws ApplicationException {
        if (status == null) {
            throw new ApplicationException("");
        }
        
        String source = status.getSource();
        IDedicatedClient client = getClient(source);
        status.setDedicatedClient(client);
        
        if (getDirectoryService().getLoginUser() != null) {
            IFavorite favorite = getFavorite(status.getStatusId());
            status.setFavorite(favorite);
        }
        
        Set<Long> userIdSet = new HashSet<Long>();
        userIdSet.add(status.getReferenceSenderId());
        userIdSet.add(status.getSenderId());
        
        IFavorite[] otherFavorites = getFavoritesByStatus(status.getStatusId(), 13);
        if (otherFavorites != null || otherFavorites.length <= 0) {
            status.setOtherFavorites(otherFavorites);
        }
        for (IFavorite favorite : otherFavorites) {
            userIdSet.add(favorite.getUserId());
        }
        
        Long[] userIds = userIdSet.toArray(new Long[userIdSet.size()]);
        IUser[] users = getDirectoryService().getUsers(ArrayUtils.toPrimitive(userIds));
        Map<Long, IUser> userMap = new HashMap<Long, IUser>();
        for (IUser user : users) {
            IServerFile serverFile = user.getProfileImage();
            if (serverFile != null) {
                serverFile.prepareForView();
            }
            userMap.put(user.getUserId(), user);
        }
        
        for (IFavorite favorite : otherFavorites) {
            favorite.setUser(userMap.get(favorite.getUserId()));
        }
        
        long userId = status.getReferenceSenderId();
        status.setReferenceSender(userMap.get(userId));
        
        userId = status.getSenderId();
        status.setSender(userMap.get(userId));
    }
    public IStatus[] prepareForView(IStatus[] statuses, int limit, boolean truncate) {
        if (truncate && statuses.length > limit) {
            statuses = truncate(statuses, 0, limit);
        }
        if (statuses == null || statuses.length <= 0) {
            return new IStatus[0];
        }
        
        Set<Long> userIdSet = new HashSet<Long>();
        Set<String> sourceSet = new HashSet<String>();
        long[] statusIds = new long[statuses.length];
        int i = 0;
        for (IStatus status : statuses) {
            userIdSet.add(status.getReferenceSenderId());
            userIdSet.add(status.getSenderId());
            sourceSet.add(status.getSource());
            statusIds[i++] = status.getStatusId();
        }
        
        Map<Long, List<IFavorite>> othrerFavoriteListMap = new HashMap<Long, List<IFavorite>>();
        IFavorite[] otherFavorites = getFavoritesByStatuses(statusIds, 13);
        for (IFavorite favorite : otherFavorites) {
            long statusId = favorite.getStatusId();
            List<IFavorite> favoriteList = othrerFavoriteListMap.get(statusId);
            if (favoriteList == null) {
                favoriteList = new ArrayList<IFavorite>();
                othrerFavoriteListMap.put(statusId, favoriteList);
            }
            favoriteList.add(favorite);
            userIdSet.add(favorite.getUserId());
        }
        
        Long[] userIds = userIdSet.toArray(new Long[userIdSet.size()]);
        IUser[] users = getDirectoryService().getUsers(ArrayUtils.toPrimitive(userIds));
        Map<Long, IUser> userMap = new HashMap<Long, IUser>();
        for (IUser user : users) {
            //サムネイル作るので廃止
            IServerFile serverFile = user.getProfileImage();
            if (serverFile != null) {
                serverFile.prepareForView();
            }
            userMap.put(user.getUserId(), user);
        }
        
        for (IFavorite favorite : otherFavorites) {
            favorite.setUser(userMap.get(favorite.getUserId()));
        }
        
        Map<Long, IFavorite> favoriteMap = new HashMap<Long, IFavorite>();
        IUser user = getDirectoryService().getLoginUser();
        if (user != null) {
            IFavorite[] myFavorites = getFavorites(statusIds);
            for (IFavorite favorite : myFavorites) {
                favoriteMap.put(favorite.getStatusId(), favorite);
            }
        }
        
        String[] keys = sourceSet.toArray(new String[sourceSet.size()]);
        IDedicatedClient[] clients = getClients(keys);
        Map<String, IDedicatedClient> clientMap = new HashMap<String, IDedicatedClient>();
        for (IDedicatedClient client : clients) {
            clientMap.put(client.getText(), client);
        }
        
        for (IStatus status : statuses) {
            String source = status.getSource();
            IDedicatedClient client = clientMap.get(source);
            if (client != null) {
                status.setDedicatedClient(client);
            }
            
            long userId = status.getReferenceSenderId();
            status.setReferenceSender(userMap.get(userId));
            userId = status.getSenderId();
            status.setSender(userMap.get(userId));
            long statusId = status.getStatusId();
            status.setFavorite(favoriteMap.get(statusId));
            List<IFavorite> favoriteList = othrerFavoriteListMap.get(statusId);
            if (favoriteList != null) {
                status.setOtherFavorites(favoriteList.toArray(new IFavorite[favoriteList.size()]));
            }
        }
        return statuses;
    }
    public void outputFeed(FormatType formatType, ViewType viewType, IUser user, IStatus[] statuses) throws ApplicationException {
        HttpServletRequest request = (HttpServletRequest)getContainer().getComponent(REQUEST_NAME);
        HttpServletResponse response = (HttpServletResponse)getContainer().getComponent(RESPONSE_NAME);
        MessageResources resources = (MessageResources)request.getAttribute(Globals.MESSAGES_KEY);
        
        String displayName = null;
        String foreignKey = null;
        if (user != null) {
            displayName = user.getDisplayName();
            foreignKey = user.getForeignKey();
        }
        
        Object[] args = {displayName, formatType.getId()};
        String title = resources.getMessage("feed." + viewType.getId() + ".title", args);
        String server = request.getServerName();
        if (request.getServerPort() != 80) {
            server += ":" + String.valueOf(request.getServerPort());
        }
        //args = new Object[] {server, formatType.getId(), user.getUserId()};
        //String link = resources.getMessage(viewType.getId() + ".feed.url", args);
        String link = (String)request.getAttribute("request-url");
        args = new Object[] {resources.getMessage("product.name"), displayName, foreignKey};
        String description = resources.getMessage("feed." + viewType.getId() + ".description", args);
        
        SyndFeed feed = new SyndFeedImpl();
        feed.setTitle(title);
        feed.setLink(link);
        feed.setDescription(description);
        
        List<SyndEntry> entries = new ArrayList<SyndEntry>();
        SyndEntry entry;
        SyndContent html;
        for (IStatus s : statuses) {
            IUser sender = s.getSender();
            args = new Object[] {sender.getForeignKey(), s.getBody()};
            title = resources.getMessage("feed.statuses.entry.description", args);
            args = new Object[] {server, sender.getForeignKey(), s.getStatusId()};
            link = resources.getMessage("feed.statuses.entry.link", args);
            Calendar calendar = Calendar.getInstance();
            calendar.setTimeInMillis(s.getSentTime());
            Date publishedDate = calendar.getTime();
            
            entry = new SyndEntryImpl();
            entry.setTitle(title);
            entry.setLink(link);
            entry.setPublishedDate(publishedDate);
            entry.setUpdatedDate(publishedDate);
            html = new SyndContentImpl();
            html.setType("html");
            html.setValue(title);
            entry.setDescription(html);
            entries.add(entry);
        }

        feed.setEntries(entries);

        try {
            feed.setFeedType(formatType.getFeedType());
            String mimeType = formatType.getMimeType();
            response.setContentType(mimeType + "; charset=UTF-8");
            SyndFeedOutput output = new SyndFeedOutput();
            output.output(feed, response.getWriter());
        } catch (FeedException e) {
            //TODO
        } catch (IOException e) {
            //TODO
        }
    }
    
    /////
    
    public IDirectMessage createDirectMessage(String foreignKey, String text, String source)
        throws ApplicationException {
        IDirectoryService directoryService = getDirectoryService();
        IUser recipient = directoryService.getUser(foreignKey);
        if (recipient == null) {
            //TODO
            throw new ApplicationException("");
        }
        IFriendship follower = getFollower(foreignKey);
        //自分をフォローする人だけ
        if (follower == null) {
            //TODO
            throw new ApplicationException("");
        }
        IUser sender = directoryService.getLoginUser();
        directoryService.incrementDirectMessages(recipient.getUserId());
        return getShovelLogic().createDirectMessage(text, source, sender.getUserId(),
                recipient.getUserId(), isMobilePhone());
    }
    public IDirectMessage createDirectMessage(long userId, String text, String source)
        throws ApplicationException {
        IDirectoryService directoryService = getDirectoryService();
        IUser recipient = directoryService.getUser(userId);
        if (recipient == null) {
            //TODO
            throw new ApplicationException("");
        }
        return createDirectMessage(recipient.getForeignKey(), text, source);
    }
    public IDirectMessage destroyDirectMessage(long directMessageId) throws ApplicationException {
        IDirectMessage directMessage = getShovelLogic().getDirectMessage(directMessageId);
        
        IDirectoryService directoryService = getDirectoryService();
        IUser user = directoryService.getLoginUser();
        boolean sent = true;
        if (directMessage.getRecipientId() == user.getUserId()) {
            if (!isMobilePhone()) {
                directoryService.decrementDirectMessages(user.getUserId());
            }
            sent = false;
        } else if (directMessage.getSenderId() != user.getUserId()){
            //TODO
            throw new ApplicationException("");
        }
        
        int result = getShovelLogic().destroyDirectMessage(directMessageId, sent, isMobilePhone());
        if (result <= 0) {
            //TODO
            throw new ApplicationException("");
        }
        return directMessage;
    }
    public IDirectMessage getDirectMessage(long directMessageId) {
        return getShovelLogic().getDirectMessage(directMessageId);
    }
    IDirectMessage[] truncate(IDirectMessage[] directMessages) {
        IUser login = getDirectoryService().getLoginUser();
        //TODO
        int limit = 10;
        if (login != null) {
            limit = login.getViewLines();
        }
        if (directMessages.length > limit) {
            directMessages = (IDirectMessage[])ArrayUtils.subarray(directMessages, 0, limit);
        }
        return directMessages;
    }
    public IDirectMessage[] getDirectMessages(boolean sent, int page, int limit) {
        IUser user = getDirectoryService().getLoginUser();
        limit = correctLimit(limit);
        
        //1つ多く検索して次ページの有無の判断に
        return getShovelLogic().getDirectMessages(user.getUserId(), sent, page * limit, limit + 1);
    }
    public IDirectMessage[] getDirectMessages(boolean sent, long sinceId, int limit) {
        IUser user = getDirectoryService().getLoginUser();
        limit = correctLimit(limit);
        
        //1つ多く検索して次のページの有無の判断に
        return getShovelLogic().getDirectMessages(user.getUserId(), sent, sinceId, limit);
    }
    public IDirectMessage[] getDirectMessages(boolean sent, String since, int limit) throws ApplicationException {
        IUser user = getDirectoryService().getLoginUser();
        limit = correctLimit(limit);
        
        //1つ多く検索して次のページの有無の判断に
        return getShovelLogic().getDirectMessages(user.getUserId(), sent, since, limit);
    }
    public IDirectMessage[] prepareForView(IDirectMessage[] directMessages, boolean truncate) {
        if (truncate) {
            directMessages = truncate(directMessages);
        }
        if (directMessages == null || directMessages.length <= 0) {
            return new IDirectMessage[0];
        }
        
        Set<Long> userIdSet = new HashSet<Long>();
        for (IDirectMessage directMessage : directMessages) {
            userIdSet.add(directMessage.getSenderId());
            userIdSet.add(directMessage.getRecipientId());
        }
        Long[] userIds = userIdSet.toArray(new Long[userIdSet.size()]);
        IUser[] users = getDirectoryService().getUsers(ArrayUtils.toPrimitive(userIds));
        Map<Long, IUser> userMap = new HashMap<Long, IUser>();
        for (IUser user : users) {
            //サムネイル作るので廃止
            IServerFile serverFile = user.getProfileImage();
            if (serverFile != null) {
                serverFile.prepareForView();
            }
            userMap.put(user.getUserId(), user);
        }
        
        for (IDirectMessage directMessage : directMessages) {
            long userId = directMessage.getSenderId();
            directMessage.setSender(userMap.get(userId));
            userId = directMessage.getRecipientId();
            directMessage.setRecipient(userMap.get(userId));
        }
        return directMessages;
    }
    public void outputFeed(FormatType formatType, ViewType viewType, IUser user, IDirectMessage[] directMessages) throws ApplicationException {
        HttpServletRequest request = getRequest();
        HttpServletResponse response = getResponse();
        MessageResources resources = (MessageResources)request.getAttribute(Globals.MESSAGES_KEY);
        Object[] args = {user.getForeignKey()};
        String title = resources.getMessage("feed." + viewType.getId() + ".title", args);
        String server = request.getServerName();
        if (request.getServerPort() != 80) {
            server += ":" + String.valueOf(request.getServerPort());
        }
        //args = new Object[] {server, formatType.getId(), user.getUserId()};
        //String link = resources.getMessage(viewType.getId() + ".feed.url", args);
        String link = (String)request.getAttribute("request-url");
        String description = resources.getMessage("feed." + viewType.getId() + ".description", args);
        
        SyndFeed feed = new SyndFeedImpl();
        feed.setTitle(title);
        feed.setLink(link);
        feed.setDescription(description);
        
        List<SyndEntry> entries = new ArrayList<SyndEntry>();
        SyndEntry entry;
        for (IDirectMessage dm : directMessages) {
            IUser sender = dm.getSender();
            IUser recipient = dm.getRecipient();
            args = new Object[] {sender.getForeignKey(), recipient.getForeignKey()};
            title = resources.getMessage("feed." + viewType.getId() + ".entry.title", args);
            args = new Object[] {dm.getText()};
            description = resources.getMessage("feed." + viewType.getId() + ".entry.description", args);
            args = new Object[] {server, dm.getDirectMessageId()};
            link = resources.getMessage("feed." + viewType.getId() + ".entry.link", args);
            SyndContent content = new SyndContentImpl();
            content.setValue(description);
            Calendar calendar = Calendar.getInstance();
            calendar.setTimeInMillis(dm.getSentTime());
            Date publishedDate = calendar.getTime();
            
            entry = new SyndEntryImpl();
            entry.setTitle(title);
            if (formatType.isRss()) {
                entry.setDescription(content);
            } else {
                List<SyndContent> contents = new ArrayList<SyndContent>();
                contents.add(content);
                entry.setContents(contents);
            }
            entry.setPublishedDate(publishedDate);
            entry.setLink(link);
            entries.add(entry);
        }
        feed.setEntries(entries);
        try {
            feed.setFeedType(formatType.getFeedType());
            String mimeType = formatType.getMimeType();
            response.setContentType(mimeType + "; charset=UTF-8");
            SyndFeedOutput output = new SyndFeedOutput();
            output.output(feed, response.getWriter());
        } catch (FeedException e) {
            //TODO
        } catch (IOException e) {
            //TODO
        }
    }

    ////////////////////
    //Friendship
    
    public IFriendship[] getFriends(String foreignKey, int offset, int limit) throws ApplicationException {
        IUser user = getDirectoryService().getUser(foreignKey);
        if (user == null) {
            //TODO
            throw new ApplicationException("");
        }
        limit = correctLimit(limit);
        
        return getShovelLogic().getFriends(user.getUserId(), offset, limit + 1);
    }
    public IFriendship[] getFriends(String foreignKey, boolean withFollower, int page, int limit) throws ApplicationException {
        limit = correctLimit(limit);
        IUser user = getDirectoryService().getUser(foreignKey);
        IFriendship[] friends = getShovelLogic().getFriends(user.getUserId(), page * limit, limit + 1);
        if (friends != null && friends.length > 0) {
            Map<Long, IFriendship> friendMap = new HashMap<Long, IFriendship>();
            long[] passiveIds = new long[friends.length];
            int i = 0;
            for (IFriendship friendship : friends) {
                long passiveId = friendship.getPassiveId();
                passiveIds[i++] = passiveId;
                friendMap.put(passiveId, friendship);
            }
            IFriendship[] mines = getShovelLogic().getFriends(user.getUserId(), passiveIds);
            for (IFriendship mine : mines) {
                IFriendship peoples = friendMap.get(mine.getPassiveId());
                peoples.setFriend(mine);
            }
            if (withFollower) {
                IFriendship[] others = getShovelLogic().getFollowers(user.getUserId(), passiveIds);
                for (IFriendship other : others) {
                    IFriendship peoples = friendMap.get(other.getActiveId());
                    peoples.setFollower(other);
                }
            }
        }
        return friends;
    }
    public IFriendship getFriend(String foreignKey) throws ApplicationException {
        IUser active = getDirectoryService().getLoginUser();
        if (active == null) {
            return null;
        }
        return getShovelLogic().getFriendship(active.getUserId(), foreignKey);
    }
    public IFriendship getFriend(long passiveId) throws ApplicationException {
        IUser active = getDirectoryService().getLoginUser();
        if (active == null) {
            return null;
        }
        return getShovelLogic().getFriendship(active.getUserId(), passiveId);
    }
    public IFriendship getFollower(String foreignKey) throws ApplicationException {
        IUser passive = getDirectoryService().getLoginUser();
        if (passive == null) {
            return null;
        }
        IUser active = getDirectoryService().getUser(foreignKey);
        if (active == null) {
            throw new ApplicationException("");
        }
        return getShovelLogic().getFriendship(active.getUserId(), passive.getUserId());
    }
    public IFriendship[] getFriends(long[] passiveIds) throws ApplicationException {
        IUser user = getDirectoryService().getLoginUser();
        if (user == null) {
            throw new ApplicationException("");
        }
        return getShovelLogic().getFriends(user.getUserId(), passiveIds);
    }
    public IFriendship[] getFollowers(String foreignKey, boolean withFriends, int page, int limit) throws ApplicationException {
        IUser user = getDirectoryService().getUser(foreignKey);
        long passiveId = user.getUserId();
        limit = correctLimit(limit);
        user = getDirectoryService().getLoginUser();
        IFriendship[] followers = getShovelLogic().getFollowers(passiveId, false, page * limit, limit);
        if (followers != null && followers.length > 0) {
            Map<Long, IFriendship> followerMap = new HashMap<Long, IFriendship>();
            long[] passiveIds = new long[followers.length];
            int i = 0;
            for (IFriendship friendship : followers) {
                passiveId = friendship.getActiveId();
                passiveIds[i++] = passiveId;
                followerMap.put(passiveId, friendship);
            }
            if (withFriends) {
                IFriendship[] mines = getShovelLogic().getFriends(user.getUserId(), passiveIds);
                for (IFriendship mine : mines) {
                    IFriendship peoples = followerMap.get(mine.getPassiveId());
                    peoples.setFriend(mine);
                }
            }
        }
        return followers;
    }
    public IFriendship[] searchFriends(long userId, String keywords, SearchFriendsType type, int page, int limit) {
        String[] parsedKeywords = null;
        if (keywords != null && keywords.length() > 0) {
            parsedKeywords = keywords.split("\\s");
        }
        limit = correctLimit(limit);
        switch (type) {
        case FOLLOWING:
        default:
            return getShovelLogic().searchFollowing(userId, parsedKeywords, page * limit, limit);
        case RECIPROCAL_FOLLOW:
            return getShovelLogic().searchReciprocalFollow(userId, parsedKeywords, page * limit, limit);
        case FOLLOWING_ONLY:
            return getShovelLogic().searchFollowingOnly(userId, parsedKeywords, page * limit, limit);
        case FOLLOWERS_ONLY:
            return getShovelLogic().searchFollowersOnly(userId, parsedKeywords, page * limit, limit);
        }
    }
    public IFriendship[] getRequests(long activeId) throws ApplicationException {
        IUser passive = getDirectoryService().getLoginUser();
        if (passive == null) {
            return new IFriendship[0];
        }
        return getShovelLogic().getRequests(activeId, passive.getUserId());
    }
    public IFriendship[] getRequests(String foreignKey, int page, int limit) throws ApplicationException {
        IUser user = getDirectoryService().getLoginUser();
        limit = correctLimit(limit);
        user = getDirectoryService().getUser(foreignKey);
        return getShovelLogic().getRequests(user.getUserId(), page * limit, limit + 1);
    }
    public int countFriends(String foreignKey) {
        IUser user = getDirectoryService().getUser(foreignKey);
        long userId = user.getUserId();
        int friends = getShovelLogic().countPublicFriends(userId);
        friends += getShovelLogic().countProtectFriends(userId);
        return friends;
    }
    public int countFollows(String foreignKey) {
        IUser user = getDirectoryService().getUser(foreignKey);
        long userId = user.getUserId();
        int followers;
        if (user.isProtect()) {
            followers = getShovelLogic().countAcceptFollowers(userId);
        } else {
            followers = getShovelLogic().countAllFollowers(userId);
        }
        return followers;
    }
    public int countRequests(String foreignKey) {
        IUser user = getDirectoryService().getUser(foreignKey);
        long userId = user.getUserId();
        return getShovelLogic().countRequestFollowers(userId);
    }
    IFriendship createFriendship(IUser active, IUser passive) throws ApplicationException {
        if (passive == null || passive.isRemove()) {
            //TODO
            throw new ApplicationException("");
        }
        return getShovelLogic().createFriendship(active.getUserId(), passive.getUserId(), active.isProtect(), isMobilePhone());
    }
    public IFriendship createFriendship(String passiveId) throws ApplicationException {
        IDirectoryService directoryService = getDirectoryService();
        IUser active = directoryService.getLoginUser();
        IUser passive = directoryService.getUser(passiveId);
        
        return createFriendship(active, passive);
    }
    public IFriendship createFriendship(long passiveId) throws ApplicationException {
        IDirectoryService directoryService = getDirectoryService();
        IUser active = directoryService.getLoginUser();
        IUser passive = directoryService.getUser(passiveId);
        
        return createFriendship(active, passive);
    }
    IFriendship acceptFriendship(IUser active, IUser passive) throws ApplicationException {
        if (active == null) {
            //TODO
            throw new ApplicationException("");
        }
        //必ず許可する側がステータスの更新を保護してる
        if (!passive.isProtect()) {
            //TODO
            throw new ApplicationException("");
        }
        
        return getShovelLogic().acceptFriendship(active.getUserId(), passive.getUserId(), isMobilePhone());
    }
    public IFriendship acceptFriendship(String activeId) throws ApplicationException {
        IUser active = getDirectoryService().getUser(activeId);
        IUser passive = getDirectoryService().getLoginUser();
        
        return acceptFriendship(active, passive);
    }
    public IFriendship acceptFriendship(long activeId) throws ApplicationException {
        IUser active = getDirectoryService().getUser(activeId);
        IUser passive = getDirectoryService().getLoginUser();
        
        return acceptFriendship(active, passive);
    }
    public IFriendship updateNotification(long passiveId, boolean notify) throws ApplicationException {
        IUser active = getDirectoryService().getLoginUser();
        return getShovelLogic().updateNotification(active.getUserId(), passiveId, notify);
    }
    public IFriendship updateNotification(String passiveId, boolean notify) throws ApplicationException {
        IUser active = getDirectoryService().getLoginUser();
        return getShovelLogic().updateNotification(active.getUserId(), passiveId, notify);
    }
    IFriendship destroyFriendship(IUser active, IUser passive) throws ApplicationException {
        if (passive == null) {
            throw new ApplicationException("");
        }
        return getShovelLogic().destroyFriendship(active.getUserId(), passive.getUserId(), active.isProtect(), isMobilePhone());
    }
    public IFriendship destroyFriendship(long passiveId) throws ApplicationException {
        IUser active = getDirectoryService().getLoginUser();
        IUser passive = getDirectoryService().getUser(passiveId);
        
        return destroyFriendship(active, passive);
    }
    public IFriendship destroyFriendship(String passiveId) throws ApplicationException {
        IUser active = getDirectoryService().getLoginUser();
        IUser passive = getDirectoryService().getUser(passiveId);
        
        return destroyFriendship(active, passive);
    }
    IFriendship denyFriendship(IUser active, IUser passive) throws ApplicationException {
        if (active == null) {
            throw new ApplicationException("");
        }
        return getShovelLogic().denyFriendship(active.getUserId(), passive.getUserId(), isMobilePhone());
    }
    public IFriendship denyFriendship(long activeId) throws ApplicationException {
        IUser active = getDirectoryService().getUser(activeId);
        IUser passive = getDirectoryService().getLoginUser();
        
        return denyFriendship(active, passive);
    }
    public IFriendship denyFriendship(String activeId) throws ApplicationException {
        IUser passive = getDirectoryService().getUser(activeId);
        IUser active = getDirectoryService().getLoginUser();
        
        return denyFriendship(active, passive);
    }
    public IFriendship[] prepareForView(IFriendship[] friendships, int limit, boolean truncate) {
        if (truncate) {
            friendships = truncate(friendships, limit);
        }
        if (friendships == null || friendships.length <= 0) {
            return null;
        }
        
        Set<Long> userIdSet = new HashSet<Long>();
        for (IFriendship friendship : friendships) {
            userIdSet.add(friendship.getActiveId());
            userIdSet.add(friendship.getPassiveId());
        }
        
        long[] userIds = ArrayUtils.toPrimitive(userIdSet.toArray(new Long[userIdSet.size()]));
        IUser[] users = getDirectoryService().getUsers(userIds);
        Map<Long, IUser> userMap = new HashMap<Long, IUser>();
        for (IUser user : users) {
            IServerFile serverFile = user.getProfileImage();
            if (serverFile != null) {
                serverFile.prepareForView();
            }
            userMap.put(user.getUserId(), user);
        }
        
        for (IFriendship friendship : friendships) {
            long userId = friendship.getActiveId();
            friendship.setActive(userMap.get(userId));
            userId = friendship.getPassiveId();
            friendship.setPassive(userMap.get(userId));
        }
        
        return friendships;
    }
    
    /////
    
    public IFavorite getFavorite(long statusId) {
        IDirectoryService directoryService = getDirectoryService();
        IUser user = directoryService.getLoginUser();
        return getShovelLogic().getFavorite(statusId, user.getUserId());
    }
    public IFavorite[] getFavorites(long[] statusIds) {
        IDirectoryService directoryService = getDirectoryService();
        IUser user = directoryService.getLoginUser();
        return getShovelLogic().getFavorites(statusIds, user.getUserId());
    }
    public IFavorite[] getFavoritesByStatus(long statusId, int limit) {
        return getShovelLogic().getFavoritesByStatus(statusId, limit);
    }
    public IFavorite[] getFavoritesByStatuses(long[] statusIds, int limit) {
        return getShovelLogic().getFavoritesByStatuses(statusIds, limit);
    }
    public IFavorite createFavorite(String foreignKey) throws ApplicationException {
        IStatus status = getRecent(foreignKey);
        if (status == null) {
            return null;
        }
        return createFavorite(status.getStatusId());
    }
    public IFavorite createFavorite(long statusId) throws ApplicationException {
        if (getFavorite(statusId) != null) {
            return null;
        }
        IStatus status = getStatus(statusId);
        //これはない。。。か？まあ、念のため
        if (status.isRemove()) {
            return null;
        }
        
        IDirectoryService directoryService = getDirectoryService();
        IUser user = directoryService.getLoginUser();
        
        if (!isMobilePhone()) {
            directoryService.incrementFavorites(user.getUserId());
            if (status.getSenderId() != user.getUserId()) {
                directoryService.incrementGivenFavorites(status.getSenderId());
            }
        }
        return getShovelLogic().createFavorite(statusId, user.getUserId(), isMobilePhone());
    }
    public int destroyFavorite(long statusId) throws ApplicationException {
        if (getFavorite(statusId) == null) {
            return 0;
        }
        IStatus status = getStatus(statusId);
        if (status.isRemove()) {
            return 0;
        }
        
        IDirectoryService directoryService = getDirectoryService();
        IUser user = directoryService.getLoginUser();
        
        if (!isMobilePhone()) {
            directoryService.decrementFavorites(user.getUserId());
            if (status.getSenderId() != user.getUserId()) {
                directoryService.decrementGivenFavorites(status.getSenderId());
            }
        }
        int result = getShovelLogic().destroyFavorite(statusId, user.getUserId(), isMobilePhone());
        if (result <= 0) {
            throw new ApplicationException("");
        }
        return result;
    }
    
    /////
    
    public IDevice createDevice(String deviceType, String address) {
        IUser user = getDirectoryService().getLoginUser();
        IDevice device = user.getDevice();
        device.setAddress(address);
        device.setAvailabilityType(INACTIVATION);
        device.setType(deviceType);
        device.setUserId(user.getUserId());
        if (device.getDeviceId() > 0) {
            getShovelLogic().updateDevice(device);
        } else {
            getShovelLogic().createDevice(device);
        }
        return device;
    }
    public int activateDevice() {
        IUser user = getDirectoryService().getLoginUser();
        IDevice device = user.getDevice();
        device.setAvailabilityType(ON);
        return getShovelLogic().updateDevice(device);
    }
    public int updateDevice(AvailabilityType availabilityType) {
        IUser user = getDirectoryService().getLoginUser();
        IDevice device = user.getDevice();
        device.setAvailabilityType(availabilityType);
        return getShovelLogic().updateDevice(device);
    }
    public int resetDevice() {
        IUser user = getDirectoryService().getLoginUser();
        IDevice device = user.getDevice();
        device.setAddress("");
        device.setAvailabilityType(INACTIVATION);
        device.setDeviceType(UNKNOWN);
        return getShovelLogic().updateDevice(device);
    }
    
    /////
    
    public IDedicatedClient createClient(String key, String url, String version) {
        return getShovelLogic().createClient(key, url, version);
    }
    public int updateClient(IDedicatedClient client) {
        return getShovelLogic().updateClient(client);
    }
    public IDedicatedClient getClient(String key) {
        return getShovelLogic().getClient(key);
    }
    public IDedicatedClient[] getClients(String[] keys) {
        return getShovelLogic().getClients(keys);
    }
    public int removeClient(String key) {
        return getShovelLogic().removeClient(key);
    }
    
    /////
    
    public IUser[] prepareForView(IUser[] users, int limit) {
        users = truncate(users, limit);
        if (users == null || users.length <= 0) {
            return new IUser[0];
        }
        
        for (IUser user : users) {
            IServerFile profileImage = user.getProfileImage();
            if (profileImage != null) {
                profileImage.prepareForView();
            }
            IServerFile backgroundImage = user.getBackgroundImage();
            if (backgroundImage != null) {
                backgroundImage.prepareForView();
            }
        }
        return users;
    }
    public IUser prepareForView(IUser user) throws ApplicationException {
        if (user == null) {
            return null;
        }
        
        String foreignKey = user.getForeignKey();
        user.setFollowers(countFollows(foreignKey));
        user.setFollowing(countFriends(foreignKey));
        user.setRequests(countRequests(foreignKey));
        user.setRecentlyStatus(getRecent(user.getUserId()));
        
        IServerFile profileImage = user.getProfileImage();
        if (profileImage != null) {
            profileImage.prepareForView();
        }
        IServerFile backgroundImage = user.getBackgroundImage();
        if (backgroundImage != null) {
            backgroundImage.prepareForView();
        }
        return user;
    }
    
    /////
    
    IMobilePhone mobilePhone_;
    
    public IMobilePhone getMobilePhone() throws ApplicationException {
        if (mobilePhone_ == null) {
            mobilePhone_ = getMobilePhoneLogic().createMobilePhone(null);
        }
        return mobilePhone_;
    }
    public boolean isMobilePhone() throws ApplicationException {
        return getMobilePhone() != null;
    }
    
    /////
    
    IDirectoryService directoryService_;
    IServerFileService serverFileService_;
    
    public void setDirectoryService(IDirectoryService directoryService) {
        directoryService_ = directoryService;
    }
    public IDirectoryService getDirectoryService() {
        return directoryService_;
    }
    public void setServerFileService(IServerFileService serverFileService) {
        serverFileService_ = serverFileService;
    }
    public IServerFileService getServerFileService() {
        return serverFileService_;
    }
    
    HttpServletRequest request_;
    HttpServletResponse response_;
    
    public HttpServletRequest getRequest() {
        if (request_ == null) {
            request_ = (HttpServletRequest)getContainer().getComponent(REQUEST_NAME);
        }
        return request_;
    }
    public HttpServletResponse getResponse() {
        if (response_ == null) {
            response_ = (HttpServletResponse)getContainer().getComponent(RESPONSE_NAME);
        }
        return response_;
    }
    
    S2Container container_;
    IShovelLogic shovelLogic_;
    IMobilePhoneLogic mobilePhoneLogic_;
    
    public void setContainer(S2Container container) {
        container_ = container;
    }
    S2Container getContainer() {
        return container_;
    }
    public void setShovelLogic(IShovelLogic shovelLogic) {
        shovelLogic_ = shovelLogic;
    }
    IShovelLogic getShovelLogic() {
        return shovelLogic_;
    }
    public void setMobilePhoneLogic(IMobilePhoneLogic mobilePhoneLogic) {
        mobilePhoneLogic_ = mobilePhoneLogic;
    }
    public IMobilePhoneLogic getMobilePhoneLogic() {
        return mobilePhoneLogic_;
    }
}
