/*
 * Scone - The Web Enhancement Framework
 * Copyright (C) 2009 Harald Weinreich, Volkert Buchmann, Frank Wollenweber, Torsten Haß
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package scone.accesstracking;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import scone.netobjects.Access;


/**
 * This Class stores all active frames in a list. To allow only
 * one instance of this class, get your instance by calling the
 * <code>getInstance()</code> method.<br>
 * If you want to be notified about changes in the list of frames,
 * you have to implement the method <br>
 * <code>public void framesChanged (Set contents)</code><br>
 * and call the <code>notifyMe</code> method. e.g.<br>
 * <code>Object fa = FrameAccess.getInstance();<br>
 * Object notifyObject = fa.notifyMe(this);</code><br>
 * You will need the notifyObject to unregister by calling 
 * <code>stopNotifyMe(notifyObject)</code>.<br>
 * The parameter contents holds a set of Strings, one for each
 * frame, holding the concatinated parent frame name (if existing)
 * and actual frame name.
 *
 * @author Torsten Hass
 * @version 1.0b, 12/8/2002
 */

public class FrameAccess {
    private static FrameAccess _instance = null;
    private Map map;
    private ArrayList frameList;
    private ArrayList registeredObjects;

    /**
     * Get a sole instance of FrameAccess, using the Singleton pattern.
     */
    public static FrameAccess getInstance() {
        if (_instance == null) {
            _instance = new FrameAccess();
        }
        return _instance;
    }

    private FrameAccess() {
        map = new HashMap();
        frameList = new ArrayList();
        registeredObjects = new ArrayList();
    }

    /**
     * This method adds a new Frame to the list of frames and notifys 
     * the registered objects as soon as all data is collected
     *
     * @param parentframeName  The name of the parent frame
     * @param frameName        The name of the frame
     * @param appletConnector  The instance of appletConnector to send
     *        commands to the Applet
     */
    public void add(String parentFrameName,
            String frameName,
            AppletConnector appletConnector) {
        synchronized (frameList) {
            boolean entryFound = false;
            int counter = 0;
            FrameAccessEntry fae = null;

            // test if this entry is in the list already
            while ((counter <= (frameList.size() - 1)) && !entryFound) {
                fae = (FrameAccessEntry) frameList.get(counter);
                if (fae.getParentFrameName().equals(parentFrameName)
                        && fae.getFrameName().equals(frameName)) {
                    fae.setAppletConnector(appletConnector);		// Add the appletconnector
                    entryFound = true;
                    if (fae.getAccessObject() != null) { // Notify registered objects only if the access object is set already
                        notifyRegisteredObjects();
                    }				   	
                }
                counter++;
            }
            if (!entryFound) { // if entry was not found, create a new one
                fae = new FrameAccessEntry();
                fae.setParentFrameName(parentFrameName);
                fae.setFrameName(frameName);
                fae.setAppletConnector(appletConnector);
                frameList.add(fae);
            }
        }
    }

    /**
     * This method adds the corresponding access object to a specific frame
     *
     * @param parentframeName  The name of the parent frame
     * @param frameName        The name of the frame
     * @param appletConnector  The instance of appletConnector to send
     *        commands to the Applet
     */
    public void addAccessObject(String parentFrameName, 
            String frameName, 
            Access accessObject) {
        synchronized (frameList) {
            boolean entryFound = false;
            int counter = 0;
            FrameAccessEntry fae = null;

            // search the list if parentFrameName and frameName are part of the list already
            while ((counter <= (frameList.size() - 1)) && !entryFound) {
                fae = (FrameAccessEntry) frameList.get(counter);
                if (fae.getParentFrameName().equals(parentFrameName) && // if found in list, write in access object
                        fae.getFrameName().equals(frameName)) {
                    fae.setAccessObject(accessObject);
                    entryFound = true;
                    if (fae.getAppletConnector() != null) { // Notify registered objects only if the appletConnector is set
                        notifyRegisteredObjects();
                    }				   	
                }
                counter++;
            }
            if (!entryFound) { // if entry not found, create a new entry
                fae = new FrameAccessEntry();
                fae.setParentFrameName(parentFrameName);
                fae.setFrameName(frameName);
                fae.setAccessObject(accessObject);
                frameList.add(fae);
            }
        }
    }
         											
    /**
     * This method removes a Frame from the map of frames
     *
     * @param parentFrameName  The name of the parent frame
     * @param frameName        The name of the frame to remove
     */
    public void remove(String parentFrameName,
            String frameName) {
        synchronized (frameList) {
            int counter;
            FrameAccessEntry fae = null;

            for (counter = 0; counter < frameList.size(); counter++) {
                fae = (FrameAccessEntry) frameList.get(counter);
                if (fae.getParentFrameName().equals(parentFrameName)
                        && fae.getFrameName().equals(frameName)) {
                    frameList.remove(counter);
                    notifyRegisteredObjects();
                }
            }
        }
    }

    /**
     * Returns true if the frameName exists.
     *
     * @param parentframeName  The name of the parent frame
     * @param frameName        The String to be tested
     * @return                 True if the string exists.
     */
    public boolean contains(String parentFrameName,
            String frameName) {
        synchronized (frameList) {
            int counter = 0;
            FrameAccessEntry fae = null;

            while (counter <= (frameList.size() - 1)) {
                fae = (FrameAccessEntry) frameList.get(counter);
                if (fae.getParentFrameName().equals(parentFrameName)
                        && fae.getFrameName().equals(frameName)) {
                    return true;
                }
                counter++;
            }
            return false;
        }
    }

    /**
     * Returns true if the frameName exists.
     *
     * @param parentFrameAndFrameName   The name of the parent frame name and frame name concatinated
     * @return                          True if the string exists.
     */
    public boolean contains(String parentFrameAndFrameName) {
        synchronized (frameList) {
            int counter = 0;
            FrameAccessEntry fae = null;

            while (counter <= (frameList.size() - 1)) {
                fae = (FrameAccessEntry) frameList.get(counter);
                if (parentFrameAndFrameName.equals(fae.getParentFrameName() + fae.getFrameName())) {
                    return true;
                }
                counter++;
            }
            return false;
        }
    }

    /**
     * Gets the AppletConnector object for a specific frameName.
     * If there is no entry for frameName, null will be returned.
     *
     * @param parentframeName  The name of the parent frame
     * @param frameName        The name of the frame
     * @return                 The corresponding AppletConnector
     */
    public AppletConnector get(String parentFrameName,
            String frameName) {
        synchronized (frameList) {
            int counter = 0;
            FrameAccessEntry fae = null;

            while (counter <= (frameList.size() - 1)) {
                fae = (FrameAccessEntry) frameList.get(counter);
                if (fae.getParentFrameName().startsWith(parentFrameName)
                        && fae.getFrameName().endsWith(frameName)) {
                    return fae.getAppletConnector();
                }
                counter++;
            }
            return null;
        }
    }

    /**
     * Gets the AppletConnector object for a specific
     * parentFrameAndFrameName. This frameName is a concatination
     * of parentFrameName and frameName.
     * If the list contains no entry for the given frameName, null will be
     * returned.
     *
     * @param parentFrameAndFrameName   parentFrameName and frameName concatinated
     * @return                          The corresponding AppletConnector
     */
    public AppletConnector get(String parentFrameAndFrameName) {
        if (parentFrameAndFrameName!= null) {
            synchronized (frameList) {
                int counter = 0;
                FrameAccessEntry fae = null;
    
                while (counter <= (frameList.size() - 1)) {
                    fae = (FrameAccessEntry) frameList.get(counter);
                    if (parentFrameAndFrameName.equals(fae.getParentFrameName() + fae.getFrameName())) {
                        return fae.getAppletConnector();
                    }
                    counter++;
                }
                return null;
            }
        }
        return null;
    }

    /**
     * Gets the Access object for a specific frameName.
     * If there is no entry for frameName, null will be returned.
     *
     * @param parentframeName  The name of the parent frame
     * @param frameName        The name of the frame
     * @return                 The corresponding Access object
     */
    public Access getAccessObject(String parentFrameName,
            String frameName) {
        synchronized (frameList) {
            int counter = 0;
            FrameAccessEntry fae = null;

            while (counter <= (frameList.size() - 1)) {
                fae = (FrameAccessEntry) frameList.get(counter);
                if (fae.getParentFrameName().startsWith(parentFrameName)
                        && fae.getFrameName().endsWith(frameName)) {
                    return fae.getAccessObject();
                }
                counter++;
            }
            return null;
        }
    }

    /**
     * Gets the Access object for a specific
     * parentFrameAndFrameName. This frameName is a concatination
     * of parentFrameName and frameName.
     * If the list contains no entry for the given frameName, null will be
     * returned.
     *
     * @param parentFrameAndFrameName   parentFrameName and frameName concatinated
     * @return                          The corresponding Access object
     */
    public Access getAccessObject(String parentFrameAndFrameName) {
        synchronized (frameList) {
            int counter = 0;
            FrameAccessEntry fae = null;

            while (counter <= (frameList.size() - 1)) {
                fae = (FrameAccessEntry) frameList.get(counter);
                if (parentFrameAndFrameName.equals(fae.getParentFrameName() + fae.getFrameName())) {
                    return fae.getAccessObject();
                }
                counter++;
            }
            return null;
        }
    }

    /**
     * Show all parent frame names concatinated with the frame names in the list to stdout.
     */
    public void showOpenFrames() {
        synchronized (frameList) {
            int counter = 0;
            FrameAccessEntry fae = null;

            // System.out.println("Open frames:");
            for (counter = 0; counter < frameList.size(); counter++) {
                fae = (FrameAccessEntry) frameList.get(counter);
                System.out.println("  " + fae.getParentFrameName() + fae.getFrameName());
            }
        }
    }

    /**
     * Returns a Set of frame names which are open right now.
     *
     * @return a Set of parent frame names concatinated with frame names (strings) 
     */
    public Set getSetOfContents() {

        map.clear();
		
        synchronized (frameList) {
            int counter;
            FrameAccessEntry fae = null;

//            System.out.println("Open frames:");
            for (counter = 0; counter < frameList.size(); counter++) {
                fae = (FrameAccessEntry) frameList.get(counter);
                map.put(fae.getParentFrameName() + fae.getFrameName(), fae.getAppletConnector());
            }
            return map.keySet();
        }
    }

    /**
     * notifyMe allows other objects to register for a callback on any change
     * of the FrameList and send the new Framelist.
     * 
     * @param callersRef Reference of the caller's class with the implemented 
     * method FramesChanged.
     * @return The returned notifyObject is needed for the stopNotifyMe method
     */
    public Object notifyMe(Object callersRef) {
        registeredObjects.add(callersRef);
        return callersRef;
    }

    /**
     * stopNotifyMe clears the caller's reference from the list of regeistered
     * objects.
     * 
     * @param notifyObject The object from the notifyMe method to remove from 
     * the list of registered threads
     */
    public void stopNotifyMe(Object notifyObject) {
        registeredObjects.remove(registeredObjects.indexOf(notifyObject));
    }

    /**
     * NotifyRegisteredObjects calls the method framesChanged in all objects 
     * that called notifyMe and sends the new list of Frames
     * 
     */
    private void notifyRegisteredObjects() {
        int counter;
        int listSize;
      
        // put all open frames in a map to send a set of this map to all registered plugins
        map.clear();
        FrameAccessEntry fae = null;

        for (counter = 0; counter < frameList.size(); counter++) {
            fae = (FrameAccessEntry) frameList.get(counter);
            map.put(fae.getParentFrameName() + fae.getFrameName(), fae.getAppletConnector());
        }

        // call the framesChanged method of every registered object
        listSize = registeredObjects.size();
        for (counter = 0; counter < listSize; counter++) {
            synchronized (map) {
                try {
                    ((FrameAccessObject) registeredObjects.get(counter)).framesChanged(map.keySet());
                } catch (Exception e) {
                    registeredObjects.remove(counter);
                }
            }
        }
    }

}   


