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


/**
 * a <code>HtmlNode</code> represents a Html document.
 * <br>
 * use the static methods provided by this HtmlNodeCache to obtain HtmlNode objects.
 *
 * @author Harald Weinreich
 * @author Volkert Buchmann
 */

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

    protected static DBTableAdapter dbTable;

    protected NetNode node = null;

    protected LinkVector outgoingLinks = new LinkVector();
    protected LinkVector incomingLinks = new LinkVector();
    protected Vector inclusions = new Vector();

    // set sql meta-data
    static {

        dbTable = new DBTableAdapter("HtmlDocumentTable");

        /*
         dbTable=new DBTableAdapter("HtmlDocumentTable",16);

         dbTable.addField("nodeId",                dbTable.STRING,  "",     dbTable.KEY);
         dbTable.addField("title",                 dbTable.STRING,  "",     dbTable.FIELD);
         dbTable.addField("author",                dbTable.STRING,  "",     dbTable.FIELD);
         dbTable.addField("description",           dbTable.STRING,  "",     dbTable.FIELD);
         dbTable.addField("abstract",              dbTable.STRING,  "",     dbTable.FIELD);
         dbTable.addField("language",              dbTable.STRING,  "",     dbTable.FIELD);
         dbTable.addField("bodytext",              dbTable.STRING,  "",     dbTable.FIELD);
         dbTable.addField("sourcecode",            dbTable.STRING,  "",     dbTable.FIELD);
         dbTable.addField("numberOfLinks",         dbTable.NUMBER, "-1",    dbTable.FIELD);
         dbTable.addField("numberOfExternalLinks", dbTable.NUMBER, "-1",    dbTable.FIELD);
         dbTable.addField("numberOfImages",        dbTable.NUMBER, "-1",    dbTable.FIELD);
         dbTable.addField("spaceOfImages",         dbTable.NUMBER, "-1",    dbTable.FIELD);
         dbTable.addField("numberOfWords",         dbTable.NUMBER, "-1",    dbTable.FIELD);
         dbTable.addField("numberOfParagraphs",    dbTable.NUMBER, "-1",    dbTable.FIELD);
         dbTable.addField("fingerprint",           dbTable.STRING, "",      dbTable.FIELD);      // Save a fingerprint of that page to identify identical pages.
         dbTable.addField("type",                  dbTable.NUMBER, "0",     dbTable.FIELD);
         */
    }

    /**
     * returns the title of the document
     * @return the title of the document
     */
    public String getTitle() {
        return fieldValues.get("title");
    }

    /**
     * sets the title of the document
     * @param title the title of the document
     */
    public void setTitle(String title) {
        fieldValues.put("title", title);
    }

    /**
     * returns the author of the document
     * @return the author of the document
     */
    public String getAuthor() {
        return fieldValues.get("author");
    }

    /**
     * sets the author of the document
     * @param author the author of the document
     */
    public void setAuthor(String author) {
        fieldValues.put("author", author);
    }

    /**
     * returns the description of the document
     * @return the description of the document
     */
    public String getDescription() {
        return fieldValues.get("description");
    }

    /**
     * sets the dexcription of the document
     * @param des the description of the document
     */
    public void setDescription(String des) {
        fieldValues.put("description", des);
    }

    /**
     * returns the abstract of the document. These are the first lines without navigation link text.
     * @return the abstract of the document
     */
    public String getAbstract() {
        return fieldValues.get("abstract");
    }

    /**
     * sets the abstract of the document: Fist lines...
     * @param des the abstract of the document
     */
    public void setAbstract(String abs) {
        fieldValues.put("abstract", abs);
    }

    /**
     * returns the language of the document
     * @return the language of the document
     */
    public String getLanguage() {
        return fieldValues.get("language");
    }

    /**
     * sets the language of the document
     * @param lang the language of the document
     */
    public void setLanguage(String lang) {
        fieldValues.put("language", lang);
    }

    /**
     * returns the bodyText of the document (any words which are not inside tags)
     * @return the bodyText of the document
     */
    public String getBodyText() {
        return fieldValues.get("bodytext");
    }

    /**
     * sets the bodyText of the document (any words which are not inside tags)
     * @param body the body of the document
     */
    public void setBodyText(String bodyText) {
        fieldValues.put("bodytext", bodyText);
    }

    /**
     * returns the source code of the document
     * @return the sourceCode of the document
     */
    public String getSourceCode() {
        return fieldValues.get("sourcecode");
    }

    /**
     * sets the source code of the document
     * @param body the sourceCode of the document
     */
    public void setSourceCode(String sourceCode) {
        fieldValues.put("sourcecode", sourceCode);
    }

    /**
     * returns the fingerprint of the document
     * @return a (quite) unique 16 digit hex number string
     */
    public String getFingerprint() {
        return fieldValues.get("fingerprint");
    }

    /**
     * sets the fingerprint of the document
     * @param fingerprint Fhe fingerprint of the document
     */
    public void setFingerprint(String fingerprint) {
        fieldValues.put("fingerprint", fingerprint);
    }

    /**
     * returns the number of links defined in the document
     * @return the number of links in the document
     */
    public String getNumberOfLinks() {
        return fieldValues.get("numberOfLinks");
    }

    /**
     * sets the number of links defined in the document
     * @param links the number of links in the document
     */
    public void setNumberOfLinks(String links) {
        fieldValues.put("numberOfLinks", links);
    }

    /**
     * returns the number of links to documents stored on other servers
     * @return the number of links to external documents
     */
    public String getNumberOfExternalLinks() {
        return fieldValues.get("numberOfExternalLinks");
    }

    /**
     * sets the number of links to documents stored on different servers
     * @param v the number of links to external documents
     */
    public void setNumberOfExternalLinks(String v) {
        fieldValues.put("numberOfExternalLinks", v);
    }

    /**
     * returns the number of images on the document
     * @return the number of images
     */
    public String getNumberOfImages() {
        return fieldValues.get("numberOfImages");
    }

    /**
     * sets the number of images on the document
     * @param v the number of images on the document
     */
    public void setNumberOfImages(String v) {
        fieldValues.put("numberOfImages", v);
    }

    /**
     * returns the space of the page occupied by images
     * @return the space in sqare pixels, -1 if unknown.
     */
    public String getSpaceOfImages() {
        return fieldValues.get("spaceOfImages");
    }

    /**
     * sets the space of the page occupied by images
     * @param v the sapce used by the images of the document
     */
    public void setSpaceOfImages(String v) {
        fieldValues.put("spaceOfImages", v);
    }

    /**
     * returns the number of words of the document
     * @return the number of words of the document
     */
    public String getNumberOfWords() {
        return fieldValues.get("numberOfWords");
    }

    /**
     * sets the number of words of the document
     * @param v the number of words of the document
     */
    public void setNumberOfWords(String v) {
        fieldValues.put("numberOfWords", v);
    }

    /**
     * returns the number paragraphs &quot;&lt;P&gt;&quot; of the document
     * @return the number of paragraphs
     */
    public String getNumberOfParagraphs() {
        return fieldValues.get("numberOfParagraphs");
    }

    /**
     * sets the number paragraphs &quot;&lt;P&gt;&quot; of the document
     * @param p the number of paragraphs
     */
    public void setNumberOfParagraphs(String p) {
        fieldValues.put("numberOfParagraphs", p);
    }

    /**
     * returns the <code>NetNode</code> where this document is stored
     * @return the NetNode
     */
    public NetNode getNode() {
        return node;
    }

    /**
     * returns the id of the <code>NetNode</code> where this document is stored
     * @return the netNodeId
     */
    public String getNodeId() {
        return node.getNodeId();
    }

    /**
     * creates a new HtmlNode (a NetNode that represents a Html document)
     * @param node the NetNode
     */
    protected HtmlNode(NetNode node) {
        this.node = node;
        dbTable.init(this);
        fieldValues.put("nodeId", node.getNodeId());
        dbTable.dbCheck(this);              // We have to check if it is in DB!
    }

    /**
     * store this object in the database
     */
    public void store() {
        if (hasRecordInDB) {    // Entry exists in DB: Update
            dbTable.updateDB(this);
        } else {                  // Create new...
            dbTable.createInDB(this);
        }
    }

    /**
     * returns the key of this object which can be used in Hashtable objects
     * @return the key
     */
    public Object getKey(int KeyNo) {
        return getNodeId();
    }

    public String toString() {
        return "HTMLObject\t " + this.node.toString();
    }

    /**
     * Returns the Links defined in this document. CONSIDERLINKS has to be set, for this method to work!
     * @return a Vector of the outgoing links
     */
    public LinkVector getOutgoingLinks() {
        if (outgoingLinks.size() == 0) {
            outgoingLinks = LinkCache.getLinksFrom(node);
        }
        return outgoingLinks;
    }

    /**
     * Returns the Links from other documents to this one. CONSIDERLINKS has to be set, for this method to work!
     * @return a Vector of incoming links
     */
    public LinkVector getIncomingLinks() {
        if (incomingLinks.size() == 0) {
            incomingLinks = LinkCache.getLinksTo(node);
        }
        return incomingLinks;
    }

    /**
     * Returns the Inclusions defined in this document.
     * Please node: CONSIDERINCLUSIONS has to be set, for this method to work!
     * @return a Vector of Inclusions of this Node
     */
    public Vector getIncludedObjects() {
        if (inclusions.size() == 0) {
            inclusions = InclusionCache.getIncludedObjects(node);
        }
        return inclusions;
    }

    /**
     * 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() {
        node.access();
    }

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

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

    /**
     * Sets the size of this node in bytes.
     * @param size the size in bytes
     */
    public void setSize(String size) {
        node.setSize(size);
    }

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

    /**
     * Sets the date of the last modification of the node  (at the server)
     * @param date the lastModified-date
     */
    public void setLastModified(long time) {
        node.setLastModified(time);
    }

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

    /**
     * Sets the mimeType of the node
     * @param v the mimeType
     */
    public void setMimeType(String v) {
        node.setMimeType(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 node.getAccessStatus();
    }

    /**
     * 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) {
        node.setAccessStatus(v);
    }

    /**
     * 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 node.getFirstAccess();
    }

    /**
     * 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 node.getLastAccess();
    }

    /**
     * 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) {
        node.setLastAccess(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 node.getAccessCounter();
    }

    /**
     * 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) {
        node.setAccessCounter(v);
    }

    /**
     * Increases the accessCounter.<br>
     * <b>Use <code>access()</code> instead for consistency!</b>
     */
    protected void incAccessCounter() {
        node.incAccessCounter();
    }

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

    /**
     * Returns if the page has frames.<br> Bit 0 of type.
     */
    public boolean isFrames() {
        return isTypeBit(0);
    }

    /**
     * Set to true, if the page has a frameset and frame tags.
     */
    public void setFrames(boolean b) {
        setTypeBit(0, b);
    }

    /**
     * Returns if the page has form tags.<P>Bit 1 of type<br>
     */
    public boolean isForms() {
        return isTypeBit(1);
    }

    /**
     * Set to true, if the page has form tags, i.e. it includes forms.
     */
    public void setForms(boolean b) {
        setTypeBit(1, b);
    }

    /**
     * Returns if the document is commercial (with Ads etc).<br> Bit 2 of type.
     */
    public boolean isCommercial() {
        return isTypeBit(2);
    }

    /**
     * Set to true, if the page has ads.
     */
    public void setCommercial(boolean b) {
        setTypeBit(2, b);
    }

    /**
     * Returns if the page uses JavaScript.<br> Bit 3 of type.
     */
    public boolean isJavaScript() {
        return isTypeBit(3);
    }

    /**
     * Set to true, if the page uses JavaScript.
     */
    public void setJavaScript(boolean b) {
        setTypeBit(3, b);
    }

    /**
     * Returns if the page needs a plugin.<br> Bit 4 of type
     */
    public boolean isPlugins() {
        return isTypeBit(4);
    }

    /**
     * Set to true, if the page needs a plugin.
     */
    public void setPlugins(boolean b) {
        setTypeBit(4, b);
    }

    /**
     * Returns if the page has animated graphics.<br> Bit 5 of type.
     */
    public boolean isAnimation() {
        return isTypeBit(5);
    }

    /**
     * Set to true, if the page has animated Graphics, movies, flash etc.
     */
    public void setAnimation(boolean b) {
        setTypeBit(5, b);
    }

    /**
     * Returns if the page "has" sound.<br>Bit 6 of type.
     */
    public boolean isSound() {
        return isTypeBit(6);
    }

    /**
     * Set to true, if the page has background sound.
     */
    public void setSound(boolean b) {
        setTypeBit(6, b);
    }

    /**
     * Returns if the page was completely downloaded.<br>
     */
    public boolean isCompletelyDownloaded() {
        return isTypeBit(10);
    }

    /**
     * Set to true, if the page was completely downloaded. Bit 10 of type.
     */
    public void setCompletelyDownloaded(boolean b) {
        setTypeBit(10, b);
    }

    /**
     * set bits in the type-variable.<P>
     * frames:      Bit 0<BR>
     * forms:       Bit 1<BR>
     * commercial:  Bit 2<BR>
     * javascript:  Bit 3<BR>
     * plugins:     Bit 4<BR>
     * animation:   Bit 5<BR>
     * sound:       Bit 6<BR>
     * completelyDownloaded: Bit 10
     */
    private synchronized void setTypeBit(int position, boolean b) {
        int typeValue = Integer.parseInt(fieldValues.get("type"));
        int mask = 1 << position;

        if (b) { // true: set bit
            typeValue |= mask;
        } else { // false: unset bit
            mask = mask ^ Integer.MAX_VALUE;  // all bits reversed
            typeValue &= mask; // only specified bit is unset.
        }
        fieldValues.put("type", String.valueOf(typeValue));
        // System.out.println(this.node.toString()+"  "+ position + "   " + b);
        // System.out.println(Integer.toBinaryString(Integer.parseInt(fieldValues.get("type"))));
    }

    /**
     * see if bit at specified position of the type-variable was set. see setTypeBit
     */
    private synchronized boolean isTypeBit(int position) {
        int typeValue = Integer.parseInt(fieldValues.get("type"));
        int mask = 1 << position;

        typeValue &= mask;   // find bit
        return (typeValue != 0);  // return if bit is set.
    }

    // 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 node.toDocString();
    }

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

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

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

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

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

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

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

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

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

}
