/*
 * Copyright 2006-2011 the Seasar Foundation and the Others.
 *
 * 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.seasar.codegen.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.seasar.buri.common.util.template.TextTemplate;
import org.seasar.buri.common.util.template.exception.TemplateRuntimeException;
import org.seasar.codegen.CodeGenConfig;
import org.seasar.codegen.Java;
import org.seasar.codegen.OutputCode;
import org.seasar.codegen.convert.NameConverter;
import org.seasar.codegen.element.Table;
import org.seasar.codegen.templates.LoaderClass;
import org.seasar.codegen.util.FileUtil;
import org.seasar.framework.container.annotation.tiger.Binding;
import org.seasar.framework.container.annotation.tiger.BindingType;
import org.seasar.framework.exception.IORuntimeException;
import org.seasar.framework.util.StringUtil;

import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;

@Java
public class AbstractEntityOutputCodeImpl implements OutputCode {
    protected String defaultDir = "Entity";

    protected String dirNameFtl = "${package?replace(\".\",\"/\")}/${defaultDir?lower_case?replace(\".\",\"/\")}/";

    protected String fileNameFtl = "Abstract${table.tableNameForDto?cap_first}.java";

    protected String templateFileName = "abstractEntity.ftl";

    private Map<String, Object> addRootObj = new HashMap<String, Object>();

    protected TextTemplate template;

    private NameConverter converter;

    protected CodeGenConfig codegenConfig = new CodeGenConfigImpl();

    /**
     * ジェネレーションギャップを使用する場合<code>true</code>を指定します。
     */
    protected boolean useGenerationGap = false;

    public void generateCode(File baseDir, Map<String, Table> tableList) {
        Configuration cfg = getConfiguration();
        Iterator<String> ite = tableList.keySet().iterator();
        while (ite.hasNext()) {
            String key = ite.next();
            Map<String, Object> root = createRootObj(tableList, key);
            convetName(root);
            Writer out = null;
            try {
                out = createWriter(baseDir, root);
                // generation gapの場合
                if (out == null) {
                    continue;
                }
                processTemplate(cfg, root, out);
            } finally {
                destroyWriter(out);
            }
        }
    }

    protected void destroyWriter(Writer out) {
        try {
            if (out != null) {
                out.flush();
                out.close();
            }
        } catch (Exception e) {
        }
    }

    protected void processTemplate(Configuration cfg, Map<String, Object> root,
            Writer out) {
        Template temp = createTemplate(cfg);
        try {
            temp.process(root, out);
        } catch (TemplateException e) {
            throw new TemplateRuntimeException(e);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }

    }

    protected Writer createWriter(File baseDir, Map<String, Object> root) {
        String outputDirName = baseDir.getPath() + File.separator
                + template.process(dirNameFtl, root);
        ;
        String outputFileName = template.process(fileNameFtl, root);
        Writer writer = null;
        try {
            new File(outputDirName).mkdirs();
            File dist = new File(outputDirName + outputFileName);
            if (useGenerationGap && dist.exists()) {
                return null;
            }
            writer = new OutputStreamWriter(new FileOutputStream(dist),
                    codegenConfig.getEncoding());
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
        return writer;
    }

    protected Template createTemplate(Configuration cfg) {
        Template temp = null;
        try {
            temp = cfg.getTemplate(templateFileName);
        } catch (IOException e) {
            throw new IORuntimeException(e);
        }
        return temp;
    }

    protected Map<String, Object> createRootObj(Map<String, Table> tableList,
            String key) {
        Map<String, Object> root = new HashMap<String, Object>();
        root.put("table", tableList.get(key));
        root.put("package", codegenConfig.getPackageName());
        root.put("defaultDir", defaultDir);
        root.put("encoding", codegenConfig.getEncoding());
        root.putAll(addRootObj);
        if (!StringUtil.isEmpty(codegenConfig.getHeaderTemplatePath())) {
            root.put("header", FileUtil.readText(codegenConfig
                    .getHeaderTemplatePath(), codegenConfig.getEncoding()));
        } else {
            root.put("header", "");
        }
        if (codegenConfig.isTimestamp()) {
            root.put("timestampPropertyName", codegenConfig
                    .getTimestampPropertyName());
        }
        if (codegenConfig.isVersionNo()) {
            root.put("versionNoPropetyName", codegenConfig
                    .getVersionNoPropertyName());
        }
        return root;
    }

    protected void convetName(Map<String, Object> root) {
        converter.convert(root);
    }

    protected Configuration getConfiguration() {
        Configuration cfg = new Configuration();
        cfg.setTemplateLoader(new ClassTemplateLoader(LoaderClass.class,
                "/org/seasar/codegen/templates"));

        cfg.setObjectWrapper(new DefaultObjectWrapper());
        cfg.setDefaultEncoding("UTF-8");
        return cfg;
    }

    public void addRootObjct(String name, Object data) {
        addRootObj.put(name, data);
    }

    public String getDefaultDir() {
        return defaultDir;
    }

    public void setDefaultDir(String defaultDir) {
        this.defaultDir = defaultDir;
    }

    public String getFileNameFtl() {
        return fileNameFtl;
    }

    public void setFileNameFtl(String fileNameFtl) {
        this.fileNameFtl = fileNameFtl;
    }

    public TextTemplate getTemplate() {
        return template;
    }

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

    public String getTemplateFileName() {
        return templateFileName;
    }

    public void setTemplateFileName(String templateFileName) {
        this.templateFileName = templateFileName;
    }

    public void setDirNameFtl(String dirNameFtl) {
        this.dirNameFtl = dirNameFtl;
    }

    @Binding(bindingType = BindingType.MAY)
    public void setNameConverter(NameConverter converter) {
        this.converter = converter;
    }

    @Binding(bindingType = BindingType.MAY)
    public void setCodegenConfig(CodeGenConfig codegenConfig) {
        this.codegenConfig = codegenConfig;
    }

    public boolean isUseGenerationGap() {
        return useGenerationGap;
    }

    public void setUseGenerationGap(boolean useGenerationGap) {
        this.useGenerationGap = useGenerationGap;
    }

}
