package code.loader;

import code.loader.except.FileFormatNotSupportException;
import code.loader.except.LoaderException;
import code.loader.except.ModFileNotFoundException;
import code.loader.except.ParseException;
import code.loader.parser.ModuleParser;
import code.loader.parser.ModuleParserFactory;
import code.modules.CurryModule;
import code.table.ModuleTable;
import code.stuff.Tracer;
import code.stuff.Logger;
import code.symbols.Symbol;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.util.Date;
import java.util.Enumeration;

/**
 * LoadManager loads a module and its imported modules on demand.
 * <p/>
 * This class is responsible for
 * <OL>
 * <LI> Loading a curry module on demand. 
 *      The user pecifies the name of the module to load.
 * </OL>
 * <p/>
 *
 * @author Pravin Damle
 * @since Aug 7, 2004
 * Modified by Sunita Marathe on Jul 15, 2005
 */
public class LoadManager {

    // all methods are static

    /**
     * This is the name of the default module. It is loaded when
     * no other module is loaded.  This could happen initially or
     * when a load fails.
     */
    private static final String defaultCurrentModuleName = "Prelude";

    /**
     * The name of the currently loaded module.
     */
    private static String currentModuleName = defaultCurrentModuleName;

    /**
     * A method to get the currentModuleName
     *
     * @return String currentModuleName
     */
    public static String getCurrentModuleName() { return currentModuleName;  }

    /**
     * prevent instance creation.
     */
    private LoadManager() {} ;


    /**
     * Reload the current module.
     */
    public static void reload() { load(currentModuleName); }

    /**
     * Load the specified module from the CurryModulePath.
     * The moduleName is just the name of the module.
     * It neither specifies the module file's path not its extension.
     * <p/>
     * This method will be called in response to a "load"
     * command from the load-eval-print loop.
     *
     * @param moduleName The name of the module to be loaded.
     */
    public static void load(String moduleName) {
	if (Tracer.symbols) {
	    Logger.logln ("\nloading module \"" + moduleName + "\"");
	}


        // A modulename could be specified as ../test/IO instead
        // of IO the next 2 statements extract the real module
        // name from the moduleName.
        File x = new File(moduleName);
        String realModuleName = x.getName();

        try {
            // unload previous module, if any
            ModuleTable.clear();

            // at this point moduleName should not be found in the ModuleTable.
            // no need to check it.

            load_r(moduleName);

	//load modules that have been refered but not loaded;
	//these modules did not have any explicit "IMPORT"
	Enumeration undefModules = ModuleTable.getUndefinedModules();
	while (undefModules.hasMoreElements()) {
	    String undefModule = (String) undefModules.nextElement();
	    if (Tracer.symbols) {
		Logger.logln ("\nloading referenced module  \"" + undefModule + "\"\n");
	    }
	    load_r (undefModule); // called for each undefined referenced module
	} 

            Enumeration undef = ModuleTable.getUndefinedSymbols();
            if (undef.hasMoreElements()) {
                System.err.println("Undefined Symbols:");
                while (undef.hasMoreElements())
                    System.err.println("  " + undef.nextElement());
                ModuleTable.clear();
                currentModuleName = defaultCurrentModuleName;
                System.err.println("Load failed, tables cleared.");
            } else {
                currentModuleName = realModuleName;
            }
        }
	// catch specific exceptions if some corrective
	// action is desired within the FLVM otherwise the
	// catch (Exception e) will do the appropriate
	// job.
        catch (ModFileNotFoundException ex) {
            System.err.println(ex.getMessage());
            ModuleTable.clear();
            currentModuleName = defaultCurrentModuleName;
            System.err.println("load failed ... eliminate the cause and retry");
        } catch (Exception e) {
            e.printStackTrace();
            // ModuleTable.clear();
            // currentModuleName = defaultCurrentModuleName;
            System.exit(1);
        }
    }

    /**
     * Load the specified module if not already loaded
     * and then recursively load all its imported modules.
     *
     * @param moduleName name of the module to be loaded.
     * @return CurryModule
     * @throws Exception
     */
    public static CurryModule load_r(String moduleName) throws Exception {

        // A modulename could be specified as ../test/IO instead
        // of IO the next 2 statements extract the real module
        // name from the moduleName.
        File x = new File(moduleName);
        String realModuleName = x.getName();

        // if the module had already been loaded, then just return
        // it could get loaded prior to this recursive call
        // if it was imported by a prior module.
        CurryModule module = ModuleTable.getModule(realModuleName);

        if (module != null) {
            return module;
        }

        // load the module from the specified fileName
        // which should be located somewhere in the curryModulePath
        String filePath = CurryModulePath.getFilePath(moduleName);
        ModuleLoader loader = ModuleLoaderFactory.createModuleLoader (filePath);
        CurryModule loadedModule = loader.load (realModuleName, filePath);
        return loadedModule;
    }

}
