/*
 * This file is part of Nuts Framework.
 * Copyright(C) 2009-2012 Nuts Develop Team.
 *
 * Nuts Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License any later version.
 *
 * Nuts Framework 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 Nuts Framework. If not, see <http://www.gnu.org/licenses/>.
 */
package nuts.tools.codegen;

import nuts.core.io.IOUtils;
import nuts.core.lang.ClassUtils;
import nuts.core.lang.StringUtils;
import nuts.tools.codegen.bean.Action;
import nuts.tools.codegen.bean.ActionProperty;
import nuts.tools.codegen.bean.Filter;
import nuts.tools.codegen.bean.InputField;
import nuts.tools.codegen.bean.InputUI;
import nuts.tools.codegen.bean.ListColumn;
import nuts.tools.codegen.bean.ListQuery;
import nuts.tools.codegen.bean.ListUI;
import nuts.tools.codegen.bean.Model;
import nuts.tools.codegen.bean.ModelProperty;
import nuts.tools.codegen.bean.Module;
import nuts.tools.codegen.bean.Resource;

import java.io.File;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import org.apache.commons.cli.CommandLine;

import freemarker.template.Configuration;

/**
 * generate ".properties" resource file 
 */
public class PropertyGenerator extends AbstractCodeGenerator {
	/**
	 * Main class for PropertyResourceGenerator
	 */
	public static class Main extends AbstractCodeGenerator.Main {
		@Override
		protected void addCommandLineOptions() throws Exception {
			super.addCommandLineOptions();
			
			addCommandLineOption("l", "locale", "Resource locale (e.g: ja zh_CN)");
		}

		@Override
		protected void getCommandLineOptions(CommandLine cl) throws Exception {
			super.getCommandLineOptions(cl);
			
			if (cl.hasOption("l")) {
				setParameter("locale", cl.getOptionValue("l").trim());
			}
//			else {
//				errorRequired(options, "locale");
//			}
		}

		/**
		 * @param args arguments
		 */
		public static void main(String[] args) {
			Main cgm = new Main();
			
			AbstractCodeGenerator cg = new PropertyGenerator();

			cgm.execute(cg, args);
		}

	}

	//---------------------------------------------------------------------------------------
	// properties
	//---------------------------------------------------------------------------------------
	protected static final String CHARSET = "8859_1";
	protected static final String PRO_EXT = ".properties";
	
	protected String locale;
	protected int cntModelFile = 0;
	protected int cntActionFile = 0;

	protected boolean sourceTime = false;
	
	/**
	 * @param locale the locale to set
	 */
	public void setLocale(String locale) {
		this.locale = locale;
	}

	@Override
	protected void checkParameters() throws Exception {
		super.checkParameters();
//		CommandTool.checkRequired(locale, "locale");
	}

	protected void saveProperty(PrintWriter pw, String key, String value, boolean comment) {
    	if (comment) {
        	pw.print("#>");
    	}
    	pw.print(saveConvert(key, true, true));
    	pw.print("=");
    	pw.print(saveConvert(value, false, true));
    	pw.println();
    }

    protected void saveProperty(PrintWriter pw, String key, String value) {
    	saveProperty(pw, key, value, false);
    }

    protected void saveComment(PrintWriter pw, String key, String value) {
    	saveProperty(pw, key, value, true);
    }

    protected void saveGenerateInfo(PrintWriter pw) {
    	String time = (new SimpleDateFormat("yyyy-mm-dd HH:mm:ss")).format(Calendar.getInstance().getTime());
    	pw.println("# " + "This file was auto-generated by " 
    			+ getClass().getName() 
    			+ (sourceTime ? (" (" + time + ")") : ""));
    	pw.println();
    }

    protected void saveComment(PrintWriter pw, String value) {
    	pw.println("# " + value);
    }

    protected void saveBlockComment(PrintWriter pw, String value) {
    	pw.println("#---------------------------------------------------------");
    	pw.println("# " + value);
    	pw.println("#---------------------------------------------------------");
    }

    /**
     * Convert a nibble to a hex character
     * @param	nibble	the nibble to convert.
     */
    private static char toHex(int nibble) {
    	return hexDigit[(nibble & 0xF)];
    }

    /** A table of hex digits */
    private static final char[] hexDigit = {
    	'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
    };

    /*
     * Converts unicodes to encoded &#92;uxxxx and escapes
     * special characters with a preceding slash
     */
    protected String saveConvert(String theString,
			       boolean escapeSpace,
			       boolean escapeUnicode) {
        int len = theString.length();
        int bufLen = len * 2;
        if (bufLen < 0) {
            bufLen = Integer.MAX_VALUE;
        }
        StringBuilder outBuffer = new StringBuilder(bufLen);

        for(int x=0; x<len; x++) {
            char aChar = theString.charAt(x);
            // Handle common case first, selecting largest block that
            // avoids the specials below
            if ((aChar > 61) && (aChar < 127)) {
                if (aChar == '\\') {
                    outBuffer.append('\\'); outBuffer.append('\\');
                    continue;
                }
                outBuffer.append(aChar);
                continue;
            }
            switch(aChar) {
		case ' ':
		    if (x == 0 || escapeSpace) 
			outBuffer.append('\\');
		    outBuffer.append(' ');
		    break;
                case '\t':outBuffer.append('\\'); outBuffer.append('t');
                          break;
                case '\n':outBuffer.append('\\'); outBuffer.append('n');
                          break;
                case '\r':outBuffer.append('\\'); outBuffer.append('r');
                          break;
                case '\f':outBuffer.append('\\'); outBuffer.append('f');
                          break;
                case '=': // Fall through
                case ':': // Fall through
                case '#': // Fall through
                case '!':
                    outBuffer.append('\\'); outBuffer.append(aChar);
                    break;
                default:
                    if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode ) {
                        outBuffer.append('\\');
                        outBuffer.append('u');
                        outBuffer.append(toHex((aChar >> 12) & 0xF));
                        outBuffer.append(toHex((aChar >>  8) & 0xF));
                        outBuffer.append(toHex((aChar >>  4) & 0xF));
                        outBuffer.append(toHex( aChar        & 0xF));
                    } else {
                        outBuffer.append(aChar);
                    }
            }
        }
        return outBuffer.toString();
    }

    @Override
	protected void loadTemplates(Configuration cfg) throws Exception {
	}
	
	@Override
	protected void prepareModule(Module module) throws Exception {
		for (Resource resource : module.getResourceList()) {
			resource.merge(module);
			resource.prepare();
		}			
	}
	
	@Override
	protected void processModule(Module module) throws Exception {
		sourceTime = "true".equals(module.getProps().getProperty("source.datetime"));
		
		if (StringUtils.isEmpty(locale)) {
			for (Resource resource : module.getResourceList()) {
				processResource(resource);
				processLocaleResource(resource);
			}
		}
		else {
			for (Resource resource : module.getResourceList()) {
				if (locale.equals(resource.getLocale())) {
					processLocaleResource(resource);
				}
			}
		}
	}
	
	protected void processResource(Resource resource) throws Exception {
		for (Action action : resource.getActionList()) {
			if (Boolean.TRUE.equals(action.getGenerate())) {
				print2("Processing text of action - " + action.getName());
				
				Model am = null;
				for (Model model : resource.getModelList()) {
					if (model.getName().equals(action.getModel())) {
						am = model;
						break;
					}
				}
				
				if (am == null) {
					throw new Exception("Can not find model[" + action.getModel() + "] of action[" + action.getName() + "]");
				}
	
				processAction(action, am);
			}
		}
	}

	protected void processLocaleResource(Resource resource) throws Exception {
		String locale = StringUtils.isEmpty(resource.getLocale()) ? "" : "_" + resource.getLocale();

		for (Model model : resource.getModelList()) {
			if (Boolean.TRUE.equals(model.getGenerate())) {
				print2("Processing text of model - " + model.getName() + locale);
				processLocaleModel(model, locale);
			}
		}
		
		for (Action action : resource.getActionList()) {
			if (Boolean.TRUE.equals(action.getGenerate())) {
				print2("Processing text of action - " + action.getName() + locale);
				
				Model am = null;
				for (Model model : resource.getModelList()) {
					if (model.getName().equals(action.getModel())) {
						am = model;
						break;
					}
				}
				
				if (am == null) {
					throw new Exception("Can not find model[" + action.getModel() + "] of action[" + action.getName() + "]");
				}
	
				processLocaleAction(action, am, locale);
			}
		}
	}

	@Override
	protected void postProcess() throws Exception {
		print0(cntModule + " modules processed, " + cntModelFile + " model & " + cntActionFile + " action resource files generated successfully.");
	}

	protected void processLocaleModel(Model model, String locale) throws Exception {
		PrintWriter pwmbp = null;

		try {
			File fmdir = new File(out, ClassUtils.getPackageName(model.getModelBeanClass()).replace('.', '/'));
			fmdir.mkdirs();
			
			File fmbp = new File(fmdir.getPath(), ClassUtils.getSimpleClassName(model.getModelBeanClass()) + locale + PRO_EXT);
			print3("Generating - " + fmbp.getPath());

			pwmbp = new PrintWriter(fmbp, CHARSET);
			saveGenerateInfo(pwmbp);

			for (ModelProperty p : model.getPropertyList()) {
				if (p.getLabel() != null) {
					saveProperty(pwmbp, p.getName(), p.getLabel());
				}
				
				if (p.getTooltip() != null) {
					saveProperty(pwmbp, p.getName() + "-tip", p.getTooltip());
				}

				pwmbp.println();
			}

			pwmbp.println();
			pwmbp.flush();
			
			cntModelFile++;
		}
		finally {
			IOUtils.closeQuietly(pwmbp);
		}
	}	
	
	protected void processAction(Action action, Model model) throws Exception {
		PrintWriter pwap = null;
		try {
			File foutdir = new File(out, ClassUtils.getPackageName(action.getFullActionClass()).replace('.', '/'));
			foutdir.mkdirs();

			File fap = new File(foutdir.getPath(), ClassUtils.getSimpleClassName(action.getFullActionClass()) + PRO_EXT);
			print3("Generating - " + fap.getPath());

			pwap = new PrintWriter(fap, CHARSET);
			saveGenerateInfo(pwap);

			for (ListUI listui : action.getListUIList()) {
				if (Boolean.TRUE.equals(listui.getGenerate())) {
					saveBlockComment(pwap, listui.getName());

					for (ListColumn lc : listui.getColumnList()) {
						saveProperty(pwap, 
							listui.getName() + "-column-" + lc.getName(), 
							"${getText(\"" + action.getDataFieldName() + "." + lc.getName() + "\", \"\")}");
						saveProperty(pwap, 
							listui.getName() + "-column-" + lc.getName() + "-tip", 
							"${getText(\"" + action.getDataFieldName() + "." + lc.getName() + "-tip\", \"\")}");
					}

					pwap.println();
					pwap.println();
					
					for (ListQuery lq : listui.getQueryList()) {
						for (Filter f : lq.getFilterList()) {
							saveProperty(pwap, 
								listui.getName() + "-filter-" + lq.getName() + "-" + f.getName(), 
								"${getText(\"" + action.getDataFieldName() + "." + f.getName() + "\", \"\")}");
							saveProperty(pwap, 
								listui.getName() + "-filter-" + lq.getName() + "-" + f.getName() + "-tip", 
								"${getText(\"" + action.getDataFieldName() + "." + f.getName() + "-tip\", \"\")}");
						}
						pwap.println();
					}
					pwap.println();
					pwap.println();
				}
			}

			for (InputUI inputui : action.getInputUIList()) {
				if (Boolean.TRUE.equals(inputui.getGenerate())) {
					saveBlockComment(pwap, inputui.getName());

					for (InputField ifd : inputui.getFieldList()) {
						String man = action.getDataFieldName() + ".";
						if (Boolean.FALSE.equals(ifd.getModelField())) {
							man = "";
						}
				
						saveProperty(pwap, 
							inputui.getName() + "-" + ifd.getName(), 
							"${getText(\"" + man + ifd.getName() + "\", \"\")}");
						saveProperty(pwap, 
							inputui.getName() + "-" + ifd.getName() + "-tip", 
							"${getText(\"" + man + ifd.getName() + "-tip\", \"\")}");

						pwap.println();
					}
					pwap.println();
					pwap.println();
				}
			}
			pwap.println();
			pwap.flush();
			
			cntActionFile++;
		}
		finally {
			IOUtils.closeQuietly(pwap);
		}
	}

	protected void processLocaleAction(Action action, Model model, String locale) throws Exception {
		PrintWriter pwabp = null;
		try {
			File foutdir = new File(out, ClassUtils.getPackageName(action.getFullActionClass()).replace('.', '/'));
			foutdir.mkdirs();

			File fabp = new File(foutdir.getPath(), ClassUtils.getSimpleClassName(action.getFullActionClass()) + locale + PRO_EXT);
			print3("Generating - " + fabp.getPath());

			pwabp = new PrintWriter(fabp, CHARSET);
			saveGenerateInfo(pwabp);

			if (action.getTitle() != null) {
				saveProperty(pwabp, "title", action.getTitle());
				pwabp.println();
			}

			for (ActionProperty ap : action.getPropertyList()) {
				boolean write = false;
				if (StringUtils.isNotEmpty(ap.getLabel())) {
					saveProperty(pwabp, ap.getName(), ap.getLabel());
					write = true;
				}
				if (StringUtils.isNotEmpty(ap.getTooltip())) {
					saveProperty(pwabp, ap.getName() + "-tip", ap.getTooltip());
					write = true;
				}

				if (write) {
					pwabp.println();
				}
			}
			
			for (ListUI listui : action.getListUIList()) {
				if (Boolean.TRUE.equals(listui.getGenerate())) {
					saveBlockComment(pwabp, listui.getName());

					for (ListColumn lc : listui.getColumnList()) {
						boolean write = false;
						if (StringUtils.isNotEmpty(lc.getLabel())) {
							saveProperty(pwabp, 
								listui.getName() + "-column-" + lc.getName(),
								lc.getLabel());
							write = true;
						}
						if (StringUtils.isNotEmpty(lc.getTooltip())) {
							saveProperty(pwabp, 
								listui.getName() + "-column-" + lc.getName() + "-tip",
								lc.getTooltip());
							write = true;
						}

						if (write) {
							pwabp.println();
						}
					}
					pwabp.println();
					pwabp.println();

					for (ListQuery lq : listui.getQueryList()) {
						for (Filter f : lq.getFilterList()) {
							boolean write = false;
							if (StringUtils.isNotEmpty(f.getLabel())) {
								saveProperty(pwabp, 
									listui.getName() + "-filter-" + lq.getName() + "-" + f.getName(), 
									f.getLabel());
								write = true;
							}
							if (StringUtils.isNotEmpty(f.getTooltip())) {
								saveProperty(pwabp, 
									listui.getName() + "-filter-" + lq.getName() + "-" + f.getName() + "-tip", 
									f.getTooltip());
								write = true;
							}

							if (write) {
								pwabp.println();
							}
						}
						pwabp.println();
						pwabp.println();
					}
					pwabp.println();
					pwabp.println();
				}
			}

			for (InputUI inputui : action.getInputUIList()) {
				if (Boolean.TRUE.equals(inputui.getGenerate())) {
					saveBlockComment(pwabp, inputui.getName());

					for (InputField ifd : inputui.getFieldList()) {
						boolean write = false;
						if (StringUtils.isNotEmpty(ifd.getLabel())) {
							saveProperty(pwabp, 
								inputui.getName() + "-" + ifd.getName(), 
								ifd.getLabel());
							write = true;
						}
						if (StringUtils.isNotEmpty(ifd.getTooltip())) {
							saveProperty(pwabp, 
								inputui.getName() + "-" + ifd.getName() + "-tip", 
								ifd.getTooltip());
							write = true;
						}

						if (write) {
							pwabp.println();
						}
					}
					pwabp.println();
					pwabp.println();
				}
			}
			pwabp.println();
			pwabp.flush();

			cntActionFile++;
		}
		finally {
			IOUtils.closeQuietly(pwabp);
		}
	}
}
