/*
 * 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.Date;
import java.util.Properties;

import scone.netobjects.NetNode;
import scone.netobjects.NetNodeCache;
import scone.netobjects.UserCache;
import scone.util.ErrorLog;

import com.ibm.wbi.RequestEvent;
import com.ibm.wbi.RequestRejectedException;
import com.ibm.wbi.protocol.http.DocumentInfo;
import com.ibm.wbi.protocol.http.HttpGenerator;
import com.ibm.wbi.protocol.http.HttpRequest;
import com.ibm.wbi.protocol.http.HttpResponse;
import com.ibm.wbi.protocol.http.beans.FileGenerator;
import com.ibm.wbi.protocol.http.beans.FormHelper;
import com.ibm.wbi.protocol.http.beans.StaticHtmlGenerator;


/**
 * This meg creates the user logging entries in the access database.<P>
 * If the user is unknown (no cookie ID to the host tracking.scone.de is set)
 * a login screen is displayed<P>
 * After the login, a cookie is set and from now on the userId from this cookie is used.<P>\n
 * The meg is called when
 * <OL>
 * <LI> a transfer is startet (filename = _start)
 * <LI> the page was loaded (filename = _load)
 * <LI> the page was left (filename = _stop)
 * </OL>\n
 * The correcconding JavaScript-Code is inserted by the JavaScriptAdder.<BR>
 * The JS-methods stop() and loaded() calls are inserted by the BodyEventAdder.<BR>
 * onClick-events to all Links can be added by activating the LinkEventAdder.
 * <P>
 * To access the Scone parameters in your JavaScript-Code please use the following variables:<OL>
 * <LI>Scone_Parent_Name is the name of parent frame
 * <LI>Scone_Parent_Location is the URI of the parent frame
 * <LI>Scone_UserID is the current User ID
 * </OL>
 *
 * @author Harald Weinreich, Torsten Hass
 * @version 1.2, 03/31/2003
 */
public class AccessTrackingMeg extends HttpGenerator {

    // constants definition for appletControl
    public final static int AC_SHOWAPPLET = 1 << 0;     // Tells the applet to create a output window
    public final static int AC_DISABLEACCESSTRACKING = 1 << 1;     // Tells the applet not to send data to 
    // AppletConnector, but only listen
    // data is collected by IESpy
    public final static int AC_IESPYACCESSTRACKING = 1 << 2;     // Tells the JavaScript code not to send
    // data to the applet, but show it
    // to statusline for IESpy to read it

    private scone.Plugin plugin = null;
    private DocumentInfo documentInfo;
    private String       file;
    private Properties   formParameter;
    private HttpRequest  request;
    private HttpResponse response;
    private String       id;            // userid as string
    private long         userId;        // userid as integer
    private String       nodeId = ""; // NodeId of the current object.
    private long         start = 0L; // Start time of visit to page
    private String       frame = ""; // Name of this frame
    private String       parentNodeId = ""; // nodeId of parent frame
    private String       parentFrame = ""; // Name of parent frame
    private String       referrerNodeId = ""; // NodeId of the referrer
    private int          connectionPort;
    private int          resourcePort;
    private int          appletControl = 0;
    private String       clientInetAddress = "";      // String of IP-Address of browser...

    public AccessTrackingMeg(scone.Plugin plugin) {
        this.plugin = plugin;
    }

    public void handleRequest(RequestEvent e) throws RequestRejectedException {

        documentInfo = (DocumentInfo) e.getRequestInfo(); // get RequestInfo
        file = documentInfo.getPath();           // get Path+File of the URL

        formParameter = FormHelper.interpretFormData(e);  // get Query Parameter from URL
        request = new HttpRequest(e, true);          // Get the request header
        id = request.getCookie("ID");          // Get Cookie "ID"

        byte [] caddr1 = documentInfo.getClientAddress().getAddress();
        long caddr2 = 0;
        for (int i = 0; i < caddr1.length; i++ )
            caddr2 = (caddr2 * 1000) + caddr1[i];
        clientInetAddress = String.valueOf(caddr2);
        
        if (AccessTracking.props.get("Disable user handling").equals("true")) {
            id = clientInetAddress;
        }  // User tracking disabled: UserID is always "0"

        // test if the cookie contains a valid UserID
        try {
            if (id != null) {
                userId = Long.parseLong(id);
            }
        } catch (NumberFormatException ex) {
            System.err.println("Error: Cookie ID is no number");
            userId = 0L;
            id = null;
        }

        HttpGenerator g = null;

        // default browsing, transfer of page starts: file="_id"., return Javascript for tracking or Login-Screen...
        if (file.startsWith("/_id")) {
            // Verify id (and userId).
            if ( id == null || (!id.equals("0") && UserCache.checkById(id) == null) )  // No Cookie or user not known. Redirecting to new user screen...
            {
                StaticHtmlGenerator sg = new StaticHtmlGenerator(); // Return generated document to the client
                String jsCode = "function _scone_loaded() { window.location.href='http://users.scone.de/'; }\n";                      // Dummy functions

                jsCode += "function _scone_stop() {}\n";
                // jsCode += "setTimeout(\"window.location.href='http://users.scone.de/' \",500);";   //Redirect URL with timeout to avoid errors in IE4
                // jsCode += "window.location.href='http://users.scone.de/';";   //Redirect URL with timeout to avoid errors in IE
                sg.setStaticHtml(jsCode);
                sg.setContentType("application/x-javascript");      // Set type to javascript
                g = sg;                                               // Return Generator
                // System.out.println(jsCode);
                if (id == null) {
                    System.out.println("No user Cookie found for .scone.de -> redirecting browser to user tracking screen");
                } else {
                    System.out.println("User Id " + id + " unknown -> redirecting browser to user tracking screen");
                }

            } 
            else // User known
            {
                // Create nodeIdString from filename
                String nodeId = file.substring(4); // "/_id".length() is 4

                if (nodeId == null || nodeId.length() == 0) {
                    nodeId = "0";
                }   // was it available?

                // Return JavaScript code to log user actions...
                StaticHtmlGenerator sg = new StaticHtmlGenerator(); // Return generated document to the client
                String startTime = (new Long((new Date()).getTime())).toString();
                String jsCode = "";
                
                jsCode += "var Scone_Parent_Name='';\n";        // Name of parent frame
                jsCode += "var Scone_Parent_Location='';\n";    // URI of parent frame
                jsCode += "var Scone_UserID='"+id+"';\n";             // User ID
                jsCode += "var _scone_parent_location='';\n";         
                jsCode += "var _scone_parent_name='';\n";
                jsCode += "var _scone_history_length='0';\n";
                jsCode += "var _scone_referrer='';\n";
                jsCode += "var _scone_stop_event_sent='0';\n";

                jsCode += "\n\ntry { \n";  // TRY-CATCH-Code: The access to some objects may be forbidden...

                // Next line dropped because IE bug gives wrong values. Work aroud with BrowserHistory
                // jsCode += "if (history.length) {_scone_history_length=history.length;}\n";
                // Test window.name for '_blank' is needet for Netscape 4.x
                jsCode += "if ((!window.name) || (window.name=='_blank')) { window.name = 'SCONE'+_scone_start_time+Math.floor(Math.random()*10000); _scone_referrer='&referrer=[New%20Window]';}\n";
                jsCode += "else if(document.referrer) _scone_referrer='&referrer='+escape(document.referrer)\n";
                jsCode += "if (window.location!=window.parent.location) _scone_parent_location='&parentUri='+escape(window.parent.location);\n";
                jsCode += "if (window.name!=window.parent.name) _scone_parent_name='&parent='+escape(window.parent.name);\n";
                jsCode += "if (window.name!=window.parent.name) {Scone_Parent_Name=parent.top.name; Scone_Parent_Location=parent.top.location.href;}\n";
                
                jsCode += "}catch (exception) {\n";  // TRY-CATCH!
                // jsCode += "  if (exception.description == null) { alert(\"Exception: \" + exception.message); }\n";
                // jsCode += "  else { alert(\"Exception: \" + exception.description); }\n";
                jsCode += "}\n\n";
                
                // Generate imagenames and filenames from nodeId. Otherwise problems with frames (pictures loaded only once.)
                // These lines could _theoretically_ be shorter, but there are problems with JavaScript...
                jsCode += "_scone_a" + nodeId + " = new Image(); _scone_a"
                        + nodeId + ".src = 'http://tracking.scone.de/_start"
                        + nodeId
                        + "?start='+_scone_start_time+'&frame='+escape(window.name)+_scone_parent_name+_scone_referrer+_scone_parent_location;\n";

                try {
                    // Create a bit array to control Applet's behaviour
                    // and send it via parameter to the applet. 
                    if (AccessTracking.props.get("Acesstracking1: Show Applet for Debugging").equals("true")) {
                        appletControl = appletControl | AC_SHOWAPPLET; // Tells the applet to show it's gui
                    }
                    if (AccessTracking.props.get("Acesstracking1: Disable Applet").equals("true")) {
                        appletControl = appletControl | AC_DISABLEACCESSTRACKING; // Tells the applet not to transmit Data to AppletConnector
                    }
                    if (AccessTracking.props.get("Acesstracking1: Enable IESpy").equals("true")) {
                        appletControl = appletControl | AC_IESPYACCESSTRACKING; // Tells the applet not to transmit Data to AppletConnector
                    }
                } catch (Exception exc) {
                    exc.printStackTrace();
                    ErrorLog.log(this, "AccessTrackingMeg: Error while reading Scone properties", "", exc);
                }
                // If Accesstracking by IESpy is enabled, send data to Statusline
                if ((appletControl & AC_IESPYACCESSTRACKING) > 0) {
                    jsCode += "  window.status=window.name+\":SconeStartTime=\"+_scone_start_time;\n";
                    jsCode += "  window.status=window.name+\":SconeUserId=\"+\"" + userId + "\";\n";
                    jsCode += "  window.status=window.name+\":SconeNodeId=\"+\"" + nodeId + "\";\n";
                    jsCode += "  window.status=window.name+\":SconeFragment=\"+window.location.hash;\n";
                    jsCode += "  window.status=window.name+\":SconeQuery=\"+window.location.search;\n";
                    jsCode += "  window.status=window.name+\":SconeParentNodeName=\"+Scone_Parent_Location;\n";
                    jsCode += "  window.status=window.name+\":SconeParentFrameName=\"+Scone_Parent_Name;\n";
                    jsCode += "  window.status=window.name+\":SconeReferrer=\"+document.referrer;\n";
                    jsCode += "  window.status=window.name+\":SconeFrameCount=\"+parent.frames.length;\n";
                    jsCode += "  window.status=\"\";\n";
                }

                // Get "Acesstracking1: Show Applet for Debugging" from Properties to show or hide the applet's output
                if ((appletControl & AC_SHOWAPPLET) > 0) {
                    jsCode += "document.writeln('<div style=\"visibility:visible;\">');";
                } else {
                    jsCode += "if (navigator.appName == \"Netscape\") {document.writeln('<div style=\"visibility:visible;\">');}\n";
                    jsCode += "else {document.writeln('<div style=\"visibility:hidden; position:absolute; top:0px; left:0px;\">');}\n";
                }
                // Include the applet tag into html page
                jsCode += "document.writeln('<applet code=\"scone.accesstracking.applet.LogApplet\" archive=\"LogApplet.jar\" ";
                // Get Scone IP 
                jsCode += "codebase=\"http://" + ((AccessTracking)plugin).getSconeIP().trim() + ":";
                // Get resource port 
                jsCode += ((AccessTracking)plugin).getResourcePort() + "/accesstracking/res\" name=\"LogApplet\" ";
                // Get "Acesstracking1: Show Applet for Debugging" from Properties to show or hide the applet's output
                try {
                    if (AccessTracking.props.get("Acesstracking1: Show Applet for Debugging").equals("true")) {
                        jsCode += "width=\"560\" height=\"100\" ";
                    } else {
                        jsCode += "width=\"2\" height=\"1\" ";
                    }
                } catch (Exception exc) {
                    ErrorLog.log(this, "AccessTrackingMeg: Error while reading Scone properties", "", exc);
                }
                jsCode += "mayscript>');\n";
                // Get destination url from properties for the applet to connect to
                jsCode += "document.writeln('<param name=\"Scone IP\" value=\""
                          + ((AccessTracking)plugin).getSconeIP().trim() + "\">');\n"; // Acesstracking1: Scone port for AppletConnection number
                // Get the port number for the Applet's socket connection
                jsCode += "document.writeln('<param name=\"Acesstracking1: Scone port for AppletConnection\" value=\"";
                jsCode += ((AccessTracking)plugin).getConnectionPort() + "\">');\n"; // Acesstracking1: Scone port for AppletConnection number
                // send userId, nodeId, startTime and frameName(js) to applet.
                jsCode += "document.writeln('<param name=\"UserId\" value=\"" + userId + "\">');\n"; // User Id
                jsCode += "document.writeln('<param name=\"NodeId\" value=\"" + nodeId + "\">');\n"; // Node Id
                jsCode += "document.writeln('<param name=\"StartTime\" value=\"'+_scone_start_time+'\">');\n"; // Start time
                jsCode += "document.writeln('<param name=\"Frame\" value=\"'+window.name+'\">');\n"; // Frame name
                jsCode += "document.writeln('<param name=\"Fragment\" value=\"'+window.location.hash+'\">');\n"; // fragment
                jsCode += "document.writeln('<param name=\"Query\" value=\"'+escape(window.location.search)+'\">');\n"; // query
                jsCode += "document.writeln('<param name=\"Post\" value=\"'+escape(postData)+'\">');\n"; // Data of posted forms
                jsCode += "document.writeln('<param name=\"ParentNodeName\" value=\"'+Scone_Parent_Location+'\">');\n"; // parent node name
                jsCode += "document.writeln('<param name=\"ParentFrame\" value=\"'+Scone_Parent_Name+'\">');\n"; // parent frame name
                jsCode += "document.writeln('<param name=\"Referrer\" value=\"'+document.referrer+'\">');\n"; // Referrer
                // Next line dropped because IE bug gives wrong values. Work aroud with BrowserHistory
                // jsCode += "document.writeln('<param name=\"History\" value=\"'+_scone_history_length+'\">');\n"; // length of history
                jsCode += "document.writeln('<param name=\"isIE\" value=\"'+document.all+'\">');\n"; // Is the browser the InternetExplorer 4, 5 or 6?
                jsCode += "document.writeln('<param name=\"isNS\" value=\"'+document.layers+'\">');\n"; // Is it Netscape 4.x?
                jsCode += "document.writeln('<param name=\"ATMegTime\" value=\""
                        + startTime + "\">');\n"; // Node Id
                jsCode += "document.writeln('<param name=\"FrameCount\" value=\"'+parent.frames.length+'\">');\n"; // length of history
                jsCode += "document.writeln('<param name=\"AppletControl\" value=\""
                        + appletControl + "\">');\n";
                jsCode += "document.writeln('</applet></div>');\n";


                // JavaScript functions ---------------------------------------
                jsCode += "function _scone_loaded() {\n";
                if ((appletControl & AC_IESPYACCESSTRACKING) > 0) {
                    jsCode += "  window.status=window.name+\":SconeLoadTime=\"+(new Date).getTime();\n";
                    jsCode += "  window.status=\"\";\n";
                }
                if ((appletControl & AC_DISABLEACCESSTRACKING) == 0) {
                    jsCode += "  document.LogApplet.logMsg(\"loaded\");\n";
                }
                jsCode += "}\n";

                jsCode += "function _scone_stop() {\n";
                jsCode += "if (_scone_stop_event_sent=='0') {\n";  // Send event only once, even in IE!
                
                if ((appletControl & AC_IESPYACCESSTRACKING) > 0) {
                    jsCode += "  window.status=window.name+\":SconeStopTime=\"+(new Date).getTime();\n";
                    jsCode += "  window.status=\"\";\n";
                }
                jsCode += "  _scone_stop_event_sent='1';\n";
                jsCode += "  document.LogApplet.logMsg(\"stopped\");\n }\n }\n";
                
                jsCode += "function _scone_link(linkId) {\n";
                if ((appletControl & AC_IESPYACCESSTRACKING) > 0) {
                    jsCode += "  window.status=window.name+\":SconeLinkId=\"+linkId;\n";
                    jsCode += "  window.status=\"\";\n";
                }
                if ((appletControl & AC_DISABLEACCESSTRACKING) == 0) {
                    jsCode += "  document.LogApplet.logMsg(\"link\"+linkId);\n";
                }
                jsCode += "}\n";
                
                jsCode += "function _scone_form(action) {\n";
                if ((appletControl & AC_IESPYACCESSTRACKING) > 0) {
                    jsCode += "  window.status=window.name+\":SconeFormAction=\"+action;\n";
                    jsCode += "  window.status=\"\";\n";
                }
                if ((appletControl & AC_DISABLEACCESSTRACKING) == 0) {
                    jsCode += "  document.LogApplet.logMsg(\"form\"+action)\n";
                }
                jsCode += "}\n";

                // Add functions to control browser!
                jsCode += "function _scone_bringWindowToFront()\n";
                jsCode += "{ //self.focus();\n";
                jsCode += "  setTimeout(\"self.focus()\",400)}\n";
                jsCode += "function _scone_openURL(url)\n";
                jsCode += "{ setTimeout(\"window.location.href = '\"+url+\"'\",700)}\n";
                jsCode += "function _scone_blurBrowser()\n";
                jsCode += "{ self.blur(); }\n";
                jsCode += "function _scone_closeBrowser()\n";
                jsCode += "{ self.close(); }\n";
                jsCode += "function _scone_anyFunction(func)\n";  // Eval any String with function and parameters...
                jsCode += "{ eval(func); }\n";
            
                // Output JavaScript Code for debugging...
                // System.out.println(jsCode);

                sg.setStaticHtml(jsCode);                           // Set dummy text
                sg.setContentType("application/x-javascript");      // Set type to javascript
                g = sg;                                               // Return Generator
            }

        } else if (file.startsWith("/_start")) // User entered page
        {
            // System.out.println("\n*start " + id);

            getAccessKey("/_start");
            getBrowserData();

            // Create new access object
            /* Access a = AccessCache.get(id, nodeId, start, frame);
             a.setReferrerNodeId(referrerNodeId);
             a.setParentFrameName(parentFrame);
             a.setParentFrameNodeId(parentNodeId);
             a.store();
             */
            // return dummy line
            FileGenerator rg = new FileGenerator("resources/scone/proxy/pixel.gif", "image/gif");

            g = rg;
        } else {
            // Create response from file
            // System.out.println("Returning: "+file);
            String ext = file.substring(file.lastIndexOf(".") + 1);
            FileGenerator rg = new FileGenerator("resources/scone/proxy/" + file, getContentType(ext));

            g = rg;
        }

        response = g.getHttpResponse();                   // Get http response header
        // Besser eine Fehlermeldung zurückgeben?
        response.add("Pragma", "no-cache");
        response.add("Cache-control", "no-cache, must-revalidate");
        response.add("Expires", "0");
        // response.setCache(false);

        // Send Response to Browser
        try {
            forwardRequest(g, e);
        } catch (java.io.IOException ex) {
            System.out.println("Error sending response in AccessTrackingMeg!");
        }

    }

    /*
     * Create most important mime types from file extensions
     */
    private String getContentType(String ext) {
        ext = ext.toLowerCase();
        if (ext.equals("txt")) {
            return "text/txt";
        }
        if (ext.equals("css")) {
            return "text/css";
        }
        if (ext.equals("js")) {
            return "application/x-javascript";
        }
        if (ext.equals("gif")) {
            return "image/gif";
        }
        if (ext.equals("jpg")) {
            return "image/jpeg";
        }
        return "text/html";
    }

    /*
     * Create nodeId, start and frame from query string
     */
    private void getAccessKey(String filenameStart) {
        // get nodeId from filename. This is not a query parameter, as there are otherwise problems
        // with frames.
        try {
            nodeId = file.substring(filenameStart.length());
            if (nodeId == null || nodeId.length() == 0) {
                nodeId = "0";
            }								// was it available?

            // get start time
            String startString = formParameter.getProperty("start");

            // System.out.println("AccessTrackingMeg: start = " + startString);
            // startString = file.substring(file.indexOf("?start=")+7, file.indexOf("&frame="));
            // System.out.println("AccessTrackingMeg: start = " + startString);
            if (startString == null) {
                startString = "0";
            }
            start = Long.parseLong(startString);			// Convert to long
            // Date startDate = new Date(start); System.out.println(startDate);

            // Name of this frame/window
            frame = formParameter.getProperty("frame");
            if (frame == null) {
                frame = "";
            }
            // System.out.println("frame = " + frame);
        } catch (NumberFormatException ex) {
            System.err.println("AccessTrackingMeg: Error creating numbers: " + file);
            System.err.println("nodeId = " + nodeId);
            System.err.println("start = " + start);
            System.err.println("frame = " + frame);
            nodeId = "0";
            start = 0;
            frame = "";
        }
    }

    /*
     * What is the parent, the frame name, the parent frame name, the referrer etc.
     * This method is called from _start and from _loaded to miminize problems
     * caussed by "§%§%" browser JS-bugs...
     */
    private void getBrowserData() {
        // Parent frame name
        parentFrame = formParameter.getProperty("parent");
        if (parentFrame == null) {
            parentFrame = "";
        }
        // System.out.println("parent = " + parentFrame);

        // int referrer (NodeId: where did the user come from...)
        String referrerUri = formParameter.getProperty("referrer");

        if (referrerUri == null) {
            referrerUri = "";
        } else {
            try {
                NetNode referrerNetNode = NetNodeCache.get(referrerUri);

                referrerNodeId = referrerNetNode.getNodeId();
            } catch (Exception uriException) {
                System.out.println("Exception: referrerUri = " + referrerUri);
                if (referrerUri.equals("[Unknown Origin]")) {
                    referrerNodeId = "Bookmark";
                }  // Bookmark for Netscape ???
                if (referrerUri.equals("[New Window]")) {
                    referrerNodeId = "New Window";
                }      // New window was opened and name was assigned
            }
        }
        // System.out.println("referrerUri = " + referrerUri);
        // System.out.println("referrer Id = " + referrerNodeId);

        // Parent Frame Uri, if available: int parentNodeId
        String parentUri = formParameter.getProperty("parentUri");

        if (parentUri == null) {
            parentUri = "";
        } else {
            NetNode parentNetNode = NetNodeCache.get(parentUri);

            parentNodeId = parentNetNode.getNodeId();
        }
        // System.out.println("parentUri = " + parentUri);
        // System.out.println("parent Id = " + parentNodeId);
    }

}
