/*******************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.ofbiz.workeffort.content;

import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.base.util.GeneralException;
import org.apache.ofbiz.base.util.GeneralRuntimeException;
import org.apache.ofbiz.base.util.StringUtil;
import org.apache.ofbiz.base.util.UtilCodec;
import org.apache.ofbiz.base.util.UtilHttp;
import org.apache.ofbiz.base.util.UtilValidate;
import org.apache.ofbiz.base.util.cache.UtilCache;
import org.apache.ofbiz.content.content.ContentWorker;
import org.apache.ofbiz.content.content.ContentWrapper;
import org.apache.ofbiz.entity.Delegator;
import org.apache.ofbiz.entity.GenericValue;
import org.apache.ofbiz.entity.model.ModelEntity;
import org.apache.ofbiz.entity.model.ModelUtil;
import org.apache.ofbiz.entity.util.EntityQuery;
import org.apache.ofbiz.entity.util.EntityUtilProperties;
import org.apache.ofbiz.service.LocalDispatcher;

/**
 * WorkEffortContentWrapper; gets work effort content for display
 */
public class WorkEffortContentWrapper implements ContentWrapper {

    private static final String MODULE = WorkEffortContentWrapper.class.getName();

    private static final UtilCache<String, String> WORK_EFFORT_CONTENT_CACHE = UtilCache.createUtilCache("workeffort.content.rendered", true);

    private LocalDispatcher dispatcher;
    private GenericValue workEffort;
    private Locale locale;
    private String mimeTypeId;

    public WorkEffortContentWrapper(LocalDispatcher dispatcher, GenericValue workEffort, Locale locale, String mimeTypeId) {
        this.dispatcher = dispatcher;
        this.workEffort = workEffort;
        this.locale = locale;
        this.mimeTypeId = mimeTypeId;
    }

    public WorkEffortContentWrapper(GenericValue workEffort, HttpServletRequest request) {
        this.workEffort = workEffort;
        this.locale = UtilHttp.getLocale(request);
        this.mimeTypeId = EntityUtilProperties.getPropertyValue("content", "defaultMimeType", "text/html; charset=utf-8",
                (Delegator) request.getAttribute("delegator"));
    }

    /** interface implementation(s) */
    public String get(String workEffortContentId, boolean useCache, String encoderType) {
        return getWorkEffortContentAsText(workEffort, workEffortContentId, locale, mimeTypeId, workEffort.getDelegator(), dispatcher, useCache,
                encoderType);
    }

    /**
     * Get the most current content data by the defined type
     * @param contentTypeId Type of content to return
     * @return String containing the content data
     */
    @Override
    public StringUtil.StringWrapper get(String contentTypeId, String encoderType) {
        return StringUtil.makeStringWrapper(get(contentTypeId, true, encoderType));
    }

    /**
     * Get the ID from the most current content data by the defined type
     * @param contentTypeId Type of content to return
     * @return String containing the contentId
     */
    public String getContentId(String contentTypeId) {
        GenericValue workEffortContent = getFirstWorkEffortContentByType(null, workEffort, contentTypeId, workEffort.getDelegator(), true);
        if (workEffortContent != null) {
            return workEffortContent.getString("contentId");
        } else {
            return null;
        }
    }

    /**
     * Get the name of the most current content data by the defined type
     * @param contentTypeId Type of content to return
     * @return String containing the name of the content record
     */
    public String getContentName(String contentTypeId) {
        GenericValue workEffortContent = getFirstWorkEffortContentByType(null, workEffort, contentTypeId, workEffort.getDelegator(), true);
        if (workEffortContent != null) {
            GenericValue content;
            try {
                content = workEffortContent.getRelatedOne("Content", false);
            } catch (GeneralException e) {
                Debug.logError(e, MODULE);
                return null;
            }

            if (content != null) {
                return content.getString("contentName");
            }
        }

        return null;
    }

    /**
     * Get the fromDate from teh most current content data by the defined type
     * @param contentTypeId Type of content to return
     * @return Timestamp of the fromDate field for this content type
     */
    public Timestamp getFromDate(String contentTypeId) {
        GenericValue workEffortContent = getFirstWorkEffortContentByType(null, workEffort, contentTypeId, workEffort.getDelegator(), true);
        if (workEffortContent != null) {
            return workEffortContent.getTimestamp("fromDate");
        } else {
            return null;
        }
    }

    /**
     * Gets data resource id.
     * @param contentTypeId the content type id
     * @return the data resource id
     */
    public String getDataResourceId(String contentTypeId) {
        GenericValue workEffortContent = getFirstWorkEffortContentByType(null, workEffort, contentTypeId, workEffort.getDelegator(), true);
        if (workEffortContent != null) {
            GenericValue content;
            try {
                content = workEffortContent.getRelatedOne("Content", false);
            } catch (GeneralException e) {
                Debug.logError(e, MODULE);
                return null;
            }
            if (content != null) {
                GenericValue dataResource;
                try {
                    dataResource = content.getRelatedOne("DataResource", false);
                } catch (GeneralException e) {
                    Debug.logError(e, MODULE);
                    return null;
                }
                if (dataResource != null) {
                    return dataResource.getString("dataResourceId");
                }
            }
        }

        return null;
    }

    /**
     * Gets list.
     * @param contentTypeId the content type id
     * @return the list
     */
    public List<String> getList(String contentTypeId) {
        try {
            return getWorkEffortContentTextList(workEffort, contentTypeId, locale, mimeTypeId, workEffort.getDelegator(), dispatcher);
        } catch (Exception e) {
            Debug.logError(e, MODULE);
            return null;
        }
    }

    /**
     * Gets type description.
     * @param contentTypeId the content type id
     * @return the type description
     */
    public String getTypeDescription(String contentTypeId) {
        Delegator delegator = null;
        if (workEffort != null) {
            delegator = workEffort.getDelegator();
        }

        if (delegator != null) {
            GenericValue contentType = null;
            try {
                contentType = EntityQuery.use(delegator).from("WorkEffortContentType").where("workEffortContentTypeId", contentTypeId).cache()
                        .queryOne();
            } catch (GeneralException e) {
                Debug.logError(e, MODULE);
            }

            if (contentType != null) {
                return contentType.getString("description");
            }
        }

        return null;
    }

    /**
     * Gets content.
     * @param contentId   the content id
     * @param useCache    the use cache
     * @param encoderType the encoder type
     * @return the content
     */
    public String getContent(String contentId, boolean useCache, String encoderType) {
        return getWorkEffortContentAsText(workEffort, contentId, null, locale, mimeTypeId, workEffort.getDelegator(), dispatcher,
                useCache, encoderType);
    }

    /**
     * Gets content.
     * @param contentId   the content id
     * @param encoderType the encoder type
     * @return the content
     */
    public String getContent(String contentId, String encoderType) {
        return getContent(contentId, true, encoderType);
    }

    // static method helpers
    public static String getWorkEffortContentAsText(GenericValue workEffort, String workEffortContentTypeId, HttpServletRequest request,
                                                    String encoderType) {
        LocalDispatcher dispatcher = (LocalDispatcher) request.getAttribute("dispatcher");
        String mimeTypeId = EntityUtilProperties.getPropertyValue("content", "defaultMimeType", "text/html; charset=utf-8",
                workEffort.getDelegator());
        return getWorkEffortContentAsText(workEffort, workEffortContentTypeId, UtilHttp.getLocale(request), mimeTypeId, workEffort.getDelegator(),
                dispatcher, true, encoderType);
    }

    public static String getWorkEffortContentAsText(GenericValue workEffort, String workEffortContentTypeId, Locale locale,
                                                    LocalDispatcher dispatcher, String encoderType) {
        return getWorkEffortContentAsText(workEffort, workEffortContentTypeId, locale, null, null, dispatcher, true, encoderType);
    }

    public static String getWorkEffortContentAsText(GenericValue workEffort, String workEffortContentTypeId,
            Locale locale, String mimeTypeId, Delegator delegator, LocalDispatcher dispatcher, boolean useCache, String encoderType) {
        return getWorkEffortContentAsText(workEffort, null, workEffortContentTypeId, locale, mimeTypeId, delegator, dispatcher, useCache,
                encoderType);
    }

    public static String getWorkEffortContentAsText(GenericValue workEffort, String contentId, String workEffortContentTypeId,
            Locale locale, String mimeTypeId, Delegator delegator, LocalDispatcher dispatcher, boolean useCache, String encoderType) {
        if (workEffort == null) {
            return null;
        }

        UtilCodec.SimpleEncoder encoder = UtilCodec.getEncoder(encoderType);
        String candidateFieldName = ModelUtil.dbNameToVarName(workEffortContentTypeId);
        String cacheKey;
        if (contentId != null) {
            cacheKey = contentId + CACHE_KEY_SEPARATOR + locale + CACHE_KEY_SEPARATOR + mimeTypeId
                    + CACHE_KEY_SEPARATOR + workEffort.get("workEffortId");
        } else {
            cacheKey = workEffortContentTypeId + CACHE_KEY_SEPARATOR + locale + CACHE_KEY_SEPARATOR + mimeTypeId
                    + CACHE_KEY_SEPARATOR + workEffort.get("workEffortId");
        }

        try {
            if (useCache) {
                String cachedValue = WORK_EFFORT_CONTENT_CACHE.get(cacheKey);
                if (cachedValue != null) {
                    return cachedValue;
                }
            }

            Writer outWriter = new StringWriter();
            // Use cache == true to have entity-cache managed content from cache while (not managed) rendered cache above
            // may be configured with short expire time
            getWorkEffortContentAsText(contentId, null, workEffort, workEffortContentTypeId, locale, mimeTypeId,
                    delegator, dispatcher, outWriter, true);
            String outString = outWriter.toString();
            if (UtilValidate.isEmpty(outString)) {
                outString = workEffort.getModelEntity().isField(candidateFieldName) ? workEffort.getString(candidateFieldName) : "";
                outString = outString == null ? "" : outString;
            }
            outString = encoder.sanitize(outString, null);
            if (WORK_EFFORT_CONTENT_CACHE != null) {
                WORK_EFFORT_CONTENT_CACHE.put(cacheKey, outString);
            }
            return outString;
        } catch (GeneralException | IOException e) {
            Debug.logError(e, "Error rendering WorkEffortContent, inserting empty String", MODULE);
            String candidateOut = workEffort.getModelEntity().isField(candidateFieldName) ? workEffort.getString(candidateFieldName) : "";
            return candidateOut == null ? "" : encoder.sanitize(candidateOut, null);
        }
    }

    public static void getWorkEffortContentAsText(String contentId, String workEffortId, GenericValue workEffort, String workEffortContentTypeId,
                                                  Locale locale, String mimeTypeId, Delegator delegator, LocalDispatcher dispatcher,
                                                  Writer outWriter) throws GeneralException, IOException {
        getWorkEffortContentAsText(contentId, null, workEffort, workEffortContentTypeId, locale, mimeTypeId, delegator, dispatcher, outWriter, true);
    }

    public static void getWorkEffortContentAsText(String contentId, String workEffortId, GenericValue workEffort, String workEffortContentTypeId,
            Locale locale, String mimeTypeId, Delegator delegator, LocalDispatcher dispatcher, Writer outWriter, boolean cache)
            throws GeneralException, IOException {
        if (workEffortId == null && workEffort != null) {
            workEffortId = workEffort.getString("workEffortId");
        }

        if (delegator == null && workEffort != null) {
            delegator = workEffort.getDelegator();
        }

        if (UtilValidate.isEmpty(mimeTypeId)) {
            mimeTypeId = EntityUtilProperties.getPropertyValue("content", "defaultMimeType", "text/html; charset=utf-8", delegator);
        }

        if (delegator == null) {
            throw new GeneralRuntimeException("Unable to find a delegator to use!");
        }

        // Honor work effort content over WorkEffort entity fields.
        GenericValue workEffortContent;
        if (contentId != null) {
            workEffortContent = EntityQuery.use(delegator).from("WorkEffortContent").where("workEffortId", workEffortId, "contentId", contentId)
                    .cache(cache).queryOne();
        } else {
            workEffortContent = getFirstWorkEffortContentByType(workEffortId, workEffort, workEffortContentTypeId, delegator, cache);
        }
        if (workEffortContent != null) {
            // when rendering the product content, always include the Product and ProductContent records that this comes from
            Map<String, Object> inContext = new HashMap<>();
            inContext.put("workEffort", workEffort);
            inContext.put("workEffortContent", workEffortContent);
            ContentWorker.renderContentAsText(dispatcher, workEffortContent.getString("contentId"), outWriter, inContext, locale, mimeTypeId,
                    null, null, false);
            return;
        }
        // check for workeffort field
        String candidateFieldName = ModelUtil.dbNameToVarName(workEffortContentTypeId);
        ModelEntity workEffortModel = delegator.getModelEntity("WorkEffort");
        if (workEffortModel != null && workEffortModel.isField(candidateFieldName)) {
            if (workEffort == null) {
                workEffort = EntityQuery.use(delegator).from("WorkEffort").where("workEffortId", workEffortId).cache().queryOne();
            }
            if (workEffort != null) {
                String candidateValue = workEffort.getString(candidateFieldName);
                if (UtilValidate.isNotEmpty(candidateValue)) {
                    outWriter.write(candidateValue);
                    return;
                }
            }
        }
    }
    public static List<String> getWorkEffortContentTextList(GenericValue workEffort, String workEffortContentTypeId, Locale locale,
            String mimeTypeId, Delegator delegator, LocalDispatcher dispatcher) throws GeneralException, IOException {
        List<GenericValue> partyContentList = EntityQuery.use(delegator).from("WorkEffortContent")
                .where("workEffortId", workEffort.getString("partyId"), "workEffortContentTypeId", workEffortContentTypeId)
                .orderBy("-fromDate")
                .cache(true)
                .filterByDate()
                .queryList();

        List<String> contentList = new LinkedList<>();
        if (partyContentList != null) {
            for (GenericValue workEffortContent: partyContentList) {
                StringWriter outWriter = new StringWriter();
                Map<String, Object> inContext = new HashMap<>();
                inContext.put("workEffort", workEffort);
                inContext.put("workEffortContent", workEffortContent);
                ContentWorker.renderContentAsText(dispatcher, workEffortContent.getString("contentId"), outWriter, inContext, locale,
                        mimeTypeId, null, null, false);
                contentList.add(outWriter.toString());
            }
        }

        return contentList;
    }

    public static GenericValue getFirstWorkEffortContentByType(String workEffortId, GenericValue workEffort, String workEffortContentTypeId,
                                                               Delegator delegator, boolean cache) {
        if (workEffortId == null && workEffort != null) {
            workEffortId = workEffort.getString("workEffortId");
        }

        if (delegator == null && workEffort != null) {
            delegator = workEffort.getDelegator();
        }

        if (delegator == null) {
            throw new IllegalArgumentException("Delegator missing");
        }

        GenericValue workEffortContent = null;
        try {
            workEffortContent = EntityQuery.use(delegator).from("WorkEffortContent")
                                    .where("workEffortId", workEffortId, "workEffortContentTypeId", workEffortContentTypeId)
                                    .orderBy("-fromDate")
                                    .filterByDate()
                                    .cache(cache)
                                    .queryFirst();
        } catch (GeneralException e) {
            Debug.logError(e, MODULE);
        }
        return workEffortContent;
    }

    public static WorkEffortContentWrapper makeWorkEffortContentWrapper(GenericValue workEffort, HttpServletRequest request) {
        return new WorkEffortContentWrapper(workEffort, request);
    }
}
