/*
 * Copyright 2009-2013 the Fess Project and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

package jp.sf.fess.action.admin;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import jp.sf.fess.Constants;
import jp.sf.fess.crud.util.SAStrutsUtil;
import jp.sf.fess.form.admin.DocumentForm;
import jp.sf.fess.helper.SystemHelper;
import jp.sf.fess.helper.WebManagementHelper;

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.ORDER;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.codelibs.core.util.DynamicProperties;
import org.codelibs.sastruts.core.annotation.Token;
import org.codelibs.sastruts.core.exception.SSCActionMessagesException;
import org.codelibs.solr.lib.SolrGroup;
import org.codelibs.solr.lib.SolrGroupManager;
import org.codelibs.solr.lib.SolrLibConstants;
import org.codelibs.solr.lib.policy.QueryType;
import org.codelibs.solr.lib.policy.impl.StatusPolicyImpl;
import org.seasar.framework.container.SingletonS2Container;
import org.seasar.framework.util.StringUtil;
import org.seasar.struts.annotation.ActionForm;
import org.seasar.struts.annotation.Execute;
import org.seasar.struts.taglib.S2Functions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DocumentAction implements Serializable {
    private static final Logger logger = LoggerFactory
            .getLogger(DocumentAction.class);

    private static final long serialVersionUID = 1L;

    @ActionForm
    @Resource
    protected DocumentForm documentForm;

    @Resource
    protected DynamicProperties solrProperties;

    @Resource
    protected SolrGroupManager solrGroupManager;

    @Resource
    protected WebManagementHelper webManagementHelper;

    @Resource
    protected SystemHelper systemHelper;

    public String getHelpLink() {
        return systemHelper.getHelpLink("document");
    }

    protected String showIndex(final boolean redirect) {
        final Map<String, DynamicProperties> groupPropMap = new HashMap<String, DynamicProperties>();
        for (final String groupName : solrGroupManager.getSolrGroupNames()) {
            final DynamicProperties props = SingletonS2Container
                    .getComponent(groupName + "Properties");
            if (props != null) {
                groupPropMap.put(groupName, props);
            }
        }

        final String[] serverNames = solrGroupManager.getSolrServerNames();
        for (final String name : serverNames) {
            final String[] names = name.split(":");
            if (names.length == 2) {
                final Map<String, String> map = new HashMap<String, String>(4);
                map.put("groupName", names[0]);
                map.put("serverName", names[1]);
                final DynamicProperties props = groupPropMap.get(names[0]);
                if (props != null) {
                    String status = props
                            .getProperty(StatusPolicyImpl.STATUS_PREFIX
                                    + names[1]);
                    if (StringUtil.isBlank(status)) {
                        status = StatusPolicyImpl.ACTIVE;
                    }
                    map.put("status", status);
                    String index = props
                            .getProperty(StatusPolicyImpl.INDEX_PREFIX
                                    + names[1]);
                    if (StringUtil.isBlank(index)) {
                        index = StatusPolicyImpl.READY;
                    }
                    map.put("index", index);
                } else {
                    map.put("status", StatusPolicyImpl.ACTIVE);
                    map.put("index", StatusPolicyImpl.READY);
                }
                documentForm.serverStatusList.add(map);
            }
        }
        // select group status
        documentForm.currentServerForSelect = solrProperties
                .getProperty(SolrLibConstants.SELECT_GROUP);
        final SolrGroup selectSolrGroup = solrGroupManager
                .getSolrGroup(documentForm.currentServerForSelect);
        if (selectSolrGroup != null
                && selectSolrGroup.isActive(QueryType.QUERY)) {
            documentForm.currentServerStatusForSelect = Constants.ACTIVE;
        } else {
            documentForm.currentServerStatusForSelect = Constants.INACTIVE;
        }

        // update group status
        documentForm.currentServerForUpdate = solrProperties
                .getProperty(SolrLibConstants.SELECT_GROUP);
        final SolrGroup updateSolrGroup = solrGroupManager
                .getSolrGroup(documentForm.currentServerForUpdate);
        if (updateSolrGroup != null
                && updateSolrGroup.isActive(QueryType.QUERY)) {
            documentForm.currentServerStatusForUpdate = Constants.ACTIVE;
        } else {
            documentForm.currentServerStatusForUpdate = Constants.INACTIVE;
        }

        if (redirect) {
            return "index?redirect=true";
        } else {
            return "index.jsp";
        }
    }

    @Token(save = true, validate = false)
    @Execute(validator = false)
    public String index() {
        return showIndex(false);
    }

    @Token(save = false, validate = true)
    @Execute(validator = true, input = "index")
    public String commit() {
        if (systemHelper.isCrawlProcessRunning()) {
            throw new SSCActionMessagesException(
                    "errors.failed_to_start_solr_process_because_of_running");
        }
        final SolrGroup solrGroup = solrGroupManager
                .getSolrGroup(documentForm.groupName);
        if (solrGroup == null) {
            throw new SSCActionMessagesException(
                    "errors.failed_to_commit_solr_index");
        } else {
            final Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    if (systemHelper.readyCrawlProcess()) {
                        final long execTime = System.currentTimeMillis();
                        try {
                            systemHelper.updateStatus(solrGroup, QueryType.ADD);
                            solrGroup.commit();
                            systemHelper.updateStatus(solrGroup,
                                    QueryType.COMMIT);

                            if (logger.isInfoEnabled()) {
                                logger.info("[EXEC TIME] index commit time: "
                                        + (System.currentTimeMillis() - execTime)
                                        + "ms");
                            }
                        } catch (final Exception e) {
                            logger.error("Failed to commit index.", e);
                        } finally {
                            systemHelper.finishCrawlProcess();
                        }
                    } else {
                        if (logger.isInfoEnabled()) {
                            logger.info("could not start index cleanup process"
                                    + " because of running solr process.");
                        }
                    }
                }
            });
            thread.start();
            SAStrutsUtil.addSessionMessage("success.commit_solr_index");
        }
        return showIndex(true);
    }

    @Token(save = false, validate = true)
    @Execute(validator = true, input = "index")
    public String optimize() {
        if (systemHelper.isCrawlProcessRunning()) {
            throw new SSCActionMessagesException(
                    "errors.failed_to_start_solr_process_because_of_running");
        }
        final SolrGroup solrGroup = solrGroupManager
                .getSolrGroup(documentForm.groupName);
        if (solrGroup == null) {
            throw new SSCActionMessagesException(
                    "errors.failed_to_optimize_solr_index");
        } else {
            final Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    if (systemHelper.readyCrawlProcess()) {
                        final long execTime = System.currentTimeMillis();
                        try {
                            systemHelper.updateStatus(solrGroup, QueryType.ADD);
                            solrGroup.optimize();
                            systemHelper.updateStatus(solrGroup,
                                    QueryType.OPTIMIZE);
                            if (logger.isInfoEnabled()) {
                                logger.info("[EXEC TIME] index optimize time: "
                                        + (System.currentTimeMillis() - execTime)
                                        + "ms");
                            }
                        } catch (final Exception e) {
                            logger.error("Failed to optimize index.", e);
                        } finally {
                            systemHelper.finishCrawlProcess();
                        }
                    } else {
                        if (logger.isInfoEnabled()) {
                            logger.info("could not start index cleanup process"
                                    + " because of running solr process.");
                        }
                    }
                }
            });
            thread.start();
            SAStrutsUtil.addSessionMessage("success.optimize_solr_index");
        }
        return showIndex(true);
    }

    @Token(save = false, validate = true)
    @Execute(validator = true, input = "index")
    public String delete() {
        String deleteQuery;
        if ("*".equals(documentForm.sessionId)) {
            deleteQuery = "*:*";
        } else {
            deleteQuery = "segment:" + documentForm.sessionId;
        }
        return deleteByQuery(deleteQuery);
    }

    @Token(save = false, validate = true)
    @Execute(validator = true, input = "index")
    public String confirmByUrl() {
        final String confirmQuery = "url:\"" + documentForm.deleteUrl + "\"";
        return "/admin/searchList/search?query=" + S2Functions.u(confirmQuery)
                + "&redirect=true";
    }

    @Token(save = false, validate = true)
    @Execute(validator = true, input = "index")
    public String deleteByUrl() {
        final String deleteUrl = documentForm.deleteUrl;
        final String deleteQuery = "url:\"" + deleteUrl + "\"";
        return deleteByQuery(deleteQuery);
    }

    private String deleteByQuery(final String deleteQuery) {
        if (systemHelper.isCrawlProcessRunning()) {
            throw new SSCActionMessagesException(
                    "errors.failed_to_start_solr_process_because_of_running");
        }
        final SolrGroup solrGroup = solrGroupManager
                .getSolrGroup(documentForm.groupName);
        if (solrGroup == null) {
            throw new SSCActionMessagesException(
                    "errors.failed_to_delete_solr_index");
        } else {
            final Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    if (systemHelper.readyCrawlProcess()) {
                        final long execTime = System.currentTimeMillis();
                        try {
                            systemHelper.updateStatus(solrGroup,
                                    QueryType.DELETE);
                            solrGroup.deleteByQuery(deleteQuery);
                            solrGroup.commit();
                            systemHelper.updateStatus(solrGroup,
                                    QueryType.OPTIMIZE);
                            if (logger.isInfoEnabled()) {
                                logger.info("[EXEC TIME] index cleanup time: "
                                        + (System.currentTimeMillis() - execTime)
                                        + "ms");
                            }
                        } catch (final Exception e) {
                            logger.error("Failed to delete index (query="
                                    + deleteQuery + ").", e);
                        } finally {
                            systemHelper.finishCrawlProcess();
                        }
                    } else {
                        if (logger.isInfoEnabled()) {
                            logger.info("could not start index cleanup process"
                                    + " because of running solr process.");
                        }
                    }
                }
            });
            thread.start();
            SAStrutsUtil.addSessionMessage("success.delete_solr_index");
        }
        return showIndex(true);
    }

    public List<Map<String, Object>> getGroupActionItems() {
        final List<Map<String, Object>> groupActionItems = new ArrayList<Map<String, Object>>();
        try {
            for (final String groupName : solrGroupManager.getSolrGroupNames()) {
                final Map<String, Object> map = new HashMap<String, Object>();
                map.put("groupName", groupName);
                final SessionIdList<Map<String, String>> sessionIdList = getSessionIdList(groupName);
                map.put("sessionIdItems", sessionIdList);
                map.put("totalCount", sessionIdList.getTotalCount());
                groupActionItems.add(map);
            }
        } catch (final Exception e) {
            logger.info("could not get server groups.", e);
        }
        return groupActionItems;
    }

    protected SessionIdList<Map<String, String>> getSessionIdList(
            final String groupName) {
        final SessionIdList<Map<String, String>> sessionIdList = new SessionIdList<Map<String, String>>();

        SolrGroup serverGroup;
        try {
            serverGroup = solrGroupManager.getSolrGroup(groupName);
        } catch (final Exception e) {
            if (logger.isInfoEnabled()) {
                logger.info(e.getMessage());
            }
            return sessionIdList;
        }

        final SolrQuery query = new SolrQuery();
        query.setQuery("*:*");
        query.setFacet(true);
        query.addFacetField("segment");
        query.addSort("segment", ORDER.desc);

        final QueryResponse queryResponse = serverGroup.query(query);
        final List<FacetField> facets = queryResponse.getFacetFields();
        for (final FacetField facet : facets) {
            final List<FacetField.Count> facetEntries = facet.getValues();
            if (facetEntries != null) {
                for (final FacetField.Count fcount : facetEntries) {
                    final Map<String, String> map = new HashMap<String, String>(
                            3);
                    map.put("label",
                            fcount.getName() + " (" + fcount.getCount() + ")");
                    map.put("value", fcount.getName());
                    map.put("count", Long.toString(fcount.getCount()));
                    sessionIdList.add(map);
                    sessionIdList.addTotalCount(fcount.getCount());
                }
            }
        }
        return sessionIdList;
    }

    public boolean isSolrProcessRunning() {
        return systemHelper.isCrawlProcessRunning();
    }

    public String getRunningSessionId() {
        return systemHelper.getSessionId();
    }

    private static class SessionIdList<E> extends ArrayList<E> {

        private static final long serialVersionUID = 1L;

        private long totalCount = 0;

        public void addTotalCount(final long count) {
            totalCount += count;
        }

        public long getTotalCount() {
            return totalCount;
        }

    }
}