package org.jbake.app.configuration;

import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * The default implementation of a {@link JBakeConfiguration}
 */
public class DefaultJBakeConfiguration implements JBakeConfiguration {


    private static final String SOURCE_FOLDER_KEY = "sourceFolder";
    private static final String DESTINATION_FOLDER_KEY = "destinationFolder";
    private static final String ASSET_FOLDER_KEY = "assetFolder";
    private static final String TEMPLATE_FOLDER_KEY = "templateFolder";
    private static final String CONTENT_FOLDER_KEY = "contentFolder";
    private static final Pattern TEMPLATE_DOC_PATTERN = Pattern.compile("(?:template\\.)([a-zA-Z0-9-_]+)(?:\\.file)");
    private static final String DOCTYPE_FILE_POSTFIX = ".file";
    private static final String DOCTYPE_EXTENSION_POSTFIX = ".extension";
    private static final String DOCTYPE_TEMPLATE_PREFIX = "template.";
    private Logger logger = LoggerFactory.getLogger(DefaultJBakeConfiguration.class);
    private CompositeConfiguration compositeConfiguration;

    /**
     * Some deprecated implementations just need access to the configuration without access to the source folder
     *
     * @param configuration The project configuration
     * @deprecated use {@link #DefaultJBakeConfiguration(File, CompositeConfiguration)} instead
     */
    @Deprecated
    public DefaultJBakeConfiguration(CompositeConfiguration configuration) {
        this.compositeConfiguration = configuration;
    }

    public DefaultJBakeConfiguration(File sourceFolder, CompositeConfiguration configuration) {
        this.compositeConfiguration = configuration;
        setSourceFolder(sourceFolder);
        setupDefaultDestination();
        setupPathsRelativeToSourceFile();
    }

    @Override
    public Object get(String key) {
        return compositeConfiguration.getProperty(key);
    }

    @Override
    public String getArchiveFileName() {
        return getAsString(JBakeProperty.ARCHIVE_FILE);
    }

    private boolean getAsBoolean(String key) {
        return compositeConfiguration.getBoolean(key, false);
    }

    private File getAsFolder(String key) {
        return (File) get(key);
    }

    private int getAsInt(String key, int defaultValue) {
        return compositeConfiguration.getInt(key, defaultValue);
    }

    private List<String> getAsList(String key) {
        return Arrays.asList(compositeConfiguration.getStringArray(key));
    }

    private String getAsString(String key) {
        return compositeConfiguration.getString(key);
    }

    private String getAsString(String key, String defaultValue) {
        return compositeConfiguration.getString(key, defaultValue);
    }

    @Override
    public List<String> getAsciidoctorAttributes() {
        return getAsList(JBakeProperty.ASCIIDOCTOR_ATTRIBUTES);
    }

    public Object getAsciidoctorOption(String optionKey) {
        Configuration subConfig = compositeConfiguration.subset(JBakeProperty.ASCIIDOCTOR_OPTION);
        Object value = subConfig.getProperty(optionKey);

        if (value == null) {
            logger.warn("Cannot find asciidoctor option '{}.{}'", JBakeProperty.ASCIIDOCTOR_OPTION, optionKey);
            return "";
        }
        return value;
    }

    @Override
    public List<String> getAsciidoctorOptionKeys() {
        List<String> options = new ArrayList<>();
        Configuration subConfig = compositeConfiguration.subset(JBakeProperty.ASCIIDOCTOR_OPTION);

        Iterator<String> iterator = subConfig.getKeys();
        while (iterator.hasNext()) {
            String key = iterator.next();
            options.add(key);
        }

        return options;
    }

    @Override
    public File getAssetFolder() {
        return getAsFolder(ASSET_FOLDER_KEY);
    }

    public void setAssetFolder(File assetFolder) {
        if (assetFolder != null) {
            setProperty(ASSET_FOLDER_KEY, assetFolder);
            setProperty(JBakeProperty.ASSET_FOLDER, assetFolder.getName());
        }
    }

    @Override
    public String getAssetFolderName() {
        return getAsString(JBakeProperty.ASSET_FOLDER);
    }

    @Override
    public boolean getAssetIgnoreHidden() {
        return getAsBoolean(JBakeProperty.ASSET_IGNORE_HIDDEN);
    }

    public void setAssetIgnoreHidden(boolean assetIgnoreHidden) {
        setProperty(JBakeProperty.ASSET_IGNORE_HIDDEN, assetIgnoreHidden);
    }

    @Override
    public String getAttributesExportPrefixForAsciidoctor() {
        return getAsString(JBakeProperty.ASCIIDOCTOR_ATTRIBUTES_EXPORT_PREFIX, "");
    }

    @Override
    public String getBuildTimeStamp() {
        return getAsString(JBakeProperty.BUILD_TIMESTAMP);
    }

    @Override
    public boolean getClearCache() {
        return getAsBoolean(JBakeProperty.CLEAR_CACHE);
    }

    public void setClearCache(boolean clearCache) {
        setProperty(JBakeProperty.CLEAR_CACHE, clearCache);
    }

    public CompositeConfiguration getCompositeConfiguration() {
        return compositeConfiguration;
    }

    public void setCompositeConfiguration(CompositeConfiguration configuration) {
        this.compositeConfiguration = configuration;
    }

    @Override
    public File getContentFolder() {
        return getAsFolder(CONTENT_FOLDER_KEY);
    }

    public void setContentFolder(File contentFolder) {
        if (contentFolder != null) {
            setProperty(CONTENT_FOLDER_KEY, contentFolder);
            setProperty(JBakeProperty.CONTENT_FOLDER, contentFolder.getName());
        }
    }

    @Override
    public String getContentFolderName() {
        return getAsString(JBakeProperty.CONTENT_FOLDER);
    }

    @Override
    public String getDatabasePath() {
        return getAsString(JBakeProperty.DB_PATH);
    }

    public void setDatabasePath(String path) {
        setProperty(JBakeProperty.DB_PATH, path);
    }

    @Override
    public String getDatabaseStore() {
        return getAsString(JBakeProperty.DB_STORE);
    }

    public void setDatabaseStore(String storeType) {
        setProperty(JBakeProperty.DB_STORE, storeType);
    }

    @Override
    public String getDateFormat() {
        return getAsString(JBakeProperty.DATE_FORMAT);
    }

    @Override
    public String getDefaultStatus() {
        return getAsString(JBakeProperty.DEFAULT_STATUS, "");
    }

    public void setDefaultStatus(String status) {
        setProperty(JBakeProperty.DEFAULT_STATUS, status);
    }

    @Override
    public String getDefaultType() {
        return getAsString(JBakeProperty.DEFAULT_TYPE, "");
    }

    public void setDefaultType(String type) {
        setProperty(JBakeProperty.DEFAULT_TYPE, type);
    }

    @Override
    public File getDestinationFolder() {
        return getAsFolder(DESTINATION_FOLDER_KEY);
    }

    public void setDestinationFolder(File destinationFolder) {
        if (destinationFolder != null) {
            setProperty(DESTINATION_FOLDER_KEY, destinationFolder);
            setProperty(JBakeProperty.DESTINATION_FOLDER, destinationFolder.getName());
        }
    }

    @Override
    public List<String> getDocumentTypes() {
        List<String> docTypes = new ArrayList<>();
        Iterator<String> keyIterator = compositeConfiguration.getKeys();
        while (keyIterator.hasNext()) {
            String key = keyIterator.next();
            Matcher matcher = TEMPLATE_DOC_PATTERN.matcher(key);
            if (matcher.find()) {
                docTypes.add(matcher.group(1));
            }
        }

        return docTypes;
    }

    @Override
    public String getDraftSuffix() {
        return getAsString(JBakeProperty.DRAFT_SUFFIX, "");
    }

    @Override
    public String getExampleProjectByType(String templateType) {
        return getAsString("example.project." + templateType);
    }

    @Override
    public boolean getExportAsciidoctorAttributes() {
        return getAsBoolean(JBakeProperty.ASCIIDOCTOR_ATTRIBUTES_EXPORT);
    }

    @Override
    public String getFeedFileName() {
        return getAsString(JBakeProperty.FEED_FILE);
    }

    @Override
    public String getIndexFileName() {
        return getAsString(JBakeProperty.INDEX_FILE);
    }

    @Override
    public Iterator<String> getKeys() {
        return compositeConfiguration.getKeys();
    }

    @Override
    public List<String> getMarkdownExtensions() {
        return getAsList(JBakeProperty.MARKDOWN_EXTENSIONS);
    }

    public void setMarkdownExtensions(String... extensions) {
        setProperty(JBakeProperty.MARKDOWN_EXTENSIONS, StringUtils.join(extensions, ","));
    }

    @Override
    public String getOutputExtension() {
        return getAsString(JBakeProperty.OUTPUT_EXTENSION);
    }

    public void setOutputExtension(String outputExtension) {
        setProperty(JBakeProperty.OUTPUT_EXTENSION, outputExtension);
    }

    @Override
    public String getOutputExtensionByDocType(String docType) {
        String templateExtensionKey = DOCTYPE_TEMPLATE_PREFIX + docType + DOCTYPE_EXTENSION_POSTFIX;
        String defaultOutputExtension = getOutputExtension();
        return getAsString(templateExtensionKey, defaultOutputExtension);
    }

    @Override
    public boolean getPaginateIndex() {
        return getAsBoolean(JBakeProperty.PAGINATE_INDEX);
    }

    public void setPaginateIndex(boolean paginateIndex) {
        setProperty(JBakeProperty.PAGINATE_INDEX, paginateIndex);
    }

    @Override
    public int getPostsPerPage() {
        return getAsInt(JBakeProperty.POSTS_PER_PAGE, 5);
    }

    public void setPostsPerPage(int postsPerPage) {
        setProperty(JBakeProperty.POSTS_PER_PAGE, postsPerPage);
    }

    @Override
    public String getPrefixForUriWithoutExtension() {
        return getAsString(JBakeProperty.URI_NO_EXTENSION_PREFIX);
    }

    public void setPrefixForUriWithoutExtension(String prefix) {
        setProperty(JBakeProperty.URI_NO_EXTENSION_PREFIX, prefix);
    }

    @Override
    public boolean getRenderArchive() {
        return getAsBoolean(JBakeProperty.RENDER_ARCHIVE);
    }

    @Override
    public String getRenderEncoding() {
        return getAsString(JBakeProperty.RENDER_ENCODING);
    }

    @Override
    public boolean getRenderFeed() {
        return getAsBoolean(JBakeProperty.RENDER_FEED);
    }

    @Override
    public boolean getRenderIndex() {
        return getAsBoolean(JBakeProperty.RENDER_INDEX);
    }

    @Override
    public boolean getRenderSiteMap() {
        return getAsBoolean(JBakeProperty.RENDER_SITEMAP);
    }

    @Override
    public boolean getRenderTags() {
        return getAsBoolean(JBakeProperty.RENDER_TAGS);
    }

    @Override
    public boolean getRenderTagsIndex() {
        return compositeConfiguration.getBoolean(JBakeProperty.RENDER_TAGS_INDEX, false);
    }

    public void setRenderTagsIndex(boolean enable) {
        compositeConfiguration.setProperty(JBakeProperty.RENDER_TAGS_INDEX, enable);
    }

    @Override
    public boolean getSanitizeTag() {
        return getAsBoolean(JBakeProperty.TAG_SANITIZE);
    }

    @Override
    public int getServerPort() {
        return getAsInt(JBakeProperty.SERVER_PORT, 8080);
    }

    public void setServerPort(int port) {
        setProperty(JBakeProperty.SERVER_PORT, port);
    }

    @Override
    public String getSiteHost() {
        return getAsString(JBakeProperty.SITE_HOST, "http://www.jbake.org");
    }

    public void setSiteHost(String siteHost) {
        setProperty(JBakeProperty.SITE_HOST, siteHost);
    }

    @Override
    public String getSiteMapFileName() {
        return getAsString(JBakeProperty.SITEMAP_FILE);
    }

    @Override
    public File getSourceFolder() {
        return getAsFolder(SOURCE_FOLDER_KEY);
    }

    public void setSourceFolder(File sourceFolder) {
        setProperty(SOURCE_FOLDER_KEY, sourceFolder);
        setupPathsRelativeToSourceFile();
    }

    @Override
    public String getTagPathName() {
        return getAsString(JBakeProperty.TAG_PATH);
    }

    @Override
    public String getTemplateEncoding() {
        return getAsString(JBakeProperty.TEMPLATE_ENCODING);
    }

    @Override
    public File getTemplateFileByDocType(String docType) {
        String templateKey = DOCTYPE_TEMPLATE_PREFIX + docType + DOCTYPE_FILE_POSTFIX;
        String templateFileName = getAsString(templateKey);
        if (templateFileName != null) {
            return new File(getTemplateFolder(), templateFileName);
        }
        logger.warn("Cannot find configuration key '{}' for document type '{}'", templateKey, docType);
        return null;
    }

    @Override
    public File getTemplateFolder() {
        return getAsFolder(TEMPLATE_FOLDER_KEY);
    }

    public void setTemplateFolder(File templateFolder) {
        if (templateFolder != null) {
            setProperty(TEMPLATE_FOLDER_KEY, templateFolder);
            setProperty(JBakeProperty.TEMPLATE_FOLDER, templateFolder.getName());
        }
    }

    @Override
    public String getTemplateFolderName() {
        return getAsString(JBakeProperty.TEMPLATE_FOLDER);
    }

    @Override
    public String getThymeleafLocale() {
        return getAsString(JBakeProperty.THYMELEAF_LOCALE);
    }

    @Override
    public boolean getUriWithoutExtension() {
        return getAsBoolean(JBakeProperty.URI_NO_EXTENSION);
    }

    public void setUriWithoutExtension(boolean withoutExtension) {
        setProperty(JBakeProperty.URI_NO_EXTENSION, withoutExtension);
    }

    @Override
    public String getVersion() {
        return getAsString(JBakeProperty.VERSION);
    }

    public void setDestinationFolderName(String folderName) {
        setProperty(JBakeProperty.DESTINATION_FOLDER, folderName);
        setupDefaultDestination();
    }

    public void setExampleProject(String type, String fileName) {
        String projectKey = "example.project." + type;
        setProperty(projectKey, fileName);
    }

    @Override
    public void setProperty(String key, Object value) {
        compositeConfiguration.setProperty(key, value);
    }

    public void setTemplateExtensionForDocType(String docType, String extension) {
        String templateExtensionKey = DOCTYPE_TEMPLATE_PREFIX + docType + DOCTYPE_EXTENSION_POSTFIX;
        setProperty(templateExtensionKey, extension);
    }

    public void setTemplateFileNameForDocType(String docType, String fileName) {
        String templateKey = DOCTYPE_TEMPLATE_PREFIX + docType + DOCTYPE_FILE_POSTFIX;
        setProperty(templateKey, fileName);
    }

    private void setupDefaultAssetFolder() {
        String assetFolder = getAsString(JBakeProperty.ASSET_FOLDER);
        setAssetFolder(new File(getSourceFolder(), assetFolder));
    }

    private void setupDefaultContentFolder() {
        setContentFolder(new File(getSourceFolder(), getContentFolderName()));
    }

    private void setupDefaultDestination() {
        String destinationPath = getAsString(JBakeProperty.DESTINATION_FOLDER);
        setDestinationFolder(new File(getSourceFolder(), destinationPath));
    }

    private void setupDefaultTemplateFolder() {
        String destinationPath = getAsString(JBakeProperty.TEMPLATE_FOLDER);
        setTemplateFolder(new File(getSourceFolder(), destinationPath));
    }

    private void setupPathsRelativeToSourceFile() {
        setupDefaultAssetFolder();
        setupDefaultTemplateFolder();
        setupDefaultContentFolder();
    }

    @Override
    public String getHeaderSeparator() {
        return getAsString(JBakeProperty.HEADER_SEPARATOR);
    }

    public void setHeaderSeparator(String headerSeparator) {
        setProperty(JBakeProperty.HEADER_SEPARATOR, headerSeparator);
    }

    @Override
    public boolean getImgPathPrependHost() {
        return getAsBoolean(JBakeProperty.IMG_PATH_PREPEND_HOST);
    }

    public void setImgPathPrependHost(boolean imgPathPrependHost) {
        setProperty(JBakeProperty.IMG_PATH_PREPEND_HOST, imgPathPrependHost);
    }

    @Override
    public boolean getImgPathUpdate() {
        return getAsBoolean(JBakeProperty.IMG_PATH_UPDATE);
    }

    public void setImgPathUPdate(boolean imgPathUpdate) {
        setProperty(JBakeProperty.IMG_PATH_UPDATE, imgPathUpdate);
    }
}