import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Properties;

public class TestMacroProcessor {
	private static final String CRNL = "\r\n";

	private static final String MACRO_PREFIX = "//#";
	
	private File macroFolder = new File("macros");

	private String forceMacroGeneration = null;

	private ArrayList<String> processedModules = new ArrayList<String>();

	public static void main(String[] args) throws IOException {
		run(null);
	}

	/**
	 * Generate for all .ttcn_ files from the input.ats.path .ttcn files into the output.ats.path
	 * @param forceMacroGeneration if not null, will generate always the given macro instead of real one
	 */
	static void run(String forceMacroGeneration) throws FileNotFoundException, IOException {
		File configFile = new File("config.properties");
		if (!configFile.exists()) {
			throw new FileNotFoundException("No config.properties file found in current path");
		}
		
		Properties config = new Properties();
		config.load(new FileInputStream(configFile));
		
		TestMacroProcessor testMacroProcessor = new TestMacroProcessor();
		testMacroProcessor.macroFolder = new File(config.getProperty("macro.path"));
		testMacroProcessor.forceMacroGeneration = forceMacroGeneration;
		testMacroProcessor.run(new File(config.getProperty("input.ats.path")), new File(config.getProperty("output.ats.path")));
	}

	public void run(File inputPath, File outputPath) throws IOException {
		ArrayList<File> files = new ArrayList<File>();
		visitAllFiles(inputPath, files, ".ttcn_");
		visitAllFiles(inputPath, files, ".xsd");
		visitAllFiles(inputPath, files, ".xml");
		Collections.sort(files);
		
		for (File inputFile : files) {
			runForFile(inputFile, inputPath, outputPath);
		}
		System.out.println("Processed "+files.size()+" file(s) and stored to folder\n"+outputPath);
		
		
		// generate main module for force macro generation
//		if (forceMacroGeneration != null) {
			String importModule = "ImportAllGeneratedModules";
			File importAllModule = new File(outputPath, importModule+".ttcn3");
			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(importAllModule), "UTF-8"));
			bw.write("module "+importModule+" {");
			bw.write(CRNL);
			for (String moduleName : processedModules) {
				bw.write("  import from "+moduleName+" all;");
				bw.write(CRNL);
			}
			bw.write("}");
			bw.close();
//		}
	}

	public void runForFile(File inputFile, File inputPath, File outputPath) throws IOException {
		try {
			File targetFile = computeTargetFileName(inputFile, inputPath, outputPath);
			targetFile.getParentFile().mkdirs();
			if (inputFile.getName().endsWith(".ttcn_")) {
				replaceInFile(inputFile, targetFile);
			} else {
				copyFile(inputFile, targetFile);
			}
		} finally {
			System.out.println("Processed file: "+inputFile);
		}
	}

	@SuppressWarnings("resource")
  public static void copyFile(File in, File out) throws IOException {
		FileChannel inChannel = new FileInputStream(in).getChannel();
		FileChannel outChannel = new FileOutputStream(out).getChannel();
		try {
			inChannel.transferTo(0, inChannel.size(), outChannel);
		} catch (IOException e) {
			throw e;
		} finally {
			if (inChannel != null) {
			  try {
			    inChannel.close();
			  } catch (IOException e) {
			    // ignore close error
			  }
			}
			if (outChannel != null) {
			  // throws error further since target file
				outChannel.close();
			}
		}
	}

	/**
	 * Expecting //# TC or //# TC2(param1, param2)
	 */
	private void replaceInFile(File inputFile, File outputFile) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), "UTF-8"));
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "UTF-8"));
		try {
			String line;
			while ((line = br.readLine()) != null) {
				if (line.trim().startsWith(MACRO_PREFIX)) {
					int indexOfMacroPrefix = line.indexOf(MACRO_PREFIX);
					int beginIndex = indexOfMacroPrefix+MACRO_PREFIX.length();
					int endIndex = line.indexOf('(', beginIndex);
					if (endIndex <= beginIndex) {
						endIndex = line.length();
					}
					String macro = line.substring(beginIndex, endIndex).trim();
					bw.write(CRNL);
					String moduleName = inputFile.getName();
					moduleName = moduleName.substring(0, moduleName.lastIndexOf('.'));
					if (forceMacroGeneration != null) {
						macro = forceMacroGeneration;
					}
					processedModules.add(moduleName);
					writeMacro(macro, bw, moduleName, inputFile);
				} else {
					bw.write(line);
					bw.write(CRNL);
				}
			}
		} finally {
			try {
				br.close();
			} catch (IOException e) {
				// ignore
			}
			try {
				bw.close();
			} catch (IOException e) {
				// ignore
			}

		}
	}
	
	private String readMacro(BufferedReader br) throws IOException {
		StringBuilder sb = new StringBuilder();
		String line;
		while ((line = br.readLine()) != null) {
			sb.append(line).append(CRNL);
		}
		return sb.toString();
	}

	private void writeMacro(String macro, BufferedWriter bw, String moduleName, File inputFile) throws IOException {
		File macroFile = new File(macroFolder, macro+".ttcn_macro");
		BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(macroFile), "UTF-8"));
		String macroString = readMacro(br);
		macroString = macroString.replace("${module}", moduleName);
		StringBuilder xsdFileList = new StringBuilder();
		xsdFileList.append("{ ");
		File[] listXSDFiles = null;
		File parentFile = inputFile.getParentFile();
		File testData = new File(parentFile, "test_data");
		// First try to load XSD files from "test_data" folder
		boolean fromTestData = true;
		if (testData.exists() && testData.isDirectory()) {
			listXSDFiles = listXSDFiles(testData);
		}
		if (listXSDFiles == null || listXSDFiles.length == 0) {
			// No XSD files in "test_data" folder. Fall-back to the main folder
      listXSDFiles = listXSDFiles(parentFile);      
      fromTestData = false;
		}

		boolean firstFile = true;
		for (File xsdFile : listXSDFiles) {
			if (!firstFile) {
				xsdFileList.append(", ");
			}
			firstFile = false;
			xsdFileList.append("\"");
			if (fromTestData) {
				xsdFileList.append("test_data").append('/');
			}
			xsdFileList.append(xsdFile.getName());
			xsdFileList.append("\"");
		}
		xsdFileList.append(" }");
		macroString = macroString.replace("${xsdFileList}", xsdFileList);
		bw.write(macroString);
		bw.write(CRNL);
		try {
			br.close();
		} catch (Exception e) {
			// ignore
		}
	}

  private File[] listXSDFiles(File parentFolder) {
    File[] listXSDFiles = parentFolder.listFiles(new FilenameFilter() {
    	public boolean accept(File dir, String name) {
    		return name.toLowerCase().endsWith(".xsd");
    	}
    });
    return listXSDFiles;
  }

	private File computeTargetFileName(File inputFile, File inputPath, File outputPath) throws IOException, IOException {
		String canonicalInputFile = inputFile.getCanonicalPath();
		String canonicalInputPath = inputPath.getCanonicalPath();
		String relativeInputPath = canonicalInputFile.substring(canonicalInputPath.length()+1);
		if (!new File(inputPath, relativeInputPath).equals(inputFile)) {
			throw new IOException("parent inputPath not matching inputFile");
		}
		// cut _ at end of file
		if (relativeInputPath.endsWith(".ttcn_")) {
			relativeInputPath = relativeInputPath.substring(0, relativeInputPath.length()-1);
		}
		return new File(outputPath, relativeInputPath);
	}

	public static void visitAllFiles(File f, ArrayList<File> files, String extension) {
		if (f.isDirectory()) {
			String[] children = f.list();
			for (int i = 0; i < children.length; i++) {
				visitAllFiles(new File(f, children[i]), files, extension);
			}
		} else {
			if (f.toString().endsWith(extension)) {
				files.add(f);
			}
		}
	}

}
