/*
 * 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.netobjects;


import java.sql.Connection;
import java.sql.ResultSet;
import java.util.Observer;
import java.util.Vector;

import com.ibm.wbi.protocol.http.DocumentInfo;


/**
 * Caches all NetNode objects. <br>
 * Use the static methods provided by this class to obtain NetNode objects.
 *
 * @author Harald Weinreich
 * @author Volkert Buchmann
 */

public class NetNodeCache extends Cache implements TableRecordCacheInterface {

    // store an instance of this class statically: singleton pattern (uh-oh...)
    protected static NetNodeCache observable;
    protected static int observePeriod = 0;

    /**
     * key1=id<BR>
     * key2=uri
     */
    protected static CacheTable cache;

    /**
     * used to synchronize this cache
     */
    public static Object lock = new Object();
   
    private NetNodeCache() {
        super();
    }
   
    /**
     * Create a new cache for NetNodes.
     * @param timeTillCleanUp Interval to store objects from cache to database
     * @param gcType Garbage collector type: LAZYGC or EAGERGC. See CacheTable.java
     */
    public static void init(int timeTillCleanUp, int gcType) {
        cache = new CacheTable("NetNodes", 2, timeTillCleanUp, gcType, 50000);  // Every object, every Link and every inclusion creates a new object! That's a lot!
        lock = new Object();
        observePeriod = 1000 * 4;  // every 4 Secs notify!
        observable = new NetNodeCache();
    }
   
    /**
     * sets how long the cache waits until it notifies Observers about new events
     * @param period the period in miliseconds
     */
    public static void setObservePeriod(int period) {
        observePeriod = period;
    }
   
    /**
     * returns how long the cache waits until it notifies Observers about new events
     * @return the peroid in miliseconds
     */
    public static int getObservePeriod() {
        return observePeriod;
    }  
   
    /**
     * adds an Observer to the NetNodeCache class.
     * <br>
     * Use this static method instead of addObserver() !
     * @param o the Observer
     */
    public static void putObserver(Observer o) {
        observable.addObserver(o);
    }
   
    /**
     * removes an Observer from the NetNodeCache class.
     * <br>
     * Use this static method instead of deleteObserver() !
     * @param o the Observer
     */
    public static void removeObserver(Observer o) {
        observable.deleteObserver(o);
    }
   
    /**
     * get/create a NetNode by plain String
     * @param uriString the String representation of the URI of the node
     */
    public static NetNode get(String uriString) {
        SimpleUri suri = new SimpleUri(uriString);  // call get() with SimpleUri

        return get(suri);
    }
   
    /**
     * get/create a NetNode by NetNodebase and link
     * @param base the NetNode of the present page
     * @param link the String declared by the link
     */
    public static NetNode get(NetNode base, String link) {
        return get((new SimpleUri(base.getSUri(), link)));  // call get() with SimpleUri
    }
   
    /**
     * get/create a NetNode by documentInfo
     * @param documentInfo the DocumenInfo
     */
    public static NetNode get(DocumentInfo documentInfo) {
        SimpleUri suri = new SimpleUri(documentInfo.getUrl()); 
        NetNode netNode = get(suri);  // call get() with SimpleUri

        netNode.fill(documentInfo);
        return netNode;
    }
   
    /**
     * get/create a NetNode by SimpleUri<BR>
     * NetNodes are stored only on demand or if cache cleaer (@link{CacheTable}) is running. This Method
     * does not changethe state of NetNodes (persistent / non-persistent) of NetNodes already cached.<P>
     * If the netNode shall be made persistent immediately, call get(SimpleUri, true). <BR>
     * If new NetNodes shall not be made persistent, call get(SimpleUri, false). <BR>
     * To explicitly change the state of a NetNode call @link{TableRecord#setPersistent}
     * @param sUri the URI
     */
    public static NetNode get(SimpleUri sUri) {
        NetNode netNode = null;

        try {
            // formatting uriString
            synchronized (lock) {
                // in hashtable?
                netNode = (NetNode) cache.get(1, sUri.toDocString());
                // no: create a new one
                if (netNode == null) {
                    netNode = new NetNode(sUri);
                    // System.out.println(netNode.getUri());
                    // System.out.println(sUri.getHexHashCode());
                    cache.put(netNode);
                    observable.addItem(netNode);
                }
            }
        } catch (Exception e) {
            System.out.println("Error in NetNode: \nCould not find NetNode in NetNodeCache: " + e.toString());
        }
        return netNode;
    }

    /**
     * get/create a NetNode by SimpleUri<BR>
     * @param sUri the URI
     * @param persistent shall the NetNode be made persistent? <BR>
     * true: yes, write NOW to database<br>
     * false: no, never. netNode.setPersistent is set to false<BR>
     * For deferred DB persistency call get-method without boolean parameter.
     */
    public static NetNode get(SimpleUri sUri, boolean persistent) {
        NetNode netNode = get(sUri);

        if (netNode == null) {
            return null;
        }
        if (persistent) {
            netNode.store();
        } else {
            netNode.setPersistent(false);
        }
        return netNode;
    }

    /**
     * returns the NetNode stored in the database under the specified id.
     * @param id the id
     * @return the NetNode or null if it does not exist
     */
    public static NetNode getById(String id) {
        NetNode netNode = null;

        synchronized (lock) {
            netNode = (NetNode) cache.get(0, id);
            if (netNode == null) {
                netNode = new NetNode(id);
                if (!netNode.hasRecordInDB) {
                    // System.out.println("NetNodeCache: Unknown NetNode ID!");   // Happens if DB was cleared and Browser cache not.
                    return null;
                }
                cache.put(netNode);
                observable.addItem(netNode);
            }
        }
        return netNode;
    }

    /**
     * returns the NetNode stored in the database under the specified id.
     * @param sUri the URI
     * @param persistent shall the NetNode be made persistent? <BR>
     * true: yes, now, <br>
     * false: no never.<BR>
     * For deferred DB persistency call get-method without boolean parameter.
     */
    public static NetNode get(String id, boolean persistent) {
        NetNode netNode = getById(id);

        if (netNode == null) {
            return null;
        }
        if (persistent) {
            netNode.store();
        } else {
            netNode.setPersistent(false);
        }
        return netNode;
    }

    /**
     * This method returns a Vector of NetNodes to a given query string
     * @param sqlClause The where clause for the query string.
     */
    public static Vector getNetNodes(String sqlClause) {
        Connection con = NetNode.dbTable.getConnection();
        Vector netNodes = new Vector();
        int i = 0;

        synchronized (lock) {
            ResultSet results = NetNode.dbTable.queryDb(con, NetNode.dbTable.getTableName(), sqlClause);

            // eval all records returned...
            while (results != null) {
                NetNode node = new NetNode(results);  // Create new use object
                String id = node.getNodeId();

                if (id.length() > 0 && !id.equals("-1")) // another set read from DB      
                {              
                    // System.out.println(results.getString("userId") + "  " +results.getString("userName"));
                    if (cache.get(0, id) == null) {   // user not known in cache...
                        cache.put(node);
                    }
                    netNodes.add(node);
                } else {  // End of results reached!
                    return netNodes;
                }
            }
        }
        return netNodes;
    }

    /**
     * This method returns a number of NetNodes to a given query string.
     * @param tables The table names.
     * @param sqlClause The where clause for the query string.
     * @return The number of nodes.
     */
    public static int getNetNodeCount(String tables, String sqlClause) {
        Connection con = NetNode.dbTable.getConnection();
        int count;

        synchronized (lock) {
            count = NetNode.dbTable.rowCountDb(con, tables, sqlClause);
        }
        return count;
    }

    /**
     * This method returns a number of NetNodes to a given query string.
     * @param sqlClause The where clause for the query string.
     * @return The number of nodes.
     */
    public static int getNetNodeCount(String sqlClause) {
        return getNetNodeCount(NetNode.dbTable.getTableName(), sqlClause);
    }

    /**
     * stores the cached NetNodes and removes empty entries from the cache.
     */
    public static void clean() {
        cache.clean();
    }
   
}
