/*
 *  Copyright 2013 Anyware Services
 *
 *  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 org.ametys.cms.repository;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.cocoon.acting.ServiceableAction;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Redirector;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.SourceResolver;
import org.apache.cocoon.servlet.multipart.Part;
import org.apache.cocoon.servlet.multipart.PartOnDisk;
import org.apache.cocoon.servlet.multipart.RejectedPart;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.input.BOMInputStream;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.tika.parser.txt.CharsetDetector;
import org.supercsv.io.CsvListReader;
import org.supercsv.io.ICsvListReader;
import org.supercsv.prefs.CsvPreference;

import org.ametys.cms.contenttype.ContentType;
import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
import org.ametys.cms.contenttype.ContentTypesHelper;
import org.ametys.cms.contenttype.MetadataDefinition;
import org.ametys.cms.contenttype.MetadataType;
import org.ametys.cms.workflow.AbstractContentWorkflowComponent;
import org.ametys.cms.workflow.ContentWorkflowHelper;
import org.ametys.cms.workflow.EditContentFunction;
import org.ametys.core.cocoon.JSonReader;
import org.ametys.core.util.JSONUtils;
import org.ametys.plugins.repository.AmetysRepositoryException;
import org.ametys.plugins.workflow.AbstractWorkflowComponent;

import com.opensymphony.workflow.WorkflowException;

/**
 * Imports simple contents from a CSV file
 *
 */
public class ImportSimpleContentsAction extends ServiceableAction
{
    private static final String[] _ALLOWED_EXTENSIONS = new String[] {"txt", "csv"};
    
    private ContentTypeExtensionPoint _contentTypeEP;
    private ContentWorkflowHelper _contentWorkflowHelper;
    private ContentTypeExtensionPoint _cTypeEP;

    private ContentTypesHelper _contentTypeHelper;

    private JSONUtils _jsonUtils;
    
    @Override
    public void service(ServiceManager smanager) throws ServiceException
    {
        super.service(smanager);
        _contentTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
        _contentTypeHelper = (ContentTypesHelper) smanager.lookup(ContentTypesHelper.ROLE);
        _contentWorkflowHelper = (ContentWorkflowHelper) smanager.lookup(ContentWorkflowHelper.ROLE);
        _cTypeEP = (ContentTypeExtensionPoint) smanager.lookup(ContentTypeExtensionPoint.ROLE);
        _jsonUtils = (JSONUtils) smanager.lookup(JSONUtils.ROLE);
    }
    
    @Override
    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
    {
        Map<String, Object> result = new HashMap<>();
        
        Request request = ObjectModelHelper.getRequest(objectModel);
        String cTypeId = request.getParameter("contentType");
        String lang = request.getParameter("contentLanguage");
        
        ContentType cType = _contentTypeEP.getExtension(cTypeId);
        if (!cType.isSimple())
        {
            result.put("error", "invalid-content-type");
            request.setAttribute(JSonReader.OBJECT_TO_READ, result);
            return EMPTY_MAP;
        }
        
        Part part = (Part) request.get("importFile");
        if (part instanceof RejectedPart)
        {
            result.put("error", "rejected-file");
            request.setAttribute(JSonReader.OBJECT_TO_READ, result);
            return EMPTY_MAP;
        }
        
        PartOnDisk uploadedFilePart = (PartOnDisk) part;
        File uploadedFile = (uploadedFilePart != null) ? uploadedFilePart.getFile() : null;
        String filename = (uploadedFilePart != null) ? uploadedFilePart.getFileName().toLowerCase() : null;
        
        if (!FilenameUtils.isExtension(filename, _ALLOWED_EXTENSIONS))
        {
            result.put("error", "invalid-extension");
            request.setAttribute(JSonReader.OBJECT_TO_READ, result);
            return EMPTY_MAP;
        }
        
        String workflowName = request.getParameter("workflowName");
        int initWorkflowActionId = Integer.valueOf(request.getParameter("initWorkflowActionId"));
        int workflowEditActionId = Integer.valueOf(request.getParameter("editWorkflowActionId"));

        try
        {
            List<String> contentIds = _createSimpleContents(cTypeId, lang, workflowName, initWorkflowActionId, workflowEditActionId, uploadedFile);
            result.put("contentIds", contentIds);
            result.put("success", "true");
        }
        catch (Exception e)
        {
            result.put("error", "true");
            result.put("errorMessage", e.getMessage());
            
            getLogger().error("Unable to create simple contents.", e);
        }
        
        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
        
        return EMPTY_MAP;
    }

    private List<String> _createSimpleContents (String contentTypeId, String lang, String workflowName, int initActionId, int editActionId, File uploadedFile) throws IOException, WorkflowException
    {
        List<String> contentIds = new ArrayList<>();
        
        try (ICsvListReader csvReader = new CsvListReader(_getReader(new BOMInputStream(new FileInputStream(uploadedFile))), CsvPreference.EXCEL_NORTH_EUROPE_PREFERENCE))
        {
            String[] metadataNames = csvReader.getHeader(true);
            
            // Remove spaces and tabs before and after the metadata name
            for (int i = 0; i < metadataNames.length; i++)
            {
                metadataNames[i] = metadataNames[i].trim();
            }
            
            int titleIndex = ArrayUtils.indexOf(metadataNames, "title");
            titleIndex = titleIndex != -1 ? titleIndex : 0;
            
            List<String> parts = new ArrayList<>();
            while ((parts = csvReader.read()) != null)
            {
                Content content = _createSimpleContent(contentTypeId, lang, parts.get(titleIndex).trim(), workflowName, initActionId);
                _editSimpleContent(content, metadataNames, parts, editActionId, lang);
                contentIds.add(content.getId());
            }
        }
        // FIXME catch WorkflowException and remove all created contents?
        return contentIds;
    }
    
    /**
     * Get a reader on the data stream that detects the charset.
     * @param in the data stream.
     * @return the reader with the correct character set.
     */
    protected Reader _getReader(InputStream in)
    {
        // Use Tika/ICU to detect the file charset.
        BufferedInputStream buffIs = new BufferedInputStream(in);
        
        CharsetDetector detector = new CharsetDetector();
        return detector.getReader(buffIs, Charset.defaultCharset().name());
    }
    
    private Map<String, Object> _editSimpleContent (Content content, String[] metadataNames, List<String> values, int actionId, String defaultLang) throws AmetysRepositoryException, WorkflowException
    {
        Map<String, String> jsValues = new HashMap<>();
        
        for (int i = 0; i < metadataNames.length; i++)
        {
            if (!metadataNames[i].equals("title") && values.size() > i)
            {
                MetadataDefinition metadataDef = _contentTypeHelper.getMetadataDefinition(metadataNames[i], content);
                if (metadataDef.getType() == MetadataType.MULTILINGUAL_STRING)
                {
                    Map<String, String> rawValues = new HashMap<>();
                    rawValues.put(defaultLang, StringUtils.trim(values.get(i)));
                    jsValues.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metadataNames[i], _jsonUtils.convertObjectToJson(rawValues));
                }
                else
                {
                    jsValues.put(EditContentFunction.FORM_ELEMENTS_PREFIX + metadataNames[i], StringUtils.trim(values.get(i)));
                }
            }
        }

        Map<String, Object> contextParameters = new HashMap<>();
        contextParameters.put("quit", true);
        contextParameters.put("values", jsValues);
        contextParameters.put(EditContentFunction.METADATA_SET_PARAM, null);
        
        Map<String, Object> inputs = new HashMap<>();
        inputs.put(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY, contextParameters);
        
        Map<String, Object> result = _contentWorkflowHelper.doAction((WorkflowAwareContent) content, actionId, inputs);
        return result;
    }
    
    private Content _createSimpleContent(String cTypeId, String lang, String contentTitle, String workflowName, int actionId) throws WorkflowException
    {
        ContentType cType = _cTypeEP.getExtension(cTypeId);
        MetadataDefinition titleMetaDef = cType.getMetadataDefinition(DefaultContent.METADATA_TITLE);
        
        // CMS-9474 Import reference table : Title with only figures are not supported
        String contentName = contentTitle;
        if (!_startsWithAlpha(contentName))
        {
            contentName = "content-" + contentName;
        }
        
        Map<String, Object> result = null;
        if (titleMetaDef.getType() == MetadataType.MULTILINGUAL_STRING)
        {
            Map<String, String> titleVariants = new HashMap<>();
            titleVariants.put(lang, contentTitle);
            
            result = _contentWorkflowHelper.createContent(workflowName, actionId, contentName, titleVariants, new String[] {cTypeId}, new String[0]);
        }
        else
        {
            result = _contentWorkflowHelper.createContent(workflowName, actionId, contentName, contentTitle, new String[] {cTypeId}, new String[0], lang);
        }
        
        return (Content) result.get(AbstractContentWorkflowComponent.CONTENT_KEY);
    }
    
    private boolean _startsWithAlpha(String stringToTest)
    {
        return stringToTest.toLowerCase().matches("^[a-zàèìòùáéíóúýâêîôûãñõäëïöüÿçߨøåæœ].*$");
    }
}