package org.greenstone.gatherer.gui;

import java.awt.*;
import javax.swing.*;
import java.lang.reflect.*;

public class TestingPreparation {
    public static boolean TEST_MODE = false; // set to true when launching GLI in test mode
    // TODO: set to true for debugging. Reset to false when committing to svn
    public static boolean DEBUGGING_TEST_MODE = true;
    
    /*
      For GUI swing testing of GLI, it's very handy for components to have names
      I use the GLI classname as name or if it's not a GLI class, then:
       - if a member var of a GLI class, then GLIclassName.memberVarName
       - else GLIClassName.swingComponentName
      Google: "Java swing automatically setName"
      -> https://stackoverflow.com/questions/3628218/strategy-for-naming-swing-components
         https://stackoverflow.com/questions/1782598/with-java-reflection-how-to-instantiate-a-new-object-then-call-a-method-on-it
      - https://stackoverflow.com/questions/4163500/how-to-automatic-set-name-attr-of-gui-components/4164479#4164479
      - https://help.eclipse.org/mars/index.jsp?topic=%2Forg.eclipse.wb.swing.doc.user%2Fhtml%2Fwizards%2Fswing%2Fautomatic_databinding.html
      - https://stackoverflow.com/questions/28639294/in-java-how-to-get-name-of-component-which-is-clicked
    */
    public static void setNamesRecursively(Component root) {
	if(!TEST_MODE) return;

	// setNamesRecursively won't assign a name if one is already set
	// But GUIManager for instance g_man has a name "frame0" assigned automatically
	// Setting explicitly since GUIManager is preferred for its name
	
	// Workaround to force replacement of non-informative names like frame0 for GUIManager
	// and dialog0 for OpenCollectioNDialog
	// Workaround is to not check if name already set for root, but to set it ourselves
	String className = root.getClass().getSimpleName();
	root.setName(className);

	setNamesRecursively("", root, "", true, className);
	//if (DEBUGGING_TEST_MODE) printComponentNames(root, "");
    }

    public static void setNamesRecursively(Component root, String forceNameSuffix) {
	if(!TEST_MODE) return;

	// customise component name more than alternative method above
	String finalName = root.getClass().getSimpleName() + "." + forceNameSuffix;
	root.setName(finalName);	

	setNamesRecursively(forceNameSuffix, root, "", true, finalName);
	//if (DEBUGGING_TEST_MODE) printComponentNames(root, "");

	// No access to private member fields, so this didn't work to
	// set names for widgets in SubcollectionManager.SubcollectionControl
	/*
	Field[] memberVars = root.getClass().getDeclaredFields();
	for(int i = 0; i < memberVars.length; i++) {
	    memberVars[i].setAccessible(true); // make private/protected/final etc fields accessible
	    System.err.println("## Member var: " + memberVars[i]);
	    try {
		if(memberVars[i].get(root) instanceof JComponent) {
		    JComponent memberComponent = (JComponent)memberVars[i].get(root);
		    if(memberComponent != null) {
			System.err.println("Found " + memberVars[i]);
			memberComponent.setName(finalName + "." + memberVars[i].getName());
		    }
		}
	    } catch(Exception e) {
		e.printStackTrace();
	    }
	    
	}
	*/
    }


    public static void setIndividualSubcomponentNames(Component root, Component ... subcomponents)
    {
	if(!TestingPreparation.TEST_MODE) {
	    return;
	}
	
	String className = root.getClass().getSimpleName();
	
	Field[] memberVars = root.getClass().getDeclaredFields();
	for(int i = 0; i < memberVars.length; i++) {
	    memberVars[i].setAccessible(true); // make private/protected/final etc fields accessible
	    
	    for(Component subcomp : subcomponents) {
		Field field = memberVars[i];
		try {
		    if(memberVars[i].get(root) instanceof Component) {
			Component memberComp = (Component)memberVars[i].get(root);
			// Check if this is a widget we're requested to set a custom name for
			if(memberComp != null && memberComp.equals(subcomp)) { 
			    memberComp.setName(className + "." + memberVars[i].getName());
			    //System.err.println("*** Custom comp name: " + memberComp.getName());
			}
		    }
		} catch(Exception e) {
		    e.printStackTrace();
		}
	    }
	    // No need to do more, don't need to recursively set names or anything
	}
	
	// Take care of any still null names in the usual manner:
	TestingPreparation.setNamesRecursively(root); // this will also printComponentNames
    }
    
    public static void setNamesRecursivelyForControl(Component root) {
	if(!TestingPreparation.TEST_MODE) {
	    return;
	}
	String className = root.getClass().getSimpleName();
	
	Field[] memberVars = root.getClass().getDeclaredFields();
	for(int i = 0; i < memberVars.length; i++) {
	    memberVars[i].setAccessible(true); // make private/protected/final etc fields accessible
	    //System.err.println("*** Member var: " + memberVars[i]);	    
	    
	    try {
		if(memberVars[i].get(root) instanceof JComponent) {
		    JComponent memberComponent = (JComponent)memberVars[i].get(root);
		    if(memberComponent != null) {
			//System.err.println("*** Found " + memberVars[i]);
			memberComponent.setName(className + "." + memberVars[i].getName());

			String packageName = memberComponent.getClass().getPackage().getName();
			if(memberComponent instanceof org.greenstone.gatherer.cdm.Control
			   || (packageName.contains("org.greenstone.gatherer.cdm")
			       && className.endsWith("Control"))) {
			       
			    setNamesRecursivelyForControl(memberComponent);
			}
			else {
			    memberComponent.setName(className + "." + memberVars[i].getName());
			    //System.err.println("*** Member comp name set to: "
			    //		       + className + "." + memberVars[i].getName());
			    Class memberVarClass = memberVars[i].getType();
			    // only recurse on greenstone GUI classes, not java (or others)
			    // and leave GLIButton names as assigned
			    if(packageName.startsWith("org.greenstone") && !JButton.class.isAssignableFrom(memberVarClass)) {
				TestingPreparation.setNamesRecursively(memberComponent);
			    }
			}
		    }
		}
		else if(memberVars[i].get(root) instanceof Component) {
		    Component memberComp = (Component)memberVars[i].get(root);
		    if(memberComp != null) {
			memberComp.setName(className + "." + memberVars[i].getName());
		    }
		}
	    } catch(Exception e) {
		e.printStackTrace();
	    }
	    
	}
	
	//if (DEBUGGING_TEST_MODE) printComponentNames(root, "");
	// Take care of any still null names in the usual manner:
	TestingPreparation.setNamesRecursively(root); // this will also printComponentNames

    }
    
    private static void setNamesRecursively(String prefix, Component root, String tabSpace, boolean isTrueRoot, String rootClassName) {
	if(!TEST_MODE) return;	
		
	// root starts off as GLI's JFrame, so we know that all its GUI children are specifically JComponents
	
	// https://docs.oracle.com/javase/7/docs/api/index.html?java/lang/reflect/package-summary.html
	String className = root.getClass().getSimpleName();
	
	String packageName = root.getClass().getPackage().getName();
	
	// both classes in gui package, and cdm package's (inner) classes
	// with *Control in the name are Greenstone GUI classes. We'll get better names for them
	// such as naming widgets in them after member variables
	if(!packageName.contains("org.greenstone.gatherer.gui")
	   && !packageName.contains("org.greenstone.gatherer.gems")
	   //	   && !(packageName.contains("org.greenstone.gatherer.cdm") && className.endsWith("Control"))
	   ) {
	    if(root.getName() == null || root.getName().equals("")) { // if name not already set
		if(!prefix.equals("")){
		    // TODO: set this to something more meaningful?
		    root.setName(prefix + "." + className);
		}
		else {
		    root.setName(className);
		}
	    } // else swing Component name already set
	    
	    // now we can print out this element's name for debugging
	    if (DEBUGGING_TEST_MODE) System.err.println(tabSpace + root.getName());
	} else { // root is in GLI GUI package, use its classname for name
	    
	    if(root.getName() == null || root.getName().equals("")) {
		// if it's a non-member var GLIButton, give it a more sensible name
		if(JButton.class.isAssignableFrom(root.getClass())) {
		    JButton button = (JButton)root;
		    root.setName(rootClassName + ".GLIButton." + button.getText()); // getLabel() deprecated
		} else {
		    root.setName(className);
		}
	    } else if(!isTrueRoot) {

		// GLIButtons are GLI GUI package but they have no member vars so
		// they would have been processed with their containing classes: their names
		// are already set.
		// But their names still have to be displayed if debugging
		if(JButton.class.isAssignableFrom(root.getClass())) {
		    if (DEBUGGING_TEST_MODE) System.err.println(tabSpace + root.getName());
		}
		
		// then the name of this GLI GUI element (i.e. of GLI GUI package) was already set
		// we've recursively dealt with this GLI element and its children already
		// Prevents cyclic widget references being processed in a cyclic loop ending
		// in a StackOverflowError
		return;
	    }
	    // now we can print out this element's name for debugging
	    if (DEBUGGING_TEST_MODE) System.err.println(tabSpace + root.getName());
		
	    prefix = className;
	    
	    // check member vars
	    Field[] memberVars = root.getClass().getDeclaredFields(); // private to public member vars, but not inherited ones
	    
	    for(int i = 0; i < memberVars.length; i++) {
		memberVars[i].setAccessible(true); // make private/protected etc fields accessible
		
		// https://www.tutorialspoint.com/java/lang/class_isinstance.htm
		Class jCompClass = Container.class;
		Class memberVarClass = memberVars[i].getType();
		
		// memberVarClass is a JComponent (JComponent or subclass)
		if(jCompClass.isAssignableFrom(memberVarClass)) {
		    
		    // get the actual member variable denoted by memberVars[i]
		    // on the current JComponent instance 'root'.
		    // We now know this member var to be a JComponent
		    // Having the actual member variable of the instantiated instance,
		    // we can call setName on it
		    try {
			Container memberComponent = (Container)memberVars[i].get(root);

			//System.err.println("*** Found (null?) member var: " + memberVars[i].getName());
			if(memberComponent != null) {
			    //System.err.println("*** Found member var: " + memberVars[i].getName());
			    
			    // member var is a JComponent but not of GLI package, so locally instantiated
			    // or member var can be GLIButtons which are of GLI GUI package
			    // but they contain no Component member vars we care about so
			    // process GLIButtons with their containing classes
			    if(JButton.class.isAssignableFrom(memberVarClass)
			       || !(memberVarClass.getPackage().getName().contains("org.greenstone.gatherer.gui")
				    && !memberVarClass.getPackage().getName().contains("org.greenstone.gatherer.gems"))
			       //			       && !(memberVarClass.getPackage().getName().contains("org.greenstone.gatherer.cdm") && memberVarClass.getName().endsWith("Control"))
			       )
				{
				String memberprefix = prefix + "." + memberVars[i].getName(); // append member var name
				
				// now can call setName() on the actual member variable
				memberComponent.setName(memberprefix);
				rootClassName = memberprefix; // pass nearest parent GLI GUI class name as param
				// for namespacing any local JButton members declared in memberVarClass
				setNamesRecursively(memberprefix, memberComponent, tabSpace+"  ", false, rootClassName);
			    }

			    //else  the member variable is a GLI GUI class, use its className as prefix for child components
			    // Skip this step to avoid circular references to our own member vars
			    
			    else { // member variable is a GLI GUI class.
				// will be using its className as prefix for child components
				
				//String memberprefix = memberVarClass.getSimpleName();
				//memberComponent.setName(memberprefix);
				setNamesRecursively("", memberComponent, tabSpace+"  ", false, rootClassName);
			    }
			    			
			    
			}
		    } catch(Exception e) {
			e.printStackTrace();
		    }
		}
		// else not a JComponent, skip
	    }	    
	}

	
	// https://stackoverflow.com/questions/33927349/could-someone-please-explain-note-this-method-should-be-called-under-awt-tree
	// No worries about AWT tree lock: setNamesRecursively is called by openGUI
	// which is specifically called by GathererProg on the Event Dispatch Thread (EDT)
	
		
	Component[] children = ((Container)root).getComponents();
	
	for(int i = 0; i < children.length; i++) {
	    // if we haven't already set a name for any child JComponents with the above,
	    // then the following will do so now
	    setNamesRecursively(className, children[i], tabSpace+"  ", false, rootClassName);
	}    

    }

    
    // For debugging swing Component names assigned with Container's setName()
    // Wasted a lot of my time debugging why menu bar items' names weren't set by setNamesRecursively().
    // Turns out they were being set, but that sadly
    // this method doesn't seem to print all components, e.g. JMenuItems of JMenus of a JMenuBar
    // Perhaps because those are available only through getSubElements() rather than
    // through getComponents()?
    // So have now shifted the printing of elements that we manually set into setNamesRecursively() above.
    // I'd have preferred this printing method since it prints out the component names in
    // hierarchical order rather than the sometimes arbitrary order that GUI member vars are
    // declared in GLI's own GUI classes.
    public static void printComponentNames(Component root, String tabbing) {
	if(!TEST_MODE) return;
	
	System.err.println(tabbing + root.getName());
	Component[] children = ((Container)root).getComponents();
	// recursive call
	for(int i = 0; i < children.length; i++) {
	    printComponentNames(children[i], tabbing + "  ");
	}
    }
}
