package com.cmeza.sdgenerator.provider;

import com.cmeza.sdgenerator.util.CustomResourceLoader;
import com.cmeza.sdgenerator.util.GeneratorUtils;
import com.cmeza.sdgenerator.util.SDLogger;
import com.cmeza.sdgenerator.util.Tuple;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.*;

/**
 * Created by carlos on 08/04/17.
 */
public abstract class AbstractTemplateProvider {

    private Class<?>[] excludeClasses;
    private String postfix;
    private boolean debug;
    private Collection<File> includeFilter;
    private String includeFilterPostfix = "";
    private boolean overwrite;

    public AbstractTemplateProvider(AnnotationAttributes attributes) {
        Assert.notNull(attributes, "AnnotationAttributes must not be null!");
        this.excludeClasses = attributes.getClassArray(getExcludeClasses());
        this.postfix = attributes.getString(getPostfix());
        this.debug = attributes.getBoolean("debug");
        this.overwrite = attributes.getBoolean("overwrite");
        if (excludeClasses.length > 0 && debug) {
            SDLogger.debug(String.format("Exclude %s %s in the %s generator", excludeClasses.length, excludeClasses.length == 1 ? "entity":"entities", postfix));
        }
    }

    public AbstractTemplateProvider(CustomResourceLoader customResourceLoader) {
        Assert.notNull(customResourceLoader, "CustomResourceLoader must not be null!");
        this.postfix = customResourceLoader.getPostfix();
        this.debug = true;
        this.excludeClasses = new Class[]{};
        this.overwrite = customResourceLoader.isOverwrite();
    }

    public void initializeCreation(String path, String ePackage, Collection<BeanDefinition> candidates, String[] entityPackage) {
        int generatedCount = 0;

        if(!GeneratorUtils.verifyPackage(path)){
            return;
        }

        Arrays.sort(entityPackage, Comparator.comparingInt((c) -> c.length()));

        for (BeanDefinition beanDefinition : candidates) {

            if (verifyEntityNonExclude(beanDefinition.getBeanClassName())){
                continue;
            }

            if (createHelper(path, beanDefinition, postfix, ePackage, entityPackage)) {
                generatedCount++;
            }
        }

        SDLogger.plusGenerated(generatedCount);
    }

    protected void setIncludeFilter(Collection<File> includeFilter){
        this.includeFilter = includeFilter;
    }

    protected void setIncludeFilterPostfix(String includeFilterPostfix) {
        this.includeFilterPostfix = includeFilterPostfix;
    }

    private Tuple<Boolean, Integer> verifyIncludeFilter(String beanDefinitionName) {
        int warnPosition = 0;

        if (includeFilter == null) {
            return new Tuple<>(Boolean.TRUE, warnPosition);
        }

        boolean result = includeFilter.stream().anyMatch(i ->
            i.getName().replace(".java", "")
                    .equals(beanDefinitionName + includeFilterPostfix)
        );

        if (!result) {
            warnPosition = SDLogger.addWarn(String.format("%s ignored: Repository not found for %s entity class", postfix, beanDefinitionName));
        }
        return new Tuple<>(result, warnPosition);
    }

    private boolean verifyEntityNonExclude(String beanClassName){
        return Arrays.stream(excludeClasses).anyMatch(b -> b.getName().equals(beanClassName));
    }

    private boolean createHelper(String path, BeanDefinition beanDefinition, String postfix, String repositoryPackage, String[] entityPackage){
        String simpleClassName = GeneratorUtils.getSimpleClassName(beanDefinition.getBeanClassName());
        Tuple<Boolean, Integer> result = null;
        if(simpleClassName != null){

            String fileHelper = simpleClassName + postfix + ".java";

            String additionalPath = this.getAdditionalPath(entityPackage, beanDefinition,  simpleClassName, path);
            String additionalPackage = "";
            if (!StringUtils.isEmpty(additionalPath)) {
                repositoryPackage += "." + additionalPath;
                additionalPackage = additionalPath;
                additionalPath = additionalPath.replace(".", "/") + "/";
            }
            String filePath = path + "/" + additionalPath + fileHelper;

            Tuple<Boolean, Integer> verifyInclude = verifyIncludeFilter(simpleClassName);
            if (!verifyInclude.left()) {
                SDLogger.addRowGeneratedTable(postfix, fileHelper, "Warn #" + verifyInclude.right());
                return false;
            }

            File file = new File(filePath);

            String fileCondition = "Created";
            if (overwrite && file.exists()) {
                file.delete();
                fileCondition = "Overwritten";
            }

            if (!file.exists()){
                result = createFileFromTemplate(filePath, repositoryPackage, simpleClassName, postfix, beanDefinition, additionalPackage);
                if (debug){
                    SDLogger.addRowGeneratedTable(postfix, fileHelper, result.left() ? fileCondition : "Error #" + result.right());
                }
            }
        } else {
            SDLogger.addError(String.format("Could not get SimpleName from: %s", beanDefinition.getBeanClassName()));
        }

        return result == null ? false : result.left();
    }

    protected abstract Tuple<String, Integer> getContentFromTemplate(String mPackage, String simpleClassName, String postfix, BeanDefinition beanDefinition, String additionalPackage);

    private Tuple<Boolean, Integer> createFileFromTemplate(String path, String repositoryPackage, String simpleClassName, String postfix, BeanDefinition beanDefinition, String additionalPackage){
        Tuple<String, Integer> content = getContentFromTemplate(repositoryPackage, simpleClassName, postfix, beanDefinition, additionalPackage);
        if (content.left() == null) {
            return new Tuple<>(false, content.right());
        }

        try (BufferedWriter bw = new BufferedWriter(new FileWriter(path))) {
            bw.write(content.left());
            return new Tuple<>(true, 0);
        } catch (IOException e) {
            return new Tuple<>(false, SDLogger.addError("Error occurred while persisting file: " + e.getMessage()));
        }
    }

    protected abstract String getExcludeClasses();
    protected abstract String getPostfix();

    private String getAdditionalPath(String[] entityPackages, BeanDefinition beanDefinition, String filename, String path) {
        List<String> additionalFolders = new ArrayList<>();
        for (String entityPackage : entityPackages) {
            if (beanDefinition.getBeanClassName().startsWith(entityPackage)) {
                String stack = beanDefinition.getBeanClassName().replace(entityPackage, "").replace(filename, "");
                if (stack.length() > 1) {
                    if (stack.startsWith(".")) {
                        stack = stack.substring(1);
                    }
                    if (stack.endsWith(".")) {
                        stack = stack.substring(0, stack.length() - 1);
                    }
                    additionalFolders.add(stack);
                }
            }
        }

        if (!additionalFolders.isEmpty()) {
            additionalFolders.sort((a, b)-> Integer.compare(b.length(), a.length()));
            String additional = path + "/" + additionalFolders.get(0);
            String pathAdditional = additional.replace(".", "/");
            GeneratorUtils.verifyPackage(pathAdditional);
            return additionalFolders.get(0);
        }
        return "";
    }
}