/*
 * 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.ResultSet;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;


/**
 * a <code>NetNode</code> represents a node from the Internet.
 * <br>
 * use the static methods provided by NetNodeCache class to obtain NetNode objects.
 *
 * @author Harald Weinreich
 * @author Volkert Buchmann
 */

public class NetNode extends TableRecord {
    public static final String COPYRIGHT = "(C) Harald Weinreich & Volkert Buchmann";

    // db-management
    public static DBTableAdapter dbTable;

    // parsed uri
    protected SimpleUri sUri;

    // set sql meta-data
    static {
        dbTable = new DBTableAdapter("NetNodeTable");

        /* dbTable=new DBTableAdapter("NetNodeTable",12);

         dbTable.addField("nodeId",          dbTable.STRING,  "",dbTable.KEY);
         dbTable.addField("uri",             dbTable.STRING,  "",dbTable.FIELD);
         dbTable.addField("size",            dbTable.NUMBER,"-1",dbTable.FIELD);
         dbTable.addField("lastModified",    dbTable.NUMBER,"-1",dbTable.FIELD);
         dbTable.addField("mimeType",        dbTable.STRING,  "",dbTable.FIELD);
         dbTable.addField("accessStatus",    dbTable.STRING,"-1",dbTable.FIELD);
         dbTable.addField("host",            dbTable.STRING,  "",dbTable.FIELD);
         dbTable.addField("type",            dbTable.NUMBER, "0",dbTable.FIELD);
         dbTable.addField("firstAccess",     dbTable.NUMBER, "0",dbTable.FIELD);
         dbTable.addField("lastAccess",      dbTable.NUMBER, "0",dbTable.FIELD);
         dbTable.addField("lastRobotAccess", dbTable.NUMBER, "0",dbTable.FIELD);
         dbTable.addField("accessCounter",   dbTable.NUMBER, "0",dbTable.FIELD);
         */
    }

    /**
     * Creates an empty <code>NetNodeCache</code> set with no equivalent set in the <code>scone</code> database<br>
     * Such an object may used to find out whether a corresponding set exists
     * @param sUri the parsed URI of the node
     */
    protected NetNode() {
        dbTable.init(this);           // initialize fields with defaults
        // System.out.println("NetNode!");
    }

    /**
     * Creates or reads a <code>NetNodeCache</code> set in the <code>scone</code> database<br>
     * The corresponding set in the <code>scone</code> database is identified by the URI.
     * If such a set already exists, the values are read from the database. Else a set is created.
     * @param sUri the parsed URI of the node
     */
    protected NetNode(SimpleUri sUri) {
        this();
        setNodeId(sUri.getHexHashCode());   // set the keys...
        // if (getNodeId().equals("000000000000003A"))
        //     throw new Error("----"+sUri+"xxxxx");
        dbTable.dbCheck(this);              // We have to check if it is in DB!
        if (hasRecordInDB)                          // Found in Database?
        {  // Yes:
            this.sUri = new SimpleUri(getUri());  // Objects are not set automatically
        } else {
            // No: Try to fill myself...
            this.sUri = sUri;                     // store sUri
            setMimeType(sUri.getMimeType());
            setUri(sUri);         // set uri and host!
            computeType();
        }
    }

    /**
     * Reads a <code>NetNodeCache</code> set in the <code>scone</code> database<br>
     * The corresponding set in the <code>scone</code> database is identified by the
     * <code>nodeId</code> which may have been obtained from other objects.
     * If such a set already exists, the values are read from the database.
     * Else, hasRecordInDB is set to false
     * @param id the code>nodeId</code> of the desired node
     */
    protected NetNode(String id) {
        this();
        fieldValues.put("nodeId", id);
        dbTable.dbCheck(this);
        if (hasRecordInDB) {
            sUri = new SimpleUri(getUri());
        } // Objects are not set automatically
    }

    /**
     * Create Netnode to Result of DB-Query.
     * @param results the ResultSet returnetd by the query.
     */
    public NetNode(ResultSet results) {
        dbTable.init(this);
        dbTable.fill(this, results);  // -> if there was a new row in results -> Fill user and hasRecordInDB=true
    }

    /**
     * sets <code>lastAccess</code> to now and increments <code>accessCounter</code><br>
     * if the node is visted for the first time, the method sets <code>firstAccess</code>
     */
    public void access() {
        // is the node accessed for the first time?
        if (getAccessCounter() == 0) {
            fieldValues.put("firstAccess", String.valueOf((new Date()).getTime()));
        }

        setLastAccess((new Date()).getTime());
        incAccessCounter();
        // System.out.println("Access!!!");
    }

    /**
     * Returns the key for this database set
     * @return <code>nodeId</code>
     */
    public String getNodeId() {
        return fieldValues.get("nodeId");
    }

    /**
     * Set the hash key for this database set
     * @param <code>nodeId</code>
     */
    private void setNodeId(String id) {
        fieldValues.put("nodeId", id);
    }

    /**
     * Returns the URI of the node
     * @return the URI
     */
    public String getUri() {
        return fieldValues.get("uri");
    }

    /**
     * Sets the URI and HOST of this node. The URI is a key value, thus it may not be changed externally.
     * @param uri the URI
     */
    private void setUri(SimpleUri sUri) {
        fieldValues.put("uri", sUri.toDocString());
        fieldValues.put("host", sUri.getHost());      // Redundand, aber schneller!
    }

    /**
     * Returns the size of the node in bytes
     * @return the size in bytes
     */
    public String getSize() {
        return fieldValues.get("size");
    }

    /**
     * Sets the size of this node in bytes.
     * @param size the size in bytes
     */
    public void setSize(String size) {
        if (size.length() == 0) {
            size = "-1";
        }
        fieldValues.put("size", size);
    }

    /**
     * Returns the date of the last modification of the node (at the server)
     * @return the lastModified-date
     */
    public long getLastModified() {
        return Long.parseLong(fieldValues.get("lastModified"));
    }

    /**
     * Sets the date of the last modification of the node (at the server). Use the string format
     * "EEE, dd MMM yyyy HH:mm:ss z" as common for the "Last-Modified" parameter.
     * @param date the lastModified-date
     */
    public void setLastModifiedString(String lastModifiedString) {
        // System.out.println(time);
        // System.out.println(lastModifiedString);
        SimpleDateFormat formatter = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
        ParsePosition pos = new ParsePosition(0);
        formatter.setTimeZone(TimeZone.getDefault());
        // formatter.setTimeZone(TimeZone.getTimeZone("GMT+01:00"));
        Date lastModified = formatter.parse(lastModifiedString, pos);
        // System.out.println(lastModified);
        if (lastModified!=null) {     // Sometimes formatting goes wrong!
            long time = lastModified.getTime();
            setLastModified(time);
        }
    }

    /**
     * Sets the date of the last modification of the node  (at the server)
     * @param date the lastModified-date
     */
    public void setLastModified(long time) {
        if (time != 0) {
            fieldValues.put("lastModified", String.valueOf(time));
        }
    }

    /**
     * Returns the mimeType of the node, for example <code>text/html</code>
     * @return the mimeType
     */
    public String getMimeType() {
        return fieldValues.get("mimeType");
    }

    /**
     * Sets the mimeType of the node
     * @param v the mimeType
     */
    public void setMimeType(String v) {
        if (getMimeType().equals("")) {
            fieldValues.put("mimeType", v);
        }
    }

    /**
     * Returns the status of the node, for example <code>200</code> or <code>404</code><br>
     * if <code>-1</code> is returned, the node has not yet been visited
     * @return the status, or <code>-1</code> if no status is available
     */
    public String getAccessStatus() {
        return fieldValues.get("accessStatus");
    }

    /**
     * Sets the status of the node, if the status has been -1 and is now changed, <code>firstAccess</code> is set
     * @param v the status
     */
    public void setAccessStatus(String v) {
        // is the node accessed for the first time?
        if (fieldValues.get("accessStatus").equals("-1")
                && !v.equals("-1")) {}
        fieldValues.put("accessStatus", v);
    }

    /**
     * returns the NetNode type bitfield parameter
     * @return the typ parameter
     */
    public int getType() {
        return Integer.parseInt(fieldValues.get("type"));
    }

    /**
     * sets the type parameter
     * @param t the type parameter
     */
    public void setType(int t) {
        fieldValues.put("type", String.valueOf(t));
    }

    /**
     * Returns the date of the first access of this node.<br>
     * Be sure that this node has been accessed before this date is used!
     * @return the date of the first access
     */
    public long getFirstAccess() {
        return Long.parseLong(fieldValues.get("firstAccess"));
    }

    /**
     * Returns the date of the last access of this node.<br>
     * Be sure that this node has been accessed before this date is used!
     * @return the date of the last access
     */
    public long getLastAccess() {
        return Long.parseLong(fieldValues.get("lastAccess"));
    }

    /**
     * Sets the date of the last access (the current access!) of the node.<br>
     * <b>Use <code>access()</code> instead for consistency!</b>
     * @param v the date
     */
    protected void setLastAccess(long v) {
        fieldValues.put("lastAccess", String.valueOf(v));
    }

    /**
     * Returns the times the node has been accessed by any scone-user
     * @return the times the node has been accessed
     */
    public int getAccessCounter() {
        return Integer.parseInt(fieldValues.get("accessCounter"));
    }

    /**
     * Sets the accesCounter to the specified value.<br>
     * <b>Use <code>incAccessCounter()</code> instead for consistency!</b>
     * @param v the date
     */
    protected void setAccessCounter(int v) {
        fieldValues.put("accessCounter", String.valueOf(v));
    }

    /**
     * Increases the accessCounter.<br>
     * <b>Use <code>access()</code> instead for consistency!</b>
     */
    protected void incAccessCounter() {
        // try {
            setAccessCounter(getAccessCounter() + 1);
        // } catch (Exception e) { // could not convert value in accessCounter to a number!
        //    setAccessCounter(1); // accessed at least once!
        //  }
    }

    /**
     * Returns the date of the last robot-access of this node.<br>
     * @return the date of the last robot-access
     */
    public long getLastRobotAccess() {
        return Long.parseLong(fieldValues.get("lastRobotAccess"));
    }

    /**
     * Sets the date of the last robot-access (the current access!) of the node.<br>
     * @param v the date
     */
    public void setLastRobotAccess(long v) {
        fieldValues.put("lastRobotAccess", String.valueOf(v));
    }

    /**
     * Returns the parsed URI of the node
     * @return the parsed URI  of the node
     */
    public SimpleUri getSUri() {
        return sUri;
    }

    /**
     * fills the fields <code>mimeType, size,</code> and </code>accessStatus</code> from a
     * <code>DocumentInfo</code>-object
     * @param documentInfo the <code>DocumentInfo</code>-object belonging to this node
     */
    public void fill(com.ibm.wbi.protocol.http.DocumentInfo documentInfo) {
        setMimeType(documentInfo.getResponseContentType());
        setSize(documentInfo.getResponseContentLength());
        setAccessStatus(String.valueOf(documentInfo.getResponseCode()));
        // setLastModified(documentInfo.getByKey("Last-Modified",MarkupLanguageInfo.RESPONSE_HEADER));
        int REQUEST_HEADER = 0;
        int RESPONSE_HEADER = 1;
        String lastModifiedString = documentInfo.getByKey("Last-Modified", RESPONSE_HEADER);

        if (lastModifiedString != null) {
            setLastModifiedString(lastModifiedString);
        } else {
            // System.out.println("No date returned!");
        }

    }

    /**
     * Compute NetNode type. Type information is stored in bits of an integer value. This data is
     * provided to make database access to this kind of information easier and faster.<P>
     * bit  0: Path: Path defined<BR>
     * bit  1: No Path: No path defined<BR>
     * bit  2: Filename: filename defined<BR>
     * bit  3: No Filename: no filename in URI<BR>
     * bit  4: Fragment: Fragment (#) defined<BR>
     * bit  5: No Fragment<BR>
     * bit  6: Query: Query GET-parameters defined<BR>
     * bit  7: No Query: Query GET-parameters not defined<BR>
     * bit 10: Homepage: Homepage of server<BR>
     * <BR>
     * bit 14: HTTP: Prptpcol is http.<BR>
     * bit 15: Other protocol: Prptpcol is not http but mail, ftp etc.<BR>
     * <BR>
     * bit 20: Mime type is HTML<BR>
     * bit 21: Mime type is NOT HTML<BR>
     * bit 22: Mime type: Text/*<BR>
     * bit 23: Mime type: Image/*<BR>
     */
    public void computeType() {
        int type = 0;

        if (sUri.getPath().length() > 0) {     // path
            type |= 1 << 0;
        } else {
            type |= 1 << 1;
        }

        if (sUri.getFile().length() > 0) {     // Filename of URI
            type |= 1 << 2;
        } else {
            type |= 1 << 3;
        }

        if (sUri.getFragment().length() > 0) {                         // Fragment defined
            type |= 1 << 4;
        } else {
            type |= 1 << 5;
        }

        if (sUri.getQuery().length() > 0) {                            // Query defined
            type |= 1 << 6;
        } else {
            type |= 1 << 7;
        }

        if (sUri.getPath().length() == 0 && sUri.getFile().length() == 0
                && sUri.getQuery().length() == 0) {     // homepage
            type |= 1 << 10;
        }

        // Protocol
        if (sUri.getProtocol().equals("http")) {
            type |= 1 << 14;
        } else {
            type |= 1 << 15;
        }

        // Mime-Type
        if (this.getMimeType().equals("text/html")) { // HTML-Page
            type |= 1 << 20;
        } else {
            type |= 1 << 21;
        }

        if (this.getMimeType().indexOf("text") >= 0) {  // Text
            type |= 1 << 22;
        }

        if (this.getMimeType().indexOf("image") >= 0) {  // Graphics
            type |= 1 << 23;
        }

        setType(type);
    }

    // SimpleUri methods
    // these methods are here for eays access to the URI-parts
    // they are dcumented in <code>SimpleUri</code> and will be documented later in this class, sorry!
    public String toDocString() {
        return sUri.toDocString();
    }

    public String toHostString() {
        return sUri.toHostString();
    }

    public String toString() {
        return sUri.toString();
    }

    public String getProtocol() {
        return sUri.getProtocol();
    }

    public String getHost() {
        return sUri.getHost();
    }

    public String getMainHost() {
        return sUri.getMainHost();
    }

    public String getPort() {
        return sUri.getPort();
    }

    public String getPath() {
        return sUri.getPath();
    }

    public String getFile() {
        return sUri.getFile();
    }

    public String getExtension() {
        return sUri.getExtension();
    }

    public String getQuery() {
        return sUri.getQuery();
    }

    public String getFragment() {
        return sUri.getFragment();
    }

    public String getOpaquePart() {
        return sUri.getOpaquePart();
    }

    public String getRef() {
        return sUri.getRef();
    }

    /**
     * Writes the content of the object into the <code>scone</code> database if it has been changed.<br>
     * This method will be called from time to time by the cache
     */
    public void store() {
        if (hasRecordInDB) {    // Entry exists in DB: Update
            dbTable.updateDB(this);
        } else {                  // Create new...
            dbTable.createInDB(this);
        }
    }

    /**
     * @param the number of the key
     * @return key returns the key
     */
    public Object getKey(int keyNo) {
        switch (keyNo) {
        case 0:
            return getNodeId();

        case 1:
            return getUri();

        default:
            System.out.println("Error!!!!");
            return "";
        }
    }

    /**
     * Associates a keyword with this node
     * @param keyword the keyword
     */

    /* public void addKeyword(String keyword){
     Keyword kw=KeywordCache.get(keyword);
     Node2KeyCache.associate(this,kw);
     }*/

}
