package p42_refleksja;

import p42_refleksja.klasy.ABC;
import p42_refleksja.klasy.Konto;
import p42_refleksja.klasy.Osoba;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class PrzykladRefleksji {

	public static void main(String[] args) throws Exception {
		// Różne sposoby uzyskiwania obiektu Class
		Class<?> klasa = Osoba.class;

		Class<Osoba> klasa1 = Osoba.class;
		
		Osoba osoba = new Osoba();
		Class<?> klasa2 = osoba.getClass();
		
		if(klasa2 == klasa1) {
			System.out.println("To jest ta sama klasa");
		}
		
		try {
			Class<?> klasa3 = Class.forName("p42_refleksja.klasy.Osoba");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		
		System.out.println("Obiekt klasy: " + klasa);
		System.out.println("name: " + klasa.getName());
		System.out.println("simpleName: " + klasa.getSimpleName());
		System.out.println("nadklasa: " + klasa.getSuperclass().getName());
		System.out.println();
		
		System.out.println("Lista konstruktorów");
		for (Constructor<?> constructor : klasa.getConstructors()) {
			System.out.println(constructor);
		}
		
		System.out.println("Tworzę obiekt za pomocą konstr. bezarg.");
		Object obiekt1 = klasa.newInstance();
		System.out.println("Utworzyłem obiekt: " + obiekt1);
		System.out.println("Klasa obiektu: " + obiekt1.getClass());
		System.out.println();
		
		System.out.println("Tworzę obiekt za pomocą konstruktora z argumentami");
		Constructor<?> constructor3 = klasa.getConstructor(String.class, String.class, int.class);
		Object obiekt2 = constructor3.newInstance("Ola", "Nowakowska", 33);
		System.out.println("Utworzyłem obiekt: " + obiekt2);
		System.out.println("Klasa obiektu: " + obiekt2.getClass());
		System.out.println();
		
		System.out.println("Zmienne klasy Osoba:");
		for (Field field : klasa.getFields()) {
			System.out.println("   + " + field.getName() + " : " +field.getType().getName());
		}
		System.out.println("Zmienne zadeklarowane w Osoba:");
		for (Field field : klasa.getDeclaredFields()) {
			System.out.println("   + " + field.getName() + " : " +field.getType().getName());
		}
		
		
		System.out.println("Metody klasy Osoba:");
		for (Method field : klasa.getMethods()) {
			System.out.println("   * " + field.getName());
		}

		System.out.println("Metody zadeklarowane w klasie Osoba:");
		for (Method field : klasa.getDeclaredMethods()) {
			System.out.println("   # " + field.getName());
		}
		System.out.println();
		
		Field poleImie = klasa.getField("imie");
		// System.out.println(poleImie);
		poleImie.set(obiekt1, "Ala");

		Field poleNazwisko = klasa.getField("nazwisko");
		poleNazwisko.set(obiekt1, "Kowalska");
		
		Field poleWiek = klasa.getField("wiek");
		poleWiek.setInt(obiekt1, 44);
		
		System.out.println("Ustawiłem pola, teraz obiekt wypisuje się jako:");
		System.out.println(obiekt1);
		
		System.out.println("nazwisko = " + poleNazwisko.get(obiekt1));
		System.out.println();
		
		for (Field field : klasa.getDeclaredFields()) {
			if("haslo".equals(field.getName())) {
			    try {
			        field.set(obiekt1, "abc123");
			        System.out.println("Ustawiłem hasło " + field.get(obiekt1));
			    } catch(Exception e) {
			        System.out.println("Nie udało się ustawić prywatnego hasła po raz pierwszy");
			        System.out.println(e);
			    }
                try {
                    System.out.println("Próbuję uzyskać dostęp za pomocą setAccessible");
                    field.setAccessible(true); // lub trySetAccessible() bez ryzyka wyjątku
                    field.set(obiekt1, "cba321");
                    System.out.println("Ustawiłem hasło " + field.get(obiekt1));
                } catch(Exception e) {
                    System.out.println("Nie udało się ustawić prywatnego hasła po raz drugi");
                    System.out.println(e);
                }
			    
			}
		}
		System.out.println();
		
		System.out.println("Wywołam metodę:");
		Method metodaPrzedstaw = klasa.getMethod("przedstawSie");
		metodaPrzedstaw.invoke(obiekt1);
		System.out.println();
		
		// Poniżej widać co daje typ generyczny w Class i Constructor
		// Dzięki niemu newInstance zwraca obiekt Konto bez żadnego rzutowania
		System.out.println("Teraz utworzę obiekt klasy Konto");
		Class<Konto> klasaKonto = Konto.class;
		Constructor<Konto> constructorKonto = klasaKonto.getConstructor(int.class, int.class, Osoba.class);
		Konto konto = constructorKonto.newInstance(123, 1000, obiekt1);
		System.out.println(konto);
		
		// mógłbym wywołać metodę w zwykły sposób:
		konto.wplata(100);
		
		// ale zobaczmy też jak wywołuje się metodę z parametrami przez API refleksji:
		System.out.println("Teraz wywołam metodę wplata");
		Method metodaWplata = klasaKonto.getMethod("wplata", int.class);
		System.out.println(metodaWplata);
		metodaWplata.invoke(konto, 300);
		System.out.println(konto);
		System.out.println();
		
		Method metodaGetSaldo = klasaKonto.getMethod("getSaldo");
		System.out.println(metodaGetSaldo);
		Object wynik = metodaGetSaldo.invoke(konto);
		
		System.out.println("getSaldo zwróciło w wyniku: " + wynik.getClass() + " " + wynik);
		System.out.println();
		
		System.out.println("A teraz przegląd informacji z deklaracji w klasie ABC");
		Class<ABC> klasaABC = ABC.class;
		
		System.out.println("Zadeklarowane pola:");
		for (Field field : klasaABC.getDeclaredFields()) {
			System.out.println(" * " + field);
			System.out.println("    nazwa: " + field.getName());
			System.out.println("    typ: " + field.getType());
			if(field.getType().getDeclaringClass() != null) {
				System.out.println("    Typ zadeklarowany wewnątrz: " + field.getType().getDeclaringClass());
			}
			Type genericType = field.getGenericType();
			// wynikiem jest ParameterizedType tylko jeśli typ jest faktycznie sparametryzowany
			if(genericType instanceof ParameterizedType) {
				ParameterizedType parameterizedType = (ParameterizedType) genericType;
				System.out.println("    To jest typ generyczny " + genericType);
				System.out.println("    Parametry typowe:");
				for (Type type : parameterizedType.getActualTypeArguments()) {
					System.out.println("     > " + type);
				}
			}
			Annotation[] adnotacje = field.getDeclaredAnnotations();
			if(adnotacje != null && adnotacje.length > 0) {
				System.out.println("    Adnotacje:");
				for(Annotation an : adnotacje) {
					System.out.println("      " + an);
					Class<? extends Annotation> annoType = an.annotationType();
					System.out.println("          parametry adnotacji:");
					for (Method annoMethod : annoType.getDeclaredMethods()) {
						System.out.println("           / " + annoMethod.getName() + " = " + annoMethod.invoke(an));
					}
				}
			}
			
			System.out.println();
		}
		
	}

}
