package com.pdflib.cookbook.pcos.interactive;

import com.pdflib.pCOS;
import com.pdflib.pCOSException;
import com.pdflib.cookbook.pcos.pcos_cookbook_example;

/** 
 * Count javascript occurences in document.
 * <p>
 * Required software: pCOS interface 3 (pCOS 2.x, PDFlib 7.x, TET 2.2,
 * PLOP 3.x)<br>
 * Required data: PDF document
 * 
 * @version $Id: javascript.java,v 1.5 2007/10/23 06:15:38 stm Exp $
 */
public class javascript extends pcos_cookbook_example {
    
    /* This is where the data files are. Adjust as necessary. */
    private final static String SEARCH_PATH = "../input";
   
    private interface script_info_printer {
        /**
         * Prints information about a JavaScript found in an action dictionary.
         * 
         * @param scriptnr
         *            number of the script
         * @param length
         *            length of script in bytes
         */
        void print_info(int scriptnr, int length);
    }
    
    public void example_code(pCOS p, String filename) throws pCOSException, Exception {
        /* Open the PDF document */
        int doc = p.open_document(filename, "");
        if (doc == -1)
            throw new Exception("Error: " + p.get_errmsg());
        
        System.out.println("File name: " + p.get_string(doc, "filename"));
        
        /* Document-level JavaScript */
        int docscripts = get_documentscripts(p, doc);

        /* Open action JavaScript */
        int openscripts = get_openscripts(p, doc);

        /* Page-level JavaScript */
        int pagescripts = get_pagescripts(p, doc);

        /* Annotation-level JavaScript */
        int annotscripts = get_annotscripts(p, doc);

        /* Field-level JavaScript */
        int fieldscripts = get_fieldscripts(p, doc);

        /* Bookmark-level JavaScript */
        int bookmarkscripts = get_bookmarkscripts(p, doc);

        System.out.println();
        System.out.println(openscripts + " JavaScript(s) for open action");
        System.out.println(docscripts + " JavaScript(s) on document level");
        System.out.println(pagescripts + " JavaScript(s) on page level");
        System.out.println(annotscripts + " JavaScript(s) on annotation level");
        System.out.println(fieldscripts + " JavaScript(s) on field level");
        System.out.println(bookmarkscripts + " JavaScript(s) on bookmark level");
        
        p.close_document(doc);
    }

    private class bookmark_info_printer implements script_info_printer {  
        private String title;
        
        bookmark_info_printer(String title) {
            this.title = title;
        }

        public void print_info(int scriptnr, int length) {
            System.out.println("Bookmark '" + title + "', script number "
                    + (scriptnr + 1) + ", length=" + length);
        }
    }
    
    private int get_bookmarkscripts(pCOS p, int doc)
        throws pCOSException {
        int bookmarkscripts = 0;
        int bookmarkcount = (int) p.get_number(doc, "length:bookmarks");
        
        for (int bookmark = 0; bookmark < bookmarkcount; bookmark++) {
            int objid;

            String base = "bookmarks[" + bookmark + "]/A";
            objid = (int) p.get_number(doc, "pcosid:" + base);
            
            String bookmark_name = p.get_string(doc,
                    "bookmarks[" + bookmark + "]/Title");
            
            script_info_printer info_printer =
                new bookmark_info_printer(bookmark_name);
            
            bookmarkscripts +=
                walk_action_dictionary(p, doc, objid, 0, info_printer);
        }
        
        return bookmarkscripts;
    }

    private class field_trigger_info_printer implements script_info_printer {
        private String trigger;
        private String fullname;

        field_trigger_info_printer(String trigger, String fullname) {
            this.trigger = trigger;
            this.fullname = fullname;
        }

        public void print_info(int scriptnr, int length) {
            System.out.println("Form field '" + fullname + "', "
                    + "trigger '" + trigger + "', script number "
                    + (scriptnr + 1) + ", length=" + length);
        }
    }
    
    private int get_fieldscripts(pCOS p, int doc) throws pCOSException {
        int fieldcount = (int) p.get_number(doc, "length:fields");
        int fieldscripts = 0;

        for (int field = 0; field < fieldcount; field++) {
            int actioncount = (int) p.get_number(doc,
                    "length:fields[" + field + "]/AA");

            for (int action = 0; action < actioncount; action++) {
                String base = "fields[" + field + "]/AA[" + action + "]";
                int objid = (int) p.get_number(doc, "pcosid:" + base);

                String trigger = p.get_string(doc, base + ".key");
                String form_field = p.get_string(doc, "fields[" + field
                        + "]/fullname");

                script_info_printer info_printer = new field_trigger_info_printer(
                        trigger, form_field);

                fieldscripts += walk_action_dictionary(p, doc, objid, 0,
                        info_printer);
            }
        }

        return fieldscripts;
    }

    private class annot_activation_info_printer implements script_info_printer {
        private String subtype;
        private int pagenr;
        
        annot_activation_info_printer(String subtype, int pagenr) {
            this.subtype = subtype;
            this.pagenr = pagenr;
        }
        
        public void print_info(int scriptnr, int length) {
            System.out.print("Page " + (pagenr + 1) + ", " + subtype
                    + " annotation, script number " + (scriptnr + 1) + ", ");
            System.out.println("activation script, length=" + length);
        }
    }

    private class annot_trigger_info_printer implements script_info_printer {
        private String subtype;
        private String trigger;
        private int pagenr;

        annot_trigger_info_printer(String subtype, String trigger, int pagenr) {
            this.subtype = subtype;
            this.trigger = trigger;
            this.pagenr = pagenr;
        }

        public void print_info(int scriptnr, int length) {
            System.out.print("Page " + (pagenr + 1) + ", " + subtype
                    + " annotation, script number " + scriptnr + ", ");

            System.out.println("trigger '" + trigger + "', length=" + length);
        }
    }
    
    private int get_annotscripts(pCOS p, int doc) throws pCOSException {
        int annotscripts = 0;
        int pagecount = (int) p.get_number(doc, "length:pages");
        
        for (int page = 0; page < pagecount; page++) {
            int annotcount = (int) p.get_number(doc,
                "length:pages[" + page + "]/annots");

            for (int ann = 0; ann < annotcount; ann++) {
                /* Avoid double-counting of form field Widgets */
                 String subtype = p.get_string(doc,
                    "pages[" + page + "]/annots[" + ann + "]/Subtype");

                 if ("Widget".equals(subtype))
                    continue;

                /* old-style A entry with a single action */
                String base = "pages[" + page + "]/annots[" + ann + "]/A";
                int objid = (int) p.get_number(doc, "pcosid:" + base);

                script_info_printer info_printer =
                    new annot_activation_info_printer(subtype, page);
                annotscripts += walk_action_dictionary(p, doc, objid, 0, info_printer);

                /* newer AA entry with multiple actions */
                int actioncount = (int) p.get_number(doc,
                    "length:pages[" + page + "]/annots[" + ann + "]/AA");
                for (int action = 0; action < actioncount; action++) {
                    base = "pages[" + page + "]/annots[" + ann + "]/AA[" + action + "]";
                    objid = (int) p.get_number(doc, "pcosid:" + base);

                    String trigger = p.get_string(doc, base + ".key");
                    
                    info_printer = new annot_trigger_info_printer(subtype,
                            trigger, page);    
                    annotscripts += walk_action_dictionary(p, doc, objid, 0, info_printer);
                }
            }
        }
        
        return annotscripts;
    }

    private class open_action_info_printer implements script_info_printer {
        public void print_info(int scriptnr, int length) {
            System.out.println("Open action script number " + (scriptnr + 1)
                    + ", length=" + length);
        }
    }
    
    private int get_openscripts(pCOS p, int doc) throws pCOSException {
        int openscripts = 0;
        String base = "/Root/OpenAction";
        
        /*
         * The "OpenAction" entry can be a "destination" or an "action
         * dictionary". Only examine the latter case for JavaScript occurences.
         */
        String type = p.get_string(doc, "type:" + base);
        
        if (type.equals("dict")) {
            int objid = (int) p.get_number(doc, "pcosid:" + base);
            script_info_printer info_printer = new open_action_info_printer();
            openscripts = walk_action_dictionary(p, doc, objid, 0, info_printer);
        }
        
        return openscripts;
    }


    private class document_info_printer implements script_info_printer {
        private String scriptname;
        
        document_info_printer(String scriptname) {
            this.scriptname = scriptname;
        }

        public void print_info(int scriptnr, int length) {
            System.out.println("Document-level script '" + scriptname
                    + "', length=" + length);
        }
    }

    private class document_action_info_printer implements script_info_printer {
        private String trigger;
        
        document_action_info_printer(String trigger) {
            this.trigger = trigger;
        }

        public void print_info(int scriptnr, int length) {
            System.out.println("Document-level script, trigger '" + trigger
                    + "', number " + (scriptnr + 1) + ", length=" + length);
        }
    }
    
    private int get_documentscripts(pCOS p, int doc)
            throws pCOSException {
        int docscripts = 0;

        int scriptcount = (int) p.get_number(doc, "length:names/JavaScript");

        for (int script = 0; script < scriptcount; script++) {
            String base = "names/JavaScript[" + script + "]";
            int objid = (int) p.get_number(doc, "pcosid:" + base);
            
            String script_name = p.get_string(doc, base + ".key");
            script_info_printer info_printer =
                new document_info_printer(script_name);
            
            if (check_for_script(p, doc, objid, docscripts, info_printer)) {
                docscripts++;
            }
        }

        /* Additional document-level JavaScript */
        int actioncount = (int) p.get_number(doc, "length:/Root/AA");

        for (int action = 0; action < actioncount; action++) {
            String base = "/Root/AA[" + action + "]";
            int objid = (int) p.get_number(doc, "pcosid:" + base);
            String trigger = p.get_string(doc, base + ".key");

            script_info_printer info_printer =
                new document_action_info_printer(trigger);

            docscripts += walk_action_dictionary(p, doc, objid, 0, info_printer);
        }
        return docscripts;
    }

    private class page_info_printer implements script_info_printer {
        private int pagenr;
        private String trigger;
        
        page_info_printer(int pagenr, String trigger) {
            this.trigger = trigger;
            this.pagenr = pagenr;
        }

        public void print_info(int scriptnr, int length) {
            System.out.println("Page " + (pagenr + 1) + ", trigger '"
                    + trigger + "', script number " + (scriptnr + 1)
                    + ", length=" + length);
        }
    }

    private int get_pagescripts(pCOS p, int doc) throws pCOSException {
        int pagecount = (int) p.get_number(doc, "length:pages");
        int pagescripts = 0;

        for (int page = 0; page < pagecount; page++) {
            int actioncount = (int) p.get_number(doc,
                    "length:pages[" + page + "]/AA");
            
            for (int action = 0; action < actioncount; action++) {
                String base = "pages[" + page + "]/AA[" + action + "]";
                int objid = (int) p.get_number(doc, "pcosid:" + base);

                String trigger = p.get_string(doc, base + ".key");

                script_info_printer info_printer =
                    new page_info_printer(page, trigger);

                pagescripts += walk_action_dictionary(p, doc, objid, 0,
                        info_printer);
            }
        }

        return pagescripts;
    }

    /**
     * Traverses an action dictionary.
     * <p>
     * An action dictionary can contain a tree of action definitions, and a
     * subset of them can be JavaScript actions. This routine recursively
     * traverses the action tree and counts the JavaScript occurrences.
     * <p>
     * For the definition of an action dictionary, see for example "8.5.1 Action
     * Dictionaries" in the PDF Reference version 1.7. The
     * 
     * @param p
     *            pCOS object
     * @param doc
     *            pCOS document handle
     * @param objid
     *            pCOS id of the action dictionary to traverse
     * @param scriptnr
     *            counter for the JavaScript occurrences in the action
     *            dictionary tree
     * @param info_printer
     *            used to print out the information about the action
     * 
     * @return number of scripts found in the action dictionary tree
     * 
     * @throws pCOSException
     *             an error was detected in the PDF structure
     */
    private int walk_action_dictionary(pCOS p, int doc, int objid,
            int scriptnr, script_info_printer info_printer)
            throws pCOSException {
        int scripts = 0;

        while (objid != -1) {
            if (check_for_script(p, doc, objid, scriptnr + scripts,
                    info_printer)) {
                scripts += 1;
            }
            
            String type = p.get_string(doc, "type:objects[" + objid + "]/Next");
            if (type.equals("dict")) {
                objid = (int) p.get_number(doc, "pcosid:objects[" + objid + "]/Next");
            }
            else if (type.equals("array")) {
                int length = (int) p.get_number(doc, "length:objects[" + objid + "]/Next");
                for (int child = 0; child < length; child += 1) {
                    objid = (int) p.get_number(doc,
                        "pcosid:objects[" + objid + "]/Next[" + child + "]");
                    scripts += walk_action_dictionary(p, doc, objid,
                                    scriptnr + scripts, info_printer);
                }
                objid = -1;
            }
            else {
                objid = -1;
            }
        }
        
        return scripts;
    }

    /**
     * Checks whether the given action dictionary entry is a JavaScript action.
     * 
     * @param p
     *            pCOS object
     * @param doc
     *            pCOS document handle
     * @param objid
     *            pCOS id of the action dictionary entry
     * @param scriptnr
     *            counter for the JavaScript occurrences in the action
     *            dictionary tree
     * @param info_printer
     *            for printing the information about the action
     * 
     * @return true if a script was found, false otherwise
     * @throws pCOSException
     */
    private boolean check_for_script(pCOS p, int doc, int objid,
            int scriptnr, script_info_printer info_printer)
            throws pCOSException
    {
        boolean retval = false;
        String action_dict_obj = "objects[" + objid +"]";
        String objtype = p.get_string(doc, "type:" + action_dict_obj);
        
        if (objtype.equals("dict"))
        {
            /* The action type, not the type of PDF object */
            String actiontype = p.get_string(doc, action_dict_obj + "/S");
            
            if (actiontype.equals("JavaScript"))
            {
                int len;
                
                String js_path = action_dict_obj + "/JS";
                
                objtype = p.get_string(doc, "type:" + js_path);
        
                if (objtype.equals("string")) {
                    /* fetch string contents to determine length */
                    String js = p.get_string(doc, js_path);
                    len = js.length();
                }
                else if (objtype.equals("stream")) {
                    /* fetch uncompressed stream data to determine length */
                    byte[] js = p.get_stream(doc, "convert=unicode", js_path);

                    len = js.length;
                    
                    /* Unicode detection: test for BOM, determine proper length */
                    if (js[0] == 0xFF && js[1] == 0xFE) {
                        len = (len - 2) / 2;
                    }
                }
                else {
                    throw new pCOSException("Could not get JavaScript contents"
                            + "for object with id " + objid);
                }
                
                /* call the method that prints action-specific information */
                info_printer.print_info(scriptnr, len);
                
                retval = true;
            }
        }
        
        return retval;
    }
    
    public javascript(String[] argv, String readable_name,
        String search_path, String full_rcs_file_name, String revision) {
        super(argv, readable_name, search_path, full_rcs_file_name, revision);
    }

    public static void main(String argv[]) {
        javascript example = new javascript(argv,
            "JavaScript", SEARCH_PATH,
            "$RCSfile: javascript.java,v $", "$Revision: 1.5 $");
        example.execute();
    }
}
