package p42_refleksja;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.Scanner;

public final class PokazRefleksji {
	private final Scanner sc;
	private Class<?> c = null;
	private Object o = null;
	
	PokazRefleksji() {
		sc = new Scanner(System.in);
	}
	
	
	private String menu() {
		System.out.println();
		System.out.println("Current class: " + c);
		System.out.println("Current object: " + o);
		System.out.println("\nChoose action:");
		System.out.println("q  - quit");
		if(c == null) {
			System.out.println("c  - choose class");			
		} else {
			System.out.println("c  - change class");
			System.out.println("p  - print general info about current class");
			System.out.println("lf - list fields of current class");
			System.out.println("lm - list methods of current class");
			System.out.println("lc - list constructors of current class");
			System.out.println("o  - create object ()");
			System.out.println("oc - create object using constructor");
		}
		return sc.nextLine().trim().toLowerCase();
	}

	private void changeClass() {
		System.out.println("Give the full class name (with package):");
		String className = sc.nextLine();
		try {
			c = Class.forName(className);
			System.out.println("Class " + className + " has been loaded.");
			o = null;
		} catch (ClassNotFoundException e) {
			System.out.println("Class " + className + " can not been loaded.");
			System.out.println("Exception: " + e);
		}
	}
	
	private void printInfo() {
		System.out.println(c.toGenericString());
		System.out.println("Full name: " + c.getName());
		System.out.println("Simple name: " + c.getSimpleName());
		System.out.println("Canonical name: " + c.getCanonicalName());
		if(c.getSuperclass() != null) {
			System.out.println("superclass: " + c.getSuperclass().getName());
		}
		if(c.getEnclosingClass() != null) {
			System.out.println("enclosing class: " + c.getEnclosingClass().getName());
		}
		if(c.isArray()) {
			System.out.println("This is array of " + c.getComponentType());
		}
	}
	
	private void listFields() {
		System.out.println("All accessible fields:");
		for(Field field : c.getFields()) {
			describeField(field);
		}
		System.out.println();
		System.out.println("All fields declared in this class:");
		for(Field field : c.getDeclaredFields()) {
			describeField(field);
		}
	}
	
	private void listMethods() {
		System.out.println("All accessible methods:");
		for(Method method : c.getMethods()) {
			describeMethod(method);
		}
		System.out.println();
		System.out.println("All methods declared in this class:");
		for(Method method : c.getDeclaredMethods()) {
			describeMethod(method);
		}
	}
	
	private void listConstructors() {
		System.out.println("All accessible methods:");
		for(Constructor<?> constr : c.getConstructors()) {
			describeConstructor(constr);
		}
		System.out.println();
	}
	
	private void describeField(Field field) {
		System.out.print(" * " + field.getName() + " : " + field.getGenericType());
		System.out.println("  /  " +  Modifier.toString(field.getModifiers()));
		
	}

	private void describeMethod(Method method) {
		System.out.print(" * " + method.getName());
		System.out.println("  /  " +  Modifier.toString(method.getModifiers()));
		System.out.println("    parameters:");
		for (Parameter parameter : method.getParameters()) {
			System.out.println("     - " + parameter.getName() + " : " + parameter.getType());
		}
		System.out.println("    result type: " + method.getReturnType());
	}
	
	private void describeConstructor(Constructor<?> constr) {
		System.out.print(" * " + constr);
		System.out.println("  /  " +  Modifier.toString(constr.getModifiers()));
		System.out.println("    parameters:");
		for (Parameter parameter : constr.getParameters()) {
			System.out.println("     - " + parameter.getName() + " : " + parameter.getType());
		}
	}

	private void createObject() {
		try {
			Constructor<?> defaultConstructor = c.getConstructor();
			o = defaultConstructor.newInstance();
			System.out.println("New object has been created");
			System.out.println(o);
			
		} catch (NoSuchMethodException e) {
			System.out.println("This class has no default constructor. " + e);
		} catch (Exception e) {
			System.out.println(e);
		}
	}

	private void invokeConstructor() {
		Constructor<?>[] constructors = c.getConstructors();
		if(constructors.length == 0) {
			System.out.println("No constructors available.");
			return;
		}
		System.out.println("Choose one of the constructors:");
		for (int i = 0; i < constructors.length; i++) {
			System.out.printf(" * %2d : %s\n", i,  constructors[i]);
		}
		System.out.print("Give the number: ");
		while(!sc.hasNextInt()) {
			sc.next();
		}
		int choice = sc.nextInt();
		sc.nextLine();
		try {
			Constructor<?> constructor = constructors[choice];
			Parameter[] parameters = constructor.getParameters();
			Object[] args = new Object[constructor.getParameterCount()];
			for (int i = 0; i < parameters.length; i++) {
				Parameter parameter = parameters[i];
				System.out.println("Give value of type " + parameter.getType() + " for parameter " + parameter.getName());
				args[i] = sc.nextLine();
			}
			o = constructor.newInstance(args);
			System.out.println("New object has been created");
			System.out.println(o);
		} catch (Exception e) {
			System.out.println("Cannot create object " + e);
		}
	}

	void run() {
		loop: while(true) {
			String action = menu();
			switch(action) {
			case "q" : break loop;
			case "c" : changeClass(); break;
			case "p" : printInfo(); break;
			case "lf" : listFields(); break;
			case "lm" : listMethods(); break;
			case "lc" : listConstructors(); break;
			case "o" : createObject(); break;
			case "oc" : invokeConstructor(); break;
			}
		}
		System.out.println("bye");
	}

	public static void main(String[] args) {
		new PokazRefleksji().run();
	}
}
