package es.uniovi.reflection.patternmining;

import java.io.File;
import java.io.FileNotFoundException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

import com.sun.source.tree.CompilationUnitTree;
import com.sun.tools.javac.api.JavacTaskImpl;

import es.uniovi.reflection.patternmining.dao.ASTNodesInsertion;
import es.uniovi.reflection.patternmining.dao.ASTNodesInsertionIntrospector;
import es.uniovi.reflection.patternmining.dao.ASTNodesInsertionPostgres;
import es.uniovi.reflection.patternmining.dao.ASTNodesInsertionPrintStream;
import es.uniovi.reflection.patternmining.util.Util;
import es.uniovi.reflection.patternmining.visitor.program.ProgramVisitor;

public class Main {

	/*
	 java -jar alvaro.jar -o:postgresql -e:Cp1252 -u:user -c:low C://test//Test1.java
	*/
	public static Parameters parameters = new Parameters();
	public static void main(String[] args) throws Exception {
		parseArguments(args);
		File[] files = null;
		
		if(parameters.list == null || parameters.list.isEmpty())
		{
			files = new File[parameters.inputFiles.length];
			for(int i=0; i<parameters.inputFiles.length; i++)
				files[i] = new File(parameters.inputFiles[i]);
		}
		else
		{
			try {
		      File file = new File(parameters.list);
		      Scanner reader = new Scanner(file); 
		      ArrayList<String> fileFiles = new ArrayList<String>();
		      while (reader.hasNextLine()) {
		        String fileName = reader.nextLine();
		        fileFiles.add(fileName.replace(" ", ""));
		      }
		      reader.close();
		      
		      files = new File[fileFiles.size()];
		      for(int i=0; i<fileFiles.size(); i++)
					files[i] = new File(fileFiles.get(i));
		    } catch (FileNotFoundException e) {
		    	System.err.println("An error occurred.");
		    	e.printStackTrace();
		    	System.exit(1);
		    }
		}
		
	
		Util.userID = parameters.userID;
		Util.userClass = parameters.userClass;
		Util.databaseService = getDatabaseService();		
		Charset charset = parameters.encoding.equals(OptionsConfiguration.DEFAULT_ENCODING)?null:Charset.forName(parameters.encoding);
				
		JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
		StandardJavaFileManager fileManager = compiler.getStandardFileManager(null,null, charset);
		DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
		List<File> array = Arrays.asList(files);
		List<File> finalArray = new ArrayList<File>();

		for(File file: array) {
			if (!file.isDirectory()) {
				finalArray.add(file);
			}
		}	
		
		Iterable<? extends JavaFileObject> sources = fileManager.getJavaFileObjectsFromFiles(finalArray);
		JavacTaskImpl compilerTask = (JavacTaskImpl) compiler.getTask(null, null, diagnostics, null, null, sources);

		// Syntactic analysis
		Iterable<? extends CompilationUnitTree> asts = compilerTask.parse();
		// Semantic analysis
		// It's no necessary for us
		// compilerTask.analyze();

		// If errors
		if (diagnostics.getDiagnostics().size() > 0) {
			for (Diagnostic diagnostic : diagnostics.getDiagnostics()) {
				System.err.format("Error on [%d,%d] in %s %s", diagnostic.getLineNumber(), diagnostic.getColumnNumber(),
						diagnostic.getSource(), diagnostic.getMessage(null));
			}
		} else {
			//new Printer().scan(asts, 0);
			ProgramVisitor.inputVisitor(asts, Util.userID, Util.userClass);			
			if(parameters.out.equals(OptionsConfiguration.DEFAULT_OUT))
				ASTNodesInsertionIntrospector.getInstance().show();
		}
	}
	
	private static void parseArguments(String[] args) {
	    setDefaultParameters();
        List<String> inputFileNames = new ArrayList<String>();
        for(String parameter:args) {
            parseParameter(parameter, inputFileNames);
        }

        
        parameters.inputFiles = inputFileNames.toArray(new String[] {});
        
        if (parameters.inputFiles.length == 0 && (parameters.list == null || parameters.list.isEmpty())) {
            System.out.println(OptionsConfiguration.noInputMessage);
            System.exit(1);
            return;
        }
	}
	
	private static void setDefaultParameters() {       
		parameters.userID = OptionsConfiguration.DEFAULT_USERID; // * Default user
		parameters.userClass = OptionsConfiguration.DEFAULT_USERCLASS; // * Default class
		parameters.encoding = OptionsConfiguration.DEFAULT_ENCODING; // * Default encoding
		parameters.out = OptionsConfiguration.DEFAULT_OUT; // * Default encoding
		parameters.list = OptionsConfiguration.DEFAULT_LIST; // * Default list
	}
	
	private static void parseParameter(String parameter, List<String> inputFiles) {
		for(String parameterPrefix : OptionsConfiguration.optionsPrefix) {
			if (parameter.startsWith(parameterPrefix)) {
				parseOption(parameter.substring(parameterPrefix.length(), parameter.length()).toLowerCase());
	            return;
			}
		}
		inputFiles.add(parameter);
	}
    
    private static void parseOption(String option) {
        for(String opString:OptionsConfiguration.helpOptions) {
            if (option.equals(opString)) {
                System.out.println(OptionsConfiguration.helpMessage);
                System.exit(0);
            }
        }
        for(String opString:OptionsConfiguration.encodingOptions) {
            if (option.startsWith(opString)) {
                parameters.encoding = parseValue(option.substring(opString.length(), option.length()));
                return;
            }
        }
        for(String opString:OptionsConfiguration.userOptions) {
            if (option.startsWith(opString)) {
                parameters.userID = parseValue(option.substring(opString.length(), option.length()));
                return;
            }
        }
        for(String opString:OptionsConfiguration.listOptions) {
            if (option.startsWith(opString)) {
                parameters.list = parseValue(option.substring(opString.length(), option.length()));
                return;
            }
        }
        for(String opString:OptionsConfiguration.classOptions) {
            if (option.startsWith(opString)) {
            	String classOption = parseValue(option.substring(opString.length(), option.length()));
            	if (Arrays.asList(OptionsConfiguration.classNames).indexOf(classOption) == -1) {
                    System.err.println(OptionsConfiguration.wrongClassName);
                    System.exit(3); // 3 == Bad class
                }            	
                parameters.userClass = classOption;
                return;
            }
        }
        for(String opString:OptionsConfiguration.outOptions) {
            if (option.startsWith(opString)) {
            	String outOption = parseValue(option.substring(opString.length(), option.length()));
            	if (Arrays.asList(OptionsConfiguration.outNames).indexOf(outOption) == -1) {
                    System.err.println(OptionsConfiguration.wrongOut);
                    System.exit(4); // 4 == Bad out
                }            	
                parameters.out = outOption;
                return;
            }
        }
        System.err.println(OptionsConfiguration.errorMessage);
        System.exit(1);  // 1 == Unknown option
    }
    
    private static String parseValue(String value) {
        for(String opAssignment:OptionsConfiguration.optionsAssignment)
            if (value.startsWith(opAssignment))
                return value.substring(opAssignment.length(), value.length());
        System.err.println(OptionsConfiguration.errorMessage);
        System.exit(2);  // 2 == Bad option assignment
        return null;
    }
    
    private static ASTNodesInsertion getDatabaseService() {
    	if(parameters.out.equals(OptionsConfiguration.DEFAULT_OUT))
		{   
    		return ASTNodesInsertionIntrospector.getInstance();
		}    	
    	else if(parameters.out.equals("postgresql")) {
    		return new ASTNodesInsertionPostgres();
    	}
    	else
    	{
    		//return new ASTNodesInsertionPrintStream(new PrintStream(new File("out.txt")));
    		return new ASTNodesInsertionPrintStream(System.out);    		
    	}
    }
}
