package com.symcor.od.server.soap;

import java.util.*;
import javax.activation.*;
import java.io.*;
import com.symcor.od.common.soap.message.*;
import com.symcor.od.common.config.ServerConfig;

//import com.symcor.od.server.odwek.*;
import com.ibm.edms.od.*;

/** This is the facdade class for soap service. Each of its method defines a service.
 *  This class provides service to SOAP client by call ODWEK Java API to retrieve information
 *  and data from ondemand server. The protocol objects (input parameters and results) it uses to communicate with
 *  SOAP client is defined in the com.symcor.od.common.soap.message package.
 *  The pipeline is like this:
 *                                                        common.soap.message.*
 *  on-demaind server <---> ODWEK API <---> SoapFacade <------------------------> SoapClient
 * <p>Title: </p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2002</p>
 * <p>Company: </p>
 * @author unascribed
 * @version 1.0
 */

public class SoapFacade implements ServerConfig {

    private static String LOCAL_FILE_PATH = "C:\\Projects\\ondemand\\odserver\\data\\";
    private static String SERVER_FILE_PATH = "/home/jzhang/data/";

    public SoapFacade() {
        od = new ODServer();
    }


    /**
     * This method logs on to the ondemand server and returns list of folders that this user can access.
     * @param configDir - configuration directory.
     * @param appName - application name.
     * @param serverName - server name.
     * @param loginName - user login name.
     * @param passwd - user password.
     * @return list of Folder objects.
     */
    public Vector logon (String configDir, String appName, String serverName, String loginName, String passwd) {
        boolean success = false;

        //logging
        System.out.println ("Login information: ");
        System.out.println ("configDir: " + configDir);
        System.out.println ("applicationName: " + appName);

        try {
            od.initialize(configDir, appName);
        } catch (ODException e) {
            System.out.println("initialize(), exception: " + e.getMessage());
            e.printStackTrace();
        }

        System.out.println ("serverName: " + serverName);
        System.out.println ("loginName: " + loginName);
        System.out.println ("password: " + passwd);

        Vector folders = new Vector();
        try {
            od.setServer(serverName);
            od.setUserId(loginName);
            od.setPassword(passwd);
            od.logon();
            success = true;
            System.out.println("ODWEK User login in!!!");

            for (Enumeration enum = od.getFolderNames(); enum.hasMoreElements(); ) {
                String folderName = (String) enum.nextElement();
                Folder folder = new Folder();
                folder.setName(folderName);
                folders.add(folder);
            }
        } catch (Exception e) {
            System.out.println("Logon failed, exception: " + e.getMessage());
            e.printStackTrace();
        }

        System.out.println("the logon is " + ((success)?"successful":"failed"));

        return folders;
    }


    /**
     * This methods opens a folder and returns list of criteria for this folder defined by ondemand server administrator.
     * @param folderName
     * @return
     */
    public Vector openFolder (String folderName) {
        System.out.println ("folderName: " + folderName);
        Vector criteria = new Vector();

        try {
            folder = od.openFolder(folderName);
            System.out.println("we have opened the folder: " + folderName);

            for (Enumeration enum = folder.getCriteria(); enum.hasMoreElements(); ) {
                ODCriteria odCriterion = (ODCriteria)enum.nextElement();
                String criterionName = odCriterion.getName();
                Criterion criterion = new Criterion(criterionName);

                //currently we only support between and equal operations.
                int operand = Criterion.OPEQUAL;
                int odOperand = odCriterion.getOperand();

                if (odOperand == ODConstant.OPBetween) {
                    operand = Criterion.OPBETWEEN;
                }

                criterion.setOperand(operand);
                //criterion.setValidOperands(odCriterion.getValidOperands());

                //currently we only support list and non-list types.
                int odType = odCriterion.getType();
                int type = Criterion.TYPE_NON_LIST;

                if ((odType == ODConstant.InputTypeChoice) || (odType == ODConstant.InputTypeSegment)) {
                    type = Criterion.TYPE_LIST;
                }

                criterion.setType(type);
                criterion.setSearchValues(odCriterion.getSearchValues());
                criterion.setFixedValues(odCriterion.getFixedValues());
                //convert getValues vector to defaultValues String array
                Vector v = odCriterion.getValues();
                Object[] oo = v.toArray();
                String[] defaultValues = new String[oo.length];
                for (int i=0; i<oo.length; i++) {
                    defaultValues[i] = (String)oo[i];
                }
                criterion.setDefaultValues(defaultValues);

                System.out.println("got the search criterion: " + criterion.getName());
                criteria.add(criterion);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("return criteria, number of: " + criteria.size());

        return criteria;
    }


    private void resetSearchValueToFolder (ODFolder folder) throws ODException {
        for (Enumeration enum = folder.getCriteria(); enum.hasMoreElements(); ) {
            ODCriteria odCriterion = (ODCriteria) enum.nextElement();

            String[] searchValues = odCriterion.getSearchValues();

            int operand = odCriterion.getOperand();

            if (searchValues != null) {
                if (searchValues.length >0) {
                    if (operand == Criterion.OPEQUAL) { //TYPE_LIST must have a equal operand.
                        odCriterion.setSearchValue(""); //can not use null, but "". Otherwise, ODException.
                    } else {
                        odCriterion.setSearchValues("", "");
                    }
                }
            }

        }

    };

    /**
     * find out what criteria should be assigned values and assigned them.
     * @param criteria
     * @param folder
     * @throws ODException
     */
    private void setSearchValuesToFolderCriteria (Vector criteria, ODFolder folder) throws ODException {

        for (Iterator iter = criteria.iterator(); iter.hasNext(); ) {

            Criterion criterion = (Criterion) iter.next();
            System.out.println("setSearchValuesToFolderCriteria: \n");
            System.out.println(criterion);
            String criterionName = criterion.getName();

            ODCriteria odCriterion = folder.getCriteria(criterionName);

            int operand = criterion.getOperand();
            int type = criterion.getType();

            String[] searchValues = criterion.getSearchValues();

            if (searchValues == null) {
                throw new java.lang.IllegalArgumentException("Criterion value can not be null. Incoming request is invalid. \n");
            } else {
                if (searchValues.length >0) {
                    if (operand == Criterion.OPEQUAL) { //TYPE_LIST must have a equal operand.
                        odCriterion.setSearchValue(searchValues[0]);
                    } else {
                        odCriterion.setSearchValues(searchValues[0], searchValues[1]);
                    }
                }
            }
        }
    }


    /**
     * This methods searches the folder with the specified criteria. A list of hits is returned.
     * @param criteria - criteria with user specified search values.
     * @return list of hits that qualify the search criteria.
     */
    public HitList search (Vector criteria) {

        System.out.println("The criteria that the server received: \n" + criteria);

        String []displayOrder = folder.getDisplayOrder();
        String[] headers = new String[displayOrder.length];

        System.arraycopy(displayOrder, 0, headers, 0, displayOrder.length);

        for (int i = 0; i < headers.length; i++)
        {
            System.out.print(headers[i] + "\t\t\n");

            //this needs to be removed if the odFolder.getDisplayOrder() problem is resolved.
            if (headers[i] == null) {
                headers[i] = "Unavailable";
            }
        }

        Vector hits = new Vector();

        // criterioa can be an empty list, but not a null.
        if (criteria != null) {

            try {
                //reset the search values set by last request
                resetSearchValueToFolder (folder);

                //set search values.
                setSearchValuesToFolderCriteria (criteria, folder);

                Vector odhits = folder.search();

                System.out.println("hit list: " + odhits);

                for (Iterator iter = odhits.iterator(); iter.hasNext(); ) {
                    ODHit odHit = (ODHit) iter.next();
                    Hit hit = new Hit();
                    hit.setDocId(odHit.getDocId());
                    hit.setMimeType(odHit.getMimeType());

                    //by default, it is "image/jpeg";
                    if (hit.getMimeType()==null) {
                        hit.setMimeType("image/jpeg");
                    }

                    //constructing display value array by iterating each criterion.
                    //getDisplayNames seems to return more than we expect (16 values, numOfCriteria: 12).
                    String[] displayValues = new String[headers.length];
                    for (int i=0; i<headers.length; i++) {
                        displayValues[i] = odHit.getDisplayValue(headers[i]);
                        if (displayValues[i]==null) {
                            displayValues[i] = "Unavailable";
                        }
                    }

                    hit.setDisplayValues(displayValues);
                    hits.add(hit);
                    System.out.println("hit list: " + hit.toString());
                }
            } catch (Exception e) {
                //logging
                e.printStackTrace();
                System.out.println("Got Exceptino in search()!, might with folder.search()");
            }
        } else {
             System.out.println("in search(), criteria is null, internal error. ");
            //default criteria values will be used then.
            //logging here
        }

        return new HitList(headers, hits);
    }

    /**
     * hard coded search() that returns list of files existing in the data directory.
     * @param criteria
     * @return
     */
    public Vector getFiles (String dataDir) {

        System.out.println("search(void) method is called");
        Vector hits = new Vector();

        File f = new File(dataDir);
        String []files = f.list();
        for (int i=0; i<files.length; i++) {
            Hit hit = new Hit();
            hit.setDocId(dataDir + files[i]);
            int suffixPos = files[i].indexOf(".");
            hit.setMimeType("images/" + files[i].substring(suffixPos+1));
            hits.add(hit);
            System.out.println("document id: " + hit.getDocId() + ", mime type: " + hit.getMimeType());
        }
        return hits;
    }

    /**
     * This methods return the document that contains the image data.
     * @param documentID - document ID.
     * @return
     */
    public ImageDocument getDocAsAtm (String documentID /*, String format */) {

        //retrieve back the binary data in AFP format.
        //create converter instance
        //does the conversion.
        //return the data handler for the format.
        ImageDocument doc = new ImageDocument(documentID);

        try {
            String viewer = ODConstant.NATIVE;
            String folderName = folder.getName();
            byte[] data= od.retrieve(documentID, folderName, viewer);

            javax.activation.DataSource ds = new FileDataSource(documentID);

            // in production code, need to redefine a buffer to prevent a too long image
            // generate a run time no memory available exception.
            OutputStream os = ds.getOutputStream();
            os.write(data);
            os.flush();

            //ok to close?
            os.close();

            DataHandler dh = new DataHandler(ds);
            doc.setDataHandler(dh);

            doc.setMimeType("image/jpeg");  //should be set through the parameter.
            System.out.println("return document with data, document ID: " + documentID + " mime type: " + doc.getMimeType());
        } catch (Exception e) {
            System.out.println("exception occured. document ID: " + documentID);
            e.printStackTrace();
        }

        return doc;
    }

    /**
     * This methods return the document that contains the image data.
     * @param documentID - document ID.
     * @return
     */
    public ImageDocument getDocument (String documentID /*, String format */) {

        //retrieve back the binary data in AFP format.
        //create converter instance
        //does the conversion.
        //return the data handler for the format.
        ImageDocument doc = new ImageDocument(documentID);

        try {
            String viewer = ODConstant.NATIVE;
            String folderName = folder.getName();
            byte[] data= od.retrieve(documentID, folderName, viewer);

            //I do not think this is necessary, anyway, a test.
            byte[] buffer = new byte[data.length];
            System.arraycopy(data, 0, buffer, 0, data.length);
            doc.setData(buffer);
            doc.setMimeType("image/jpeg");  //should be set through the parameter.
            System.out.println("return document with data, document ID: " + documentID + " mime type: " + doc.getMimeType());
        } catch (Exception e) {
            System.out.println("exception occured. id: " + documentID);
            e.printStackTrace();
        }

        return doc;
    }

    /**
     * This methods return the document that contains the image data.
     * @param documentID - document ID.
     * @return
     */
    public ImageDocument getFile (String filePath /*, String format */) {

        //retrieve back the binary data in AFP format.
        //create converter instance
        //does the conversion.
        //return the data handler for the format.

        javax.activation.FileDataSource fds = new FileDataSource(filePath);
        DataHandler dh = new DataHandler(fds);

        ImageDocument doc = new ImageDocument(filePath);
        doc.setDataHandler(dh);
        System.out.println("return document with data: " + filePath);
        return doc;
    }

    /**
     * This mothod logs user off.
     * @return
     */
    public boolean logOff () {
        boolean success = false;
        try {
            od.logoff();
            success = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return success;
    }

    public static void main(String[] args) {

        SoapFacade soapFacade = new SoapFacade();

        soapFacade.getFiles(LOCAL_FILE_PATH);


        Vector folders = soapFacade.logon(ARCHIVE_CONFIG_DIR, ARCHIVE_APPLICATION_NAME,
            ARCHIVE_SERVER_NAME, ARCHIVE_LOGIN_NAME, "odtest12");
        System.out.println("folders: " +  folders);

        for (Iterator iter=folders.iterator(); iter.hasNext(); ) {
            Folder folder = (Folder)iter.next();
            System.out.println("folder name: " + folder.getName());
            Vector criteria = soapFacade.openFolder(folder.getName());
            for (Iterator iter2=criteria.iterator(); iter2.hasNext(); ) {
                Criterion criterion = (Criterion)iter2.next();
                System.out.println("\nCriterion: " + criterion.getName());
            }
            HitList hitList = soapFacade.search(criteria);
            String[] headers = hitList.getHeaders();
            for (int i=0; i<headers.length; i++) {
                System.out.println(headers[i]);
            }

            Vector hits = hitList.getHits();
            for (Iterator iter2=hits.iterator(); iter2.hasNext(); ) {
                Hit hit = (Hit)iter2.next();
                System.out.println("\nHit: " + hit.getDocId());
            }
        }
    }

    private ODServer od;
    private ODFolder folder;
}