/*
 * Copyright (C) 2007-2020 Crafter Software Corporation. All Rights Reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as published by
 * the Free Software Foundation.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.craftercms.studio.impl.v2.upgrade.operations.site;

import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.URIResolver;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.configuration2.HierarchicalConfiguration;
import org.apache.commons.configuration2.tree.ImmutableNode;
import org.apache.commons.lang3.StringUtils;
import org.craftercms.studio.api.v1.log.Logger;
import org.craftercms.studio.api.v1.log.LoggerFactory;
import org.craftercms.studio.api.v2.exception.UpgradeException;
import org.craftercms.studio.api.v2.upgrade.UpgradeOperation;
import org.craftercms.studio.impl.v2.upgrade.operations.AbstractUpgradeOperation;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

import static org.craftercms.studio.api.v2.upgrade.UpgradeConstants.PARAM_KEY_SITE;
import static org.craftercms.studio.api.v2.upgrade.UpgradeConstants.PARAM_KEY_VERSION;
import static org.craftercms.studio.api.v2.utils.StudioConfiguration.GLOBAL_REPO_PATH;
import static org.craftercms.studio.api.v2.utils.StudioConfiguration.REPO_BASE_PATH;
import static org.craftercms.studio.api.v2.utils.StudioConfiguration.SANDBOX_PATH;
import static org.craftercms.studio.api.v2.utils.StudioConfiguration.SITES_REPOS_PATH;

/**
 * Base implementation of {@link UpgradeOperation} for all operations related to a XSLT template.
 *
 * <p>Supported YAML properties:
 * <ul>
 *     <li><strong>template</strong>: (required) the path to the XSLT template to apply</li>
 * </ul>
 * </p>
 *
 * @author joseross
 * @since 3.1.1
 */
public abstract class AbstractXsltFileUpgradeOperation extends AbstractUpgradeOperation {

    private static final Logger logger = LoggerFactory.getLogger(AbstractXsltFileUpgradeOperation.class);

    public static final String CONFIG_KEY_TEMPLATE = "template";

    protected static final String SAXON_CLASS = "net.sf.saxon.TransformerFactoryImpl";

    /**
     * Template file to be applied.
     */
    protected Resource template;

    public void setTemplate(final Resource template) {
        this.template = template;
    }

    @Override
    protected void doInit(final HierarchicalConfiguration<ImmutableNode> config) {
        if(template == null) {
            template = new ClassPathResource(config.getString(CONFIG_KEY_TEMPLATE));
        }
    }

    protected void executeTemplate(String site, String path, OutputStream os) throws UpgradeException {
        if(contentRepository.contentExists(site, path)) {
            try(InputStream templateIs = template.getInputStream()) {
                // Saxon is used to support XSLT 2.0
                Transformer transformer =
                    TransformerFactory.newInstance(SAXON_CLASS, null)
                        .newTransformer(new StreamSource(templateIs));
                logger.info("Applying XSLT template {0} to file {1} for site {2}", template, path, site);
                try(InputStream sourceIs = contentRepository.getContent(site, path)) {
                    transformer.setParameter(PARAM_KEY_SITE, site);
                    transformer.setParameter(PARAM_KEY_VERSION, nextVersion);
                    transformer.setURIResolver(getURIResolver(site));
                    transformer.transform(new StreamSource(sourceIs), new StreamResult(os));
                }
            } catch (Exception e) {
                throw new UpgradeException("Error processing file", e);
            }
        } else {
            logger.warn("Source file {0} does not exist in site {1}", path, site);
        }
    }

    protected URIResolver getURIResolver(String siteId) {
        return new URIResolver() {
            @Override
            public Source resolve(String href, String base) throws TransformerException {
                try {
                    Path resolverPath = null;
                    if (StringUtils.isEmpty(siteId)) {
                        resolverPath = Paths.get(
                                studioConfiguration.getProperty(REPO_BASE_PATH),
                                studioConfiguration.getProperty(GLOBAL_REPO_PATH),
                                href
                        );
                    } else {
                        resolverPath = Paths.get(
                                studioConfiguration.getProperty(REPO_BASE_PATH),
                                studioConfiguration.getProperty(SITES_REPOS_PATH),
                                siteId,
                                studioConfiguration.getProperty(SANDBOX_PATH),
                                href
                        );
                    }
                    return new StreamSource(resolverPath.toAbsolutePath().toFile());
                } catch (Exception e) {
                    logger.info("Error creating resolver for referencing documents inside xslt forms", e);
                    return  null;
                }

            }
        };
    }
}