/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 * #L%
 */
package org.alfresco.repo.content.transform;

import static org.alfresco.repo.content.transform.TransformerConfig.ANY;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.alfresco.api.AlfrescoPublicApi; 
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.service.cmr.repository.ContentAccessor;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.TransformationOptions;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanNameAware;

/**
 * A class providing basic functionality shared by both {@link ContentTransformer}s and {@link ContentTransformerWorker}s.
 * 
 * @author dward
 *
 * @deprecated The transformations code is being moved out of the codebase and replaced by the new async RenditionService2 or other external libraries.
 */
@Deprecated
@AlfrescoPublicApi
public class ContentTransformerHelper implements BeanNameAware
{
    private static final Log logger = LogFactory.getLog(ContentTransformerHelper.class);

    private MimetypeService mimetypeService;
    protected TransformerConfig transformerConfig;
    
    List<DeprecatedSetter> deprecatedSetterMessages;
    private static boolean firstDeprecatedSetter = true;

    /** The bean name. */
    private String beanName;

    /**
     * Helper setter of the mimetype service. This is not always required.
     * 
     * @param mimetypeService MimetypeService
     */
    public void setMimetypeService(MimetypeService mimetypeService)
    {
        this.mimetypeService = mimetypeService;
    }

    /**
     * @return Returns the mimetype helper
     */
    protected MimetypeService getMimetypeService()
    {
        return mimetypeService;
    }

    /**
     * @deprecated supported transformations are now set with global properties rather than spring configuration.
     */
    public void setExplicitTransformations(List<ExplictTransformationDetails> explicitTransformations)
    {
        deprecatedSupportedTransformations(explicitTransformations, null);
    }

    /**
     * @deprecated supported transformations are now set with global properties rather than spring configuration.
     */
    public void setSupportedTransformations(List<SupportedTransformation> supportedTransformations)
    {
        deprecatedSupportedTransformations(supportedTransformations, "true");
    }

    /**
     * @deprecated supported transformations are now set with global properties rather than spring configuration.
     */
    public void setUnsupportedTransformations(List<SupportedTransformation> unsupportedTransformations)
    {
        deprecatedSupportedTransformations(unsupportedTransformations, "false");
    }
    
    public void setTransformerConfig(TransformerConfig transformerConfig)
    {
        this.transformerConfig = transformerConfig;
    }

    /**
     * Convenience to fetch and check the mimetype for the given content
     * 
     * @param content
     *            the reader/writer for the content
     * @return Returns the mimetype for the content
     * @throws AlfrescoRuntimeException
     *             if the content doesn't have a mimetype
     */
    protected String getMimetype(ContentAccessor content)
    {
        String mimetype = content.getMimetype();
        if (mimetype == null)
        {
            throw new AlfrescoRuntimeException("Mimetype is mandatory for transformation: " + content);
        }
        // done
        return mimetype;
    }

    /**
     * @deprecated Should now use priority and unsupported transformer properties.
     * 
     * @see org.alfresco.repo.content.transform.ContentTransformer#isExplicitTransformation(java.lang.String,
     *      java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions)
     */
    public boolean isExplicitTransformation(String sourceMimetype, String targetMimetype, TransformationOptions options)
    {
        return transformerConfig.getPriority(((ContentTransformer)this), sourceMimetype, targetMimetype) == TransformerConfig.PRIORITY_EXPLICIT;
    }

    public boolean isSupportedTransformation(String sourceMimetype, String targetMimetype, TransformationOptions options)
    {
        return transformerConfig.isSupportedTransformation(((ContentTransformer)this), sourceMimetype, targetMimetype, options);
    }

    /**
     * Sets the Spring bean name.
     */
    @Override
    public void setBeanName(String beanName)
    {
        this.beanName = beanName;
    }

    /**
     * THIS IS A CUSTOM SPRING INIT METHOD 
     */
    public void register()
    {
        logDeprecatedSetter(deprecatedSetterMessages);
    }
    
    /**
     * Returns the Spring bean name.
     */
    public String getBeanName()
    {
        return beanName;
    }
    
    /**
     * Returns transformer name. Uses the Spring bean name, but if null uses the class name.
     */
    public String getName()
    {
        return (beanName == null) ? getClass().getSimpleName() : beanName;
    }

    /**
     * Returns the simple form of the transformer name, which has had the normal
     * "transformer." prefix to the Spring bean name removed.
     */
    public static String getSimpleName(ContentTransformer transformer)
    {
        String transformerName = transformer.getName();
        return transformerName.startsWith(TransformerConfig.TRANSFORMER)
                ? transformerName.substring(TransformerConfig.TRANSFORMER.length())
                : transformerName;
    }
    
    /**
     * Called by deprecated property setter methods that should no longer be called
     * by Spring configuration as the values are now set using global properties.
     * @param sourceMimetype so that the source extension can be worked out once the mimetypeService
     *        has been set. 
     * @param targetMimetype so that the target extension can be worked out once the mimetypeService
     *        has been set. 
     * @param suffixAndValue that should be used.
     */
    protected void deprecatedSetter(String sourceMimetype, String targetMimetype, String suffixAndValue)
    {
        if (deprecatedSetterMessages == null)
        {
            deprecatedSetterMessages = new ArrayList<DeprecatedSetter>();
        }
        deprecatedSetterMessages.add(new DeprecatedSetter(sourceMimetype, targetMimetype, suffixAndValue));
    }
    
    /**
     * Called when the bean name is set after all the deprecated setters to log
     * INFO messages with the Alfresco global properties that should now be set
     * (if not set) to replace Spring configuration.
     */
    void logDeprecatedSetter(List<DeprecatedSetter> deprecatedSetterMessages)
    {
        if (deprecatedSetterMessages != null)
        {
            StringBuilder sb = new StringBuilder();
            for (DeprecatedSetter deprecatedSetter: deprecatedSetterMessages)
            {
                String propertyNameAndValue = deprecatedSetter.getPropertyNameAndValue(beanName);
                String propertyName = propertyNameAndValue.replaceAll("=.*", "");
                if (transformerConfig.getProperty(propertyName) == null)
                {
                    if (firstDeprecatedSetter)
                    {
                        firstDeprecatedSetter = false;
                        logger.error("In order to support dynamic setting of transformer options, Spring XML configuration");
                        logger.error("is no longer used to initialise these options.");
                        logger.error(" ");
                        logger.error("Your system appears to contains custom Spring configuration which should be replace by");
                        logger.error("the following Alfresco global properties. In the case of the Enterprise edition these");
                        logger.error("values may then be dynamically changed via JMX.");
                        logger.error(" ");
                        // Note: Cannot set these automatically because, an MBean reset would clear them.
                    }
                    logger.error(propertyNameAndValue);
                    
                    sb.append(propertyNameAndValue);
                    sb.append('\n');
                }
                else
                {
                    logger.warn(propertyNameAndValue+" is set, but spring config still exists");
                }
            }
            deprecatedSetterMessages = null;
            if (sb.length() > 0)
            {
                // Add subsystem's properties anyway (even though an MBean reset would clear them),
                // so that existing unit tests work.
                transformerConfig.setProperties(sb.toString());
            }
        }
    }

    private void deprecatedSupportedTransformations(List<? extends SupportedTransformation> transformations, String value)
    {
        if (transformations != null)
        {
            for (SupportedTransformation transformation: transformations)
            {
                String sourceMimetype = transformation.getSourceMimetype();
                String targetMimetype = transformation.getTargetMimetype();
                
                if (value == null)
                {
                    deprecatedSetter(sourceMimetype, targetMimetype, TransformerConfig.PRIORITY+"="+TransformerConfig.PRIORITY_EXPLICIT);
                    deprecatedSetter(sourceMimetype, targetMimetype, TransformerConfig.SUPPORTED+"=true");
                }
                else
                {
                    deprecatedSetter(sourceMimetype, targetMimetype, TransformerConfig.SUPPORTED+"="+value);
                }
            }
        }
    }
    
    protected String getExtensionOrAny(String mimetype)
    {
        return mimetype == null || ANY.equals(mimetype) ? ANY : mimetypeService.getExtension(mimetype);
    }
    
    public String toString()
    {
        return getName();
    }
    
    /**
     * Overridden to supply a comment or String of commented out transformation properties
     * that specify any (hard coded or implied) supported transformations. Used
     * when providing a list of properties to an administrators who may be setting
     * other transformation properties, via JMX. Consider overriding if
     * {link {@link AbstractContentTransformerLimits#isTransformableMimetype(String, String, TransformationOptions)}
     * or {@link ContentTransformerWorker#isTransformable(String, String, TransformationOptions)}
     * have been overridden.
     * See {@link #getCommentsOnlySupports(List, List, boolean)} which may be used to help construct a comment.
     * @param available indicates if the transformer has been registered and is available to be selected.
     *                  {@code false} indicates that the transformer is only available as a component of a
     *                  complex transformer.
     * @return one line per property. The simple transformer name is returned by default as a comment.
     */
    public String getComments(boolean available)
    {
        return getCommentNameAndAvailable(available);
    }

    /**
     * Helper method for {@link #getComments(boolean)} to
     * create a line that indicates which source and target mimetypes
     * it supports.
     * @param available TODO
     * @return a String of the form "# only supports xxx, yyy or zzz to aaa or bb\n".
     */
    protected String getCommentsOnlySupports(List<String> sourceMimetypes, List<String> targetMimetypes, boolean available)
    {
        StringBuilder sb = new StringBuilder();
        sb.append(getCommentNameAndAvailable(available));
        sb.append("# Only supports ");
        sb.append(getExtensions(sourceMimetypes));
        sb.append(" to ");
        sb.append(getExtensions(targetMimetypes));
        sb.append("\n");
        return sb.toString();
    }

    /**
     * Returns the transformer's simple name and an indication if the transformer is not
     * available for selection.
     */
    String getCommentNameAndAvailable(boolean available)
    {
        String name = this instanceof ContentTransformer ? getSimpleName((ContentTransformer)this) : getName();
        StringBuilder sb = new StringBuilder();
        sb.append(getCommentName(name));
        if (!available)
        {
            sb.append("# ");
            sb.append(TransformerConfig.CONTENT);
            sb.append(getName());
            sb.append(TransformerConfig.AVAILABLE);
            sb.append("=false\n");
        }
        return sb.toString();
    }

    static String getCommentName(String name)
    {
        StringBuilder sb = new StringBuilder();
        sb.append("# ");
        sb.append(name);
        sb.append('\n');
        sb.append("# ");
        for (int i = name.length(); i > 0; i--)
        {
            sb.append('-');
        }
        sb.append('\n');
        return sb.toString();
    }

    /**
     * Helper method for {@link #getComments(boolean)} to
     * create a line that indicates which source and target mimetypes
     * it supports.
     * @param sourceMimetype String
     * @param targetMimetype String
     * @param available TODO
     * @return a String of the form "# only supports xxx to aaa\n".
     */
    protected String onlySupports(String sourceMimetype, String targetMimetype, boolean available)
    {
        return getCommentsOnlySupports(
                Arrays.asList(new String[] {sourceMimetype}),
                Arrays.asList(new String[] {targetMimetype}), available);
    }
    
    /**
     * Returns a comma separated String of mimetype file extensions. 
     */
    private String getExtensions(List<String> origMimetypes)
    {
        // Only use the mimetypes we have registered
        List<String> mimetypes = new ArrayList<String>(origMimetypes);
        mimetypes.retainAll(getMimetypeService().getMimetypes());
        
        StringBuilder sb = new StringBuilder();
        int j = mimetypes.size();
        int i=1;
        for (String mimetype: mimetypes)
        {
            sb.append(getMimetypeService().getExtension(mimetype));
            if (i < j)
            {
                sb.append(++i < j ? ", " : " or ");
            }
        }
        return sb.toString();
    }

    @Override
    public int hashCode()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((beanName == null) ? 0 : beanName.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj)
    {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ContentTransformerHelper other = (ContentTransformerHelper) obj;
        if (beanName == null)
        {
            if (other.beanName != null)
                return false;
        }
        else if (!beanName.equals(other.beanName))
            return false;
        return true;
    }
    
    private class DeprecatedSetter
    {
        private final String sourceMimetype;
        private final String targetMimetype;
        private final String suffixAndValue;
        
        DeprecatedSetter(String sourceMimetype, String targetMimetype, String suffixAndValue)
        {
            this.sourceMimetype = sourceMimetype;
            this.targetMimetype = targetMimetype;
            this.suffixAndValue = suffixAndValue;
        }

        public String getPropertyNameAndValue(String beanName)
        {
            return TransformerConfig.CONTENT+beanName+
                    (sourceMimetype != null
                    ? TransformerConfig.EXTENSIONS+getExtensionOrAny(sourceMimetype)+'.'+getExtensionOrAny(targetMimetype)
                    : ".")+
                    suffixAndValue;
        }
    }
}
