/*
 * Distributed as part of mchange-commons-java 0.2.11
 *
 * Copyright (C) 2015 Machinery For Change, Inc.
 *
 * Author: Steve Waldman <swaldman@mchange.com>
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of EITHER:
 *
 *     1) The GNU Lesser General Public License (LGPL), version 2.1, as 
 *        published by the Free Software Foundation
 *
 * OR
 *
 *     2) The Eclipse Public License (EPL), version 1.0
 *
 * You may choose which license to accept if you wish to redistribute
 * or modify this work. You may offer derivatives of this work
 * under the license you have chosen, or you may provide the same
 * choice of license which you have been offered here.
 *
 * This software 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.
 *
 * You should have received copies of both LGPL v2.1 and EPL v1.0
 * along with this software; see the files LICENSE-EPL and LICENSE-LGPL.
 * If not, the text of these licenses are currently available at
 *
 * LGPL v2.1: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
 *  EPL v1.0: http://www.eclipse.org/org/documents/epl-v10.php 
 * 
 */

package com.mchange.v2.debug;

import java.io.*;
import java.util.*;
import com.mchange.v2.cmdline.*;
import com.mchange.v2.io.FileIterator;
import com.mchange.v2.io.DirectoryDescentUtils;
import com.mchange.v1.io.WriterUtils;
import com.mchange.v1.lang.BooleanUtils;
import com.mchange.v1.util.SetUtils;
import com.mchange.v1.util.StringTokenizerUtils;

public final class DebugGen implements DebugConstants
{
    final static String[] VALID = new String[] 
    { 
	"codebase", 
	"packages" , 
	"trace", 
	"debug", 
	"recursive", 
	"javac", 
	"noclobber", 
	"classname",
	"skipdirs",
	"outputbase"
    }; 

    final static String[] REQUIRED = new String[] 
    { "codebase", "packages" , "trace", "debug" }; 

    final static String[] ARGS = new String[]
    { "codebase", "packages" , "trace", "debug", "classname", "outputbase" }; 

    final static String EOL;

    static
    {
	EOL = System.getProperty("line.separator");
    }

    static int      trace_level;
    static boolean  debug;
    static boolean  recursive;
    static String   classname;
    static boolean  clobber;
    static Set      skipDirs;
    
    public synchronized static final void main(String[] argv)
    {
	String   codebase;
	String   outputbase;
	File[]   srcPkgDirs;

	try
	    {
		ParsedCommandLine pcl = CommandLineUtils.parse( argv, "--", VALID, REQUIRED, ARGS);

		//get and normalize the codebase
		codebase = pcl.getSwitchArg("codebase");
		codebase = platify( codebase );
		if (! codebase.endsWith(File.separator))
		    codebase += File.separator;

		//get and normalize the outputbase
		outputbase = pcl.getSwitchArg("outputbase");
		if ( outputbase != null )
		    {
			outputbase = platify( outputbase );
			if (! outputbase.endsWith(File.separator))
			    outputbase += File.separator;
		    }
		else
		    outputbase = codebase;

		File outputBaseDir = new File( outputbase );
		//System.err.println("outputBaseDir: " + outputBaseDir + "; exists? " +  outputBaseDir.exists());
		if (outputBaseDir.exists())
		    {
			if (!outputBaseDir.isDirectory())
			    {
				//System.err.println("Output Base '" + outputBaseDir.getPath() + "' is not a directory!");
				System.exit(-1);
			    }
			else if (!outputBaseDir.canWrite())
			    {
				System.err.println("Output Base '" + outputBaseDir.getPath() + "' is not writable!");
				System.exit(-1);
			    }
		    }
		else if (! outputBaseDir.mkdirs() )
		    {
			System.err.println("Output Base directory '" + outputBaseDir.getPath() + "' does not exist, and could not be created!");
			System.exit(-1);
		    }

		//find existing package dirs 
		String[] packages = StringTokenizerUtils.tokenizeToArray(pcl.getSwitchArg("packages"),", \t");
		srcPkgDirs = new File[ packages.length ];
		for(int i = 0, len = packages.length; i < len; ++i)
		    srcPkgDirs[i] = new File(codebase + sepify(packages[i]));
		
		//find trace level
		trace_level = Integer.parseInt( pcl.getSwitchArg("trace") );

		//find debug
		debug = BooleanUtils.parseBoolean( pcl.getSwitchArg("debug") );

		//find classname, or use default
		classname = pcl.getSwitchArg("classname");
		if (classname == null)
		    classname = "Debug";

		//find recursive
		recursive = pcl.includesSwitch("recursive");

		//find clobber
		clobber = !pcl.includesSwitch("noclobber");

		//find skipDirs
		String skipdirStr = pcl.getSwitchArg("skipdirs");
		if (skipdirStr != null)
		    {
			String[] skipdirArray = StringTokenizerUtils.tokenizeToArray(skipdirStr, ", \t");
			skipDirs = SetUtils.setFromArray( skipdirArray );
		    }
		else
		    {
			skipDirs = new HashSet();
			skipDirs.add("CVS");
		    }

		if ( pcl.includesSwitch("javac") )
		    System.err.println("autorecompilation of packages not yet implemented.");

		for (int i = 0, len = srcPkgDirs.length; i < len; ++i)
		    {
			if (recursive)
			    {
				if (! recursivePrecheckPackages( codebase, srcPkgDirs[i], outputbase, outputBaseDir ))
				    {
					System.err.println("One or more of the specifies packages" +
							   " could not be processed. Aborting." +
							   " No files have been modified.");
					System.exit(-1);
				    }
			    }
			else
			    {
				if (! precheckPackage( codebase, srcPkgDirs[i], outputbase, outputBaseDir ))
				    {
					System.err.println("One or more of the specifies packages" +
							   " could not be processed. Aborting." +
							   " No files have been modified.");
					System.exit(-1);
				    }
			    }
		    }

		for (int i = 0, len = srcPkgDirs.length; i < len; ++i)
		    {
			if (recursive)
			    recursiveWriteDebugFiles( codebase, srcPkgDirs[i], outputbase, outputBaseDir );
			else
			    writeDebugFile( outputbase, srcPkgDirs[i] );
		    }
	    }
	catch (Exception e)
	    {
		e.printStackTrace();
		System.err.println();
		usage();
	    }
    }

    private static void usage()
    {
	System.err.println("java " + DebugGen.class.getName() + " \\");
	System.err.println("\t--codebase=<directory under which packages live> \\  (no default)");
	System.err.println("\t--packages=<comma separated list of packages>    \\  (no default)");
	System.err.println("\t--debug=<true|false>                             \\  (no default)");
	System.err.println("\t--trace=<an int between 0 and 10>                \\  (no default)");
	System.err.println("\t--outputdir=<directory under which to generate>  \\  (defaults to same dir as codebase)");
	System.err.println("\t--recursive                                      \\  (no args)");
	System.err.println("\t--noclobber                                      \\  (no args)");
	System.err.println("\t--classname=<class to generate>                  \\  (defaults to Debug)");
	System.err.println("\t--skipdirs=<directories that should be skipped>  \\  (defaults to CVS)");
    }

    private static String ify(String str, char fromChar, char toChar)
    {
	if ( fromChar == toChar ) return str;

	StringBuffer sb = new StringBuffer(str);
	for (int i = 0, len = sb.length(); i < len; ++i)
	    if (sb.charAt(i) == fromChar)
		sb.setCharAt(i, toChar);
	return sb.toString();
    }

    private static String platify( String str )
    {
	String out;
	out = ify( str, '/', File.separatorChar );
	out = ify( out, '\\', File.separatorChar );
	out = ify( out, ':', File.separatorChar );
	return out;
    }

    private static String dottify(String str)
    { return ify(str, File.separatorChar, '.');}

    private static String sepify(String str)
    { return ify(str, '.', File.separatorChar);}

    private static boolean recursivePrecheckPackages(String codebase, File srcPkgDir, String outputbase, File outputBaseDir) throws IOException
    {
	FileIterator fii = DirectoryDescentUtils.depthFirstEagerDescent( srcPkgDir );
	while (fii.hasNext())
	    {
		File pkgDir = fii.nextFile();
		if (! pkgDir.isDirectory() || skipDirs.contains(pkgDir.getName()))
		    continue;

		File outputDir = outputDir( codebase, pkgDir, outputbase, outputBaseDir );
		if (! outputDir.exists() && !outputDir.mkdirs() )
		    {
			System.err.println( "Required output dir: '" + outputDir + 
					    "' does not exist, and could not be created.");
			return false;
		    }
		if (!precheckOutputPackageDir( outputDir ))
		    return false;
	    }		
	return true;
    }

    private static File outputDir( String codebase, File srcPkgDir, String outputbase, File outputBaseDir )
    {
	//System.err.println("outputDir( " + codebase +", " + srcPkgDir + ", " + outputbase + ", " + outputBaseDir + " )");
	if (codebase.equals(outputbase))
	    return srcPkgDir;

	String srcPath = srcPkgDir.getPath();
	if (! srcPath.startsWith( codebase ))
	    {
		System.err.println(DebugGen.class.getName() + ": program bug. Source package path '" + srcPath + 
				   "' does not begin with codebase '" + codebase + "'.");
		System.exit(-1);
	    }
	return new File( outputBaseDir, srcPath.substring( codebase.length() ) );
    }


    private static boolean precheckPackage( String codebase, File srcPkgDir, String outputbase, File outputBaseDir ) throws IOException
    { return precheckOutputPackageDir( outputDir(codebase, srcPkgDir, outputbase, outputBaseDir) ); }

    private static boolean precheckOutputPackageDir(File dir) throws IOException
    {
	File outFile = new File( dir, classname + ".java" );
	if (! dir.canWrite())
	    {
		System.err.println("File '" + outFile.getPath() + "' is not writable.");
		return false;
	    }
	else if (!clobber && outFile.exists())
	    {
		System.err.println("File '" + outFile.getPath() + "' exists, and we are in noclobber mode.");
		return false;
	    }
	else
	    return true;
    }

    private static void recursiveWriteDebugFiles( String codebase, File srcPkgDir, String outputbase, File outputBaseDir ) throws IOException
    {
	FileIterator fii = DirectoryDescentUtils.depthFirstEagerDescent( outputDir( codebase, srcPkgDir, outputbase, outputBaseDir ) );
	while (fii.hasNext())
	    {
		File pkgDir = fii.nextFile();
		//System.err.println("pkgDir: " + pkgDir); 
		if (! pkgDir.isDirectory() || skipDirs.contains(pkgDir.getName()))
		    continue;
		
		writeDebugFile(outputbase, pkgDir);
	    }		
    }

    private static void writeDebugFile(String outputbase, File pkgDir) throws IOException
    {
	//System.err.println("outputbase: " + outputbase + "; pkgDir: " + pkgDir);

	File outFile = new File( pkgDir, classname + ".java" );
	String pkg = dottify( pkgDir.getPath().substring( outputbase.length() ) );
	System.err.println("Writing file: " + outFile.getPath());
	Writer writer = null;
	try
	    {
		writer = new OutputStreamWriter( 
			   new BufferedOutputStream( 
                             new FileOutputStream( outFile ) ), "UTF8" ); 
		writer.write("/********************************************************************" + EOL);
		writer.write(" * This class generated by " + DebugGen.class.getName() + EOL);
		writer.write(" * and will probably be overwritten by the same! Edit at" + EOL);
		writer.write(" * YOUR PERIL!!! Hahahahaha." + EOL);
		writer.write(" ********************************************************************/" + EOL);
		writer.write(EOL);
		writer.write("package " + pkg + ';' + EOL);
		writer.write(EOL);
		writer.write("import com.mchange.v2.debug.DebugConstants;" + EOL);
		writer.write(EOL);
		writer.write("final class " + classname + " implements DebugConstants" + EOL);
		writer.write("{" + EOL);
		writer.write("\tfinal static boolean DEBUG = " + debug + ';' + EOL);
		writer.write("\tfinal static int     TRACE = " + traceStr( trace_level ) + ';' + EOL);
		writer.write(EOL);
		writer.write("\tprivate " + classname + "()" + EOL);
		writer.write("\t{}" + EOL);
		writer.write("}" + EOL);
		writer.write(EOL);
		writer.flush();
	    }
	finally
	    { WriterUtils.attemptClose( writer ); }
    }

    private static String traceStr( int trace )
    {
	if (trace == TRACE_NONE)
	    return "TRACE_NONE";
	else if (trace == TRACE_MED)
	    return "TRACE_MED";
	else if (trace == TRACE_MAX)
	    return "TRACE_MAX";
	else
	    return String.valueOf(trace);
    }
}










