import java.applet.Applet;
import java.awt.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Scanner;


public class Meta extends Applet {

    private ArrayList<String> fieldsNames = new ArrayList<String>();
    private ArrayList<String> methodNames = new ArrayList<String>();
    private ArrayList<String> displayInformations = new ArrayList<String>();
    private int methodPage = 0;
    private int fieldPage = 0;
    private String className = "";
    private Class<?> c;
    private Object o;
    private int state = 0;
    private int index = -1;

  
       @Override
       public void init()  {
           newClass();
       }

       public void updateFields(Class<?> c){
           fieldPage = 0;
           Field[] fields = c.getDeclaredFields();
           fieldsNames.clear();
           for(Field f : fields){
               fieldsNames.add(f.getName());
           }

       }

       public void updateClass(Class<?> c){
           className = c.getName();
       }

       public void updateMethods(Class<?> c){
           methodPage = 0;
           Method[] methods = c.getDeclaredMethods();
           methodNames.clear();
           for(Method m : methods){
               methodNames.add(m.getName());
           }

       }

       private void fieldInformation(){
           state = 2;
           try{
            Field f = c.getDeclaredFields()[index];
            f.setAccessible(true);

            displayInformations.clear();
            
            displayInformations.add("Name:");
            displayInformations.add(f.getName());
            
            displayInformations.add("Value:");
            String temp = c.getDeclaredFields()[index].getType().getName();
            if(temp.compareTo("boolean") == 0){
                displayInformations.add(""+f.getBoolean(o));
            } else {
                if(temp.compareTo("byte") == 0){
                    displayInformations.add(""+f.getByte(o));
                } else {
                    if(temp.compareTo("char") == 0){
                        displayInformations.add(""+f.getChar(o));
                    } else {
                        if(temp.compareTo("double") == 0){
                            displayInformations.add(""+f.getDouble(o));
                        } else {
                            if(temp.compareTo("float") == 0){
                                displayInformations.add(""+f.getFloat(o));
                            } else {
                                if(temp.compareTo("int") == 0){
                                    displayInformations.add(""+f.getInt(o));
                                } else {
                                    if(temp.compareTo("long") == 0){
                                        displayInformations.add(""+f.getLong(o));
                                    } else {
                                        displayInformations.add("value is not a primitive type");
                                    }
                                }
                            }
                        }
                    }
                }
            }

            displayInformations.add("Type:");
            displayInformations.add(f.getType().getName());

            displayInformations.add("Modifiers:");
            displayInformations.add(Modifier.toString(f.getModifiers()));

            displayInformations.add("Is Synthetic:");
            displayInformations.add(""+f.isSynthetic());
            
            displayInformations.add("Is Enum Constant:");
            displayInformations.add(""+f.isEnumConstant());

           } catch(Exception e){
               System.err.println(e);
               displayInformations.clear();
           }
       }

       private void methodInformation(){
            state = 3;
            displayInformations.clear();

            Method m = c.getDeclaredMethods()[index];

            displayInformations.add("Name:");
            displayInformations.add(m.getName());

            displayInformations.add("Arguments type:");
            Class<?>[] args = m.getParameterTypes();
            if (args.length != 0) {
                 for (Class<?> arg : args){
                     displayInformations.add(arg.getName());
                     displayInformations.add("");
                 }
                 displayInformations.add("");

                 // Remove the extra empty line
                 if(displayInformations.get(displayInformations.size()-1).compareTo("") == 0 &&
                         displayInformations.get(displayInformations.size()-2).compareTo("") == 0){
                     displayInformations.remove(displayInformations.size()-1);
                     displayInformations.remove(displayInformations.size()-1);
                 }

           } else {
                  displayInformations.add("no arguments");
           }

            displayInformations.add("Return type:");
            displayInformations.add(m.getReturnType().getName());

            displayInformations.add("Throws exception type:");
            Class<?>[] exps = m.getExceptionTypes();
            if (exps.length != 0) {
                 for (Class<?> exp : exps){
                     displayInformations.add(exp.getName());
                     displayInformations.add("");
                 }
                 displayInformations.add("");

                // Remove the extra empty line
                if(displayInformations.get(displayInformations.size()-1).compareTo("") == 0 &&
                         displayInformations.get(displayInformations.size()-2).compareTo("") == 0){
                     displayInformations.remove(displayInformations.size()-1);
                     displayInformations.remove(displayInformations.size()-1);
                 }

            } else {
                 displayInformations.add("no exceptions");
            }

            displayInformations.add("Modifiers");
            displayInformations.add(Modifier.toString(m.getModifiers()));

            displayInformations.add("Is Synthetic:");
            displayInformations.add(""+m.isSynthetic());

            displayInformations.add("Is taking var args:");
            displayInformations.add(""+m.isVarArgs());

            displayInformations.add("Is a bridge");
            displayInformations.add(""+m.isBridge());
       }

       private void classInformation(){
           state = 1;
           String temp = "";
           displayInformations.clear();

           displayInformations.add("Simple name:");
           displayInformations.add(c.getSimpleName());

           displayInformations.add("Full name:");
           displayInformations.add(c.getName());

           displayInformations.add("Super Class:");
           if(c.getSuperclass()!= null){
                displayInformations.add(c.getSuperclass().getName());
           } else {
                displayInformations.add("no superclass");
           }

           displayInformations.add("Number of constructor:");
           displayInformations.add(""+c.getDeclaredConstructors().length);

           displayInformations.add("Enclosing Class:");
           if(c.getEnclosingClass() != null){
                displayInformations.add(c.getEnclosingClass().getName());
           } else {
                displayInformations.add("no enclosing class");
           }

           displayInformations.add("Modifiers:");
           displayInformations.add(Modifier.toString(c.getModifiers()));

           displayInformations.add("Generic Types:");
           TypeVariable[] tv = c.getTypeParameters();
           if (tv.length != 0) {
           for (TypeVariable t : tv)
               temp = temp+" "+t.getName();
               displayInformations.add(temp);
           } else {
               displayInformations.add("No generic types");
           }

           temp="";
           displayInformations.add("Implemented interfaces:");
           Type[] intfs = c.getGenericInterfaces();
           if (intfs.length != 0) {
                for (Type intf : intfs){
                    displayInformations.add(intf.toString());
                    displayInformations.add("");
                }
                displayInformations.add("");

                // Remove the extra empty line
                if(displayInformations.get(displayInformations.size()-1).compareTo("") == 0 &&
                         displayInformations.get(displayInformations.size()-2).compareTo("") == 0){
                     displayInformations.remove(displayInformations.size()-1);
                     displayInformations.remove(displayInformations.size()-1);
                 }

          } else {
                 displayInformations.add("No implemented interfaces");
          }

          displayInformations.add("Annorations");
          Annotation[] ann = c.getAnnotations();
          if (ann.length != 0) {
              for (Annotation a : ann){
                    displayInformations.add(a.toString());
                    displayInformations.add("");
              }
              displayInformations.add("");
          } else {
                displayInformations.add("no annorations");
          }
  
       }

       private void newClass(){
           String name;

           Scanner in = new Scanner(System.in);
           
           System.out.println("Write class to load now:");
           name = in.nextLine();
           if(name.compareTo("this") == 0){
               o = this;
               c = o.getClass();
               updateClass(c);
               updateFields(c);
               updateMethods(c);
               return;
           }
           if(name.compareTo("Math") == 0){
               math();
               return;
           }

           try {
           c = Class.forName(name);
           try {
               o = c.newInstance();
           } catch (InstantiationException ex) {
               System.err.println("Can not make an instance of this class");
               o = null;
           } catch (IllegalAccessException ex) {
               System.err.println("Can not acess this object");
           }
           updateClass(c);
           updateFields(c);
           updateMethods(c);
           System.out.println("Class loaded Succesfully");
           } catch (ClassNotFoundException ex) {
              System.out.println("No class with that name");
           }
       }

       private void math(){
           System.out.println("Value of Math.min(0.0,-0.0);");
           System.out.println(Math.min(0.0, -0.0));
       }

       private void changeValue(){
           try{
            String input;
            Scanner in = new Scanner(System.in);
            Field f = c.getDeclaredFields()[index];
            f.setAccessible(true);
            String temp = c.getDeclaredFields()[index].getType().getName();
            if(temp.compareTo("boolean") == 0){
                System.out.println("Type in boolean: ");
                input = in.next();
                f.setBoolean(o, Boolean.valueOf(input));
            } else {
                if(temp.compareTo("byte") == 0){
                    System.out.println("Type in byte: ");
                    input = in.next();
                    f.setByte(o, Byte.valueOf(input));
                } else {
                    if(temp.compareTo("char") == 0){
                        System.out.println("Type in char: ");
                        input = in.next();
                        f.setChar(o, input.charAt(0));
                    } else {
                        if(temp.compareTo("double") == 0){
                            System.out.println("Type in double: ");
                            input = in.next();
                            f.setDouble(o, Double.valueOf(input));
                        } else {
                            if(temp.compareTo("float") == 0){
                                System.out.println("Type in float: ");
                                input = in.next();
                                f.setFloat(o, Float.valueOf(input));
                            } else {
                                if(temp.compareTo("int") == 0){
                                    System.out.println("Type in int: ");
                                    input = in.next();
                                    f.setInt(o, Integer.valueOf(input));
                                } else {
                                    if(temp.compareTo("long") == 0){
                                        System.out.println("Type in long: ");
                                        input = in.next();
                                        f.setLong(o, Long.valueOf(input));
                                    } else {
                                        System.out.println("value is not a primitive type");
                                    }
                                }
                            }
                        }
                    }
                }
            }
           } catch (Exception e) {
                System.out.println(e);
           }

           fieldInformation();
       }

       private void resetClass(){
          try {
               o = c.newInstance();
               System.out.println("Succesfully reset object");
          } catch (InstantiationException ex) {
               System.err.println("Can not make an instance of this class");
          } catch (IllegalAccessException ex) {
               System.err.println("Can not acess this object");
          }
       }

       private void invokeMethod(){
           try{
               Method m = c.getDeclaredMethods()[index];
               Class<?>[] cs = m.getParameterTypes();
               Object[] obs = new Object[cs.length];
               for(int i=0;i<cs.length;i++){
                   obs[i] = cs[i].newInstance();
               }

               Object retValue = m.invoke(o, obs);
               System.out.println("Method succesfully invoked");
               if(retValue != null){
                    System.out.println("Return Value: "+retValue.toString());
               } else {
                   System.out.println("And had no return value");
               }

           } catch (Exception e){
               System.out.println(e);
           }
       }

       @Override
       public boolean mouseDown(Event e, int x, int y ) {

          if(x > 5 && x < 155 && y > 480 && y < 510){
               if(fieldPage*10+10 < fieldsNames.size()){
                     fieldPage++;
               }
           }

           if(x > 5 && x < 155 && y > 520 && y < 550){
               if(fieldPage > 0){
                    fieldPage--;
               }
           }

           if(x > 165 && x < 315 && y > 480 && y < 510){
               if(methodPage*10+10 < methodNames.size()){
                     methodPage++;
               }
           }

           if(x > 165 && x < 315 && y > 520 && y < 550){
                if(methodPage > 0){
                    methodPage--;
                }
           }
          
          if(x > 400 && x < 700 && y > 500 && y < 530){
                classInformation();
          }

          for(int i=0;i<11;i++){
              if( x>5 && x<155 && y>(5+40*i) && y<(35+40*i)){
                  if(10*fieldPage+i<fieldsNames.size()){
                      index = 10*fieldPage+i;
                      fieldInformation();
                  }
              }
          }

          for(int i=0;i<11;i++){
              if( x>165 && x<315 && y>(5+40*i) && y<(35+40*i)){
                  if(10*methodPage+i<methodNames.size()){
                      index = 10*methodPage+i;
                      methodInformation();
                  }
              }
          }

          if(x > 400 && x < 545 && y > 450 && y < 480){
                newClass();
          }
          
          if(x > 555 && x < 700 && y > 450 && y < 480){
                if(state == 1){
                    resetClass();
                }
                if(state == 2){
                    changeValue();
                }
                if(state == 3){
                    invokeMethod();
                }
          }

          repaint();
          return true;
       }

        @Override
        public void paint(Graphics g) {

           setBackground(Color.darkGray);

           g.setColor(Color.cyan);
           g.fillRoundRect(400, 500, 300, 30, 45, 45);
           g.setColor(Color.BLACK);
           g.drawString(className, 420, 520);

           g.setColor(Color.white);
           g.fillRoundRect(330, 20, 450, 400, 45, 45);
           g.setColor(Color.BLACK);

           drawBottoms(g);

           int y =5;

           for(int i = 10*fieldPage;i<fieldsNames.size();i++){
               g.setColor(Color.orange);
               g.fillRoundRect(5, y, 150, 30, 45, 45);
               g.setColor(Color.BLACK);
               g.drawString(fieldsNames.get(i), 25, 20+y);
               y = y + 40;
               if(i==10*fieldPage+9){
                   break;
               }
           }

           g.setColor(Color.orange);
           g.fillRoundRect(5, 480, 150, 30, 45, 45);
           g.setColor(Color.BLACK);
           g.drawString("Next Page", 25, 500);

           g.setColor(Color.orange);
           g.fillRoundRect(5, 520, 150, 30, 45, 45);
           g.setColor(Color.BLACK);
           g.drawString("Previous Page", 25, 540);




           y = 5;
           for(int i = 10*methodPage;i<methodNames.size();i++){
               g.setColor(Color.green);
               g.fillRoundRect(165, y, 150, 30, 45, 45);
               g.setColor(Color.BLACK);
               g.drawString(methodNames.get(i), 190, 20+y);
               y = y + 40;
               if(i==10*methodPage+9){
                   break;
               }
           }

           g.setColor(Color.green);
           g.fillRoundRect(165, 480, 150, 30, 45, 45);
           g.setColor(Color.BLACK);
           g.drawString("Next Page", 190, 500);

           g.setColor(Color.green);
           g.fillRoundRect(165, 520, 150, 30, 45, 45);
           g.setColor(Color.BLACK);
           g.drawString("Previous Page", 190, 540);

           drawInformations(g);
           
       }

        private void drawBottoms(Graphics g){
           g.setColor(Color.magenta);
           g.fillRoundRect(400, 450, 145, 30, 45, 45);
           g.setColor(Color.BLACK);
           g.drawString("New Class", 440, 470);

           g.setColor(Color.magenta);
           g.fillRoundRect(555, 450, 145, 30, 45, 45);
           g.setColor(Color.BLACK);
           if(state ==0){
                g.drawString("-----------------------", 590, 470);
           }
           if(state ==1){
                g.drawString("Reset Class", 590, 470);
           }
           if(state ==2){
                g.drawString("Change Value", 585, 470);
           }
           if(state ==3){
                g.drawString("Invoke Method", 585, 470);
           }
           
        }

        private void drawInformations(Graphics g){
            for(int i=0;i<displayInformations.size();i=i+2){
               g.drawString(displayInformations.get(i), 350, 40+i*10);
               g.drawString(displayInformations.get(i+1), 500, 40+i*10);
            }
        }

}
