/*
 * Copyright 2006-2007 Queplix Corp.
 *
 * Licensed under the Queplix Public License, Version 1.1.1 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.queplix.com/solutions/commercial-open-source/queplix-public-license/
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 *
 */

package com.queplix.core.modules.eql.ejb;

import com.queplix.core.error.ErrorHelper;
import com.queplix.core.integrator.security.LogonSession;
import com.queplix.core.jxb.entity.Efield;
import com.queplix.core.jxb.entity.Entity;
import com.queplix.core.modules.config.utils.EntityHelper;
import com.queplix.core.modules.eql.EQLERes;
import com.queplix.core.modules.eql.EQLFactory;
import com.queplix.core.modules.eql.EQLReqField;
import com.queplix.core.modules.eql.EQLResCell;
import com.queplix.core.modules.eql.EQLResRecord;
import com.queplix.core.modules.eql.EQLSession;
import com.queplix.core.modules.eql.error.EQLException;
import com.queplix.core.modules.eql.history.HistoryConstants;
import com.queplix.core.modules.eql.jxb.history.HistoryTopic;
import com.queplix.core.modules.eql.jxb.history.HistoryUnparsedTopic;
import com.queplix.core.modules.jeo.JEObjectHandler;
import com.queplix.core.modules.jeo.ejb.JEOManagerLocal;
import com.queplix.core.modules.jeo.ejb.JEOManagerLocalHome;
import com.queplix.core.modules.jeo.gen.HistoryObject;
import com.queplix.core.modules.jeo.gen.HistoryObjectHandler;
import com.queplix.core.utils.DateHelper;
import com.queplix.core.utils.JNDINames;
import com.queplix.core.utils.StringHelper;
import com.queplix.core.utils.xml.TransletWrapper;
import com.queplix.core.utils.xml.XMLFactory;
import com.queplix.core.utils.xml.XMLHelper;

import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.util.List;

/**
 * History logger EJB.
 *
 * @author [ALB] Baranov Andrey
 * @author [ONZ] Oleg N. Zhovtanyuk
 * @version $Revision: 1.2 $ $Date: 2006/07/05 12:04:51 $
 */

public class HistoryEJB
        extends AbstractEQLSupportedEJB {

    // ================================================================== Fields

    // XSL translet implementation.
    private TransletWrapper transletWrapper = XMLFactory.getTransletWrapper();

    // ========================================================= EJB API methods

    /**
     * Initialize bean.
     */
    public void ejbCreate() {
        INFO("HistoryEJB created - " + hashCode());
    }

    // ========================================================== Public methods

    /**
     * Writes the changes history.
     *
     * @param session   EQL session
     * @param res       EQLERes response
     * @param resRecord given EQL response record
     * @return new EQLResRecord object
     * @throws EQLException
     */
    public EQLResRecord logHistory(EQLSession session, EQLERes res,
                                   EQLResRecord resRecord)
            throws EQLException {

        // Initialization.
        long time = System.currentTimeMillis();
        EQLResCell historyResCell = null;
        EQLResCell pkeyResCell = null;

        // Get the base entity.
        Entity baseEntity = res.getEntity();

        if(baseEntity == null) {
            throw new IllegalStateException("Base entity is not found.");
        }

        String dbObject = baseEntity.getDbobject();
        String entityName = baseEntity.getName();

        try {

            // Check if the record was changed.
            if(!resRecord.isChanged()) {
                DEBUG("Entity '" + entityName + "' was not changed.");
                return resRecord;
            }

            // Get the names of fields that might be logged.
            List fieldNames = getLogFieldNames(session, dbObject);


            if(fieldNames == null) {
                if(getLogger().isDebugEnabled()) {
                    DEBUG("Entity '" + entityName + "' (table '" + dbObject +
                            "') doesn't have the fields to log history for.");
                }
                return resRecord;
            }

            // Find the 'pkey' field.
            Efield pkeyField = EntityHelper.getEfield(baseEntity.getPkeyfield(),
                    baseEntity);
            if(pkeyField == null) {
                WARN("Can't find the pkey field for entity '" + entityName
                        + "'.");
                return resRecord;
            }
            pkeyResCell = resRecord.getResCell(new EQLReqField(baseEntity,
                    pkeyField));
            Long pkeyId = pkeyResCell.getLong();

            if(baseEntity.getHasHistoryFields().booleanValue()) {
                // Find the 'history' field.
                Efield historyField = EntityHelper.getEfield(
                        baseEntity.getHistoryfield(), baseEntity);
                if(historyField == null) {
                    WARN("Can't find the history field for entity '"
                            + entityName + "'.");
                    return resRecord;
                }
                historyResCell = resRecord.getResCell(new EQLReqField(
                        baseEntity, historyField));
                Long historyId = historyResCell.getLong();

                Long newHistoryId = storeHistoryById(session, resRecord,
                        historyId, fieldNames, dbObject, pkeyId);

                // Store the updated history.
                historyResCell.setNumber(newHistoryId);
            } else {
                storeHistoryByObj(session, resRecord, fieldNames, dbObject,
                        pkeyId);
            }
        } catch (Throwable tr) {
            ErrorHelper.throwSystemException(tr, this);
        }

        // Ok.
        if(getLogger().isInfoEnabled()) {
            time = System.currentTimeMillis() - time;
            INFO("History topic created for entity '" + entityName
                    + "'. Time (ms) = " + time);
        }
        return resRecord;

    }

    /**
     * Writes the changes history.
     *
     * @param session    EQL session
     * @param resRecord  given EQL response record
     * @param historyId  link to the current history
     * @param fieldNames names of fields that might be logged
     * @param dbObject   table name
     * @param pkeyId     value of the pkey field
     * @return ID of a new History object
     * @throws EQLException
     */
    public Long storeHistoryById(EQLSession session, EQLResRecord resRecord,
                                 Long historyId, List fieldNames,
                                 String dbObject, Long pkeyId)
            throws EQLException {

        try {

            // Make a new history topic.
            HistoryTopic historyTopic = createHistoryTopic(session, resRecord,
                    fieldNames);

            /* Escape history saving, if all histories are empty*/
            if((historyTopic.getHistoryData() == null ||
                    historyTopic.getHistoryData().getHistoryFieldCount() == 0)
                    &&
                    (historyTopic.getIfgData() == null
                            || historyTopic.getIfgData().getIfgCount() == 0)) {
                return null;
            }

            // History topic -> string
            String history = toString(historyTopic);

            DEBUG(history);

            // Initialization.
            LogonSession ls = session.getLogonSession();
            JEOManagerLocal jeoManager = (JEOManagerLocal) session.getCOM().
                    getLocalObject(JNDINames.JEOManager,
                            JEOManagerLocalHome.class);

            // Add the topic to an existing history.
            if(historyId != null) {
                HistoryObjectHandler hisHnd = (HistoryObjectHandler)
                        HistoryObjectHandler.selectById(jeoManager, ls,
                                historyId.longValue());
                HistoryObject hisObj = (HistoryObject) hisHnd.getJEObject();
                char[] memo = hisObj.getQx_history();

                String existingHistory = String.valueOf(memo);

                // If the existing history is invalid, leave it unparsed.
                if(!isHistoryValid(existingHistory)) {
                    HistoryUnparsedTopic unparsedTopic
                            = new HistoryUnparsedTopic();
                    unparsedTopic.setHistoryUnparsedData(StringHelper.escape(
                            existingHistory));
                    existingHistory = toString(unparsedTopic);
                }

                history += existingHistory;
                hisObj.setQx_history(history.toCharArray());
                hisObj.setQx_datemodified(DateHelper.getNowDate());
                hisHnd.commit();
            } else {
                // User handler and object 
                JEObjectHandler hnd = jeoManager.create(ls,
                        HistoryObjectHandler.class);
                HistoryObject hisObj = (HistoryObject) hnd.getJEObject();
                hisObj.setQx_history(history.toCharArray());
                hisObj.setQx_object(dbObject);
                hisObj.setQx_objectid(pkeyId);
                hisObj.setQx_datemodified(DateHelper.getNowDate());
                hnd.commit();
                historyId = hisObj.getQx_historyid();
            }

        } catch (Throwable tr) {
            ErrorHelper.throwSystemException(tr, this);
        }

        return historyId;
    }

    /**
     * Writes the changes history.
     *
     * @param session    EQL session
     * @param resRecord  given EQL response record
     * @param fieldNames names of fields that might be logged
     * @param dbObject   table name
     * @param pkeyId     value of the pkey field
     * @throws EQLException
     */
    public void storeHistoryByObj(EQLSession session, EQLResRecord resRecord,
                                  List fieldNames, String dbObject, Long pkeyId)
            throws EQLException {

        try {

            // Make a new history topic.
            HistoryTopic historyTopic = createHistoryTopic(session, resRecord,
                    fieldNames);

            /* Escape history saving, if all histories are empty*/
            if((historyTopic.getHistoryData() == null ||
                    historyTopic.getHistoryData().getHistoryFieldCount() == 0)
                    &&
                    (historyTopic.getIfgData() == null
                            || historyTopic.getIfgData().getIfgCount() == 0)) {
                return;
            }

            // History topic -> string
            String history = toString(historyTopic);

            DEBUG(history);

            // Initialization.
            LogonSession ls = session.getLogonSession();
            JEOManagerLocal jeoManager = (JEOManagerLocal) session.getCOM().
                    getLocalObject(JNDINames.JEOManager,
                            JEOManagerLocalHome.class);

            HistoryObjectHandler hisHnd = (HistoryObjectHandler)
                    HistoryObjectHandler.selectByObj(jeoManager, ls, dbObject,
                            pkeyId.longValue());

            if(hisHnd == null) {
                // Creating a new history topic.
                JEObjectHandler hnd = jeoManager.create(ls,
                        HistoryObjectHandler.class);
                HistoryObject hisObj = (HistoryObject) hnd.getJEObject();
                hisObj.setQx_history(history.toCharArray());
                hisObj.setQx_object(dbObject);
                hisObj.setQx_objectid(pkeyId);
                hisObj.setQx_datemodified(DateHelper.getNowDate());
                hnd.commit();
            } else {
                // Add the topic to an existing history.
                HistoryObject hisObj = (HistoryObject) hisHnd.getJEObject();
                char[] memo = hisObj.getQx_history();

                String existingHistory = String.valueOf(memo);

                // If the existing history is invalid, leave it unparsed.
                if(!isHistoryValid(existingHistory)) {
                    HistoryUnparsedTopic unparsedTopic
                            = new HistoryUnparsedTopic();
                    unparsedTopic.setHistoryUnparsedData(StringHelper.escape(
                            existingHistory));
                    existingHistory = toString(unparsedTopic);
                }

                history += existingHistory;
                hisObj.setQx_history(history.toCharArray());
                hisObj.setQx_datemodified(DateHelper.getNowDate());
                hisHnd.commit();
            }
        } catch (Throwable tr) {
            ErrorHelper.throwSystemException(tr, this);
        }
    }

    /**
     * Returns the history log as an HTML document.
     *
     * @param history history log
     * @return HTML document
     */
    public String toHTML(String history) {
        DEBUG("Making the history log HTML view...");
        if(StringHelper.isEmpty(history)) {
            return history;
        }
        String html;
        try {
            html = transform(history, HistoryConstants.HISTORY_HTML_TRANSLET);
        } catch (Exception ex) {
            ERROR(ex);
            html = getAsCDATA(history);
        }

        DEBUG("\n\n\t XML data: \n\n" + history + "\n\n");
        DEBUG("\n\n\t HTML data: \n\n" + html + "\n\n");
        return html;
    }

    /**
     * Returns the history log as a text document.
     *
     * @param history history log
     * @return text document
     */
    public String toText(String history) {
        DEBUG("Making the history log text view...");
        if(StringHelper.isEmpty(history)) {
            return history;
        }
        String text;
        try {
            text = transform(history, HistoryConstants.HISTORY_TEXT_TRANSLET);
        } catch (Exception ex) {
            ERROR(ex);
            text = history;
        }

        DEBUG("\n\n\t XML data: \n\n" + history + "\n\n");
        DEBUG("\n\n\t Text data: \n\n" + text + "\n\n");
        return text;
    }

    /**
     * Checks history XML (really checks for <history-topic> tag).
     *
     * @param history the history topic to check
     * @return true if valid
     */
    public boolean isHistoryValid(String history) {
        if(history == null) {
            return true;
        }
        int pos = history.indexOf(HistoryConstants.HISTORY_TOPIC_TAG);
        return (pos >= 0);
    }

    // ========================================================= Private methods

    // Gets the names of fields that might be logged.

    private List getLogFieldNames(EQLSession session, String dbObject) {

        EQLFactory factory = EQLFactory.getInstance();
        return factory.getHistoryBuilder(session).getLogFieldNames(dbObject);
    }

    // Creates a new history topic.
    private HistoryTopic createHistoryTopic(EQLSession session,
                                            EQLResRecord resRecord,
                                            List fieldNames) {

        EQLFactory factory = EQLFactory.getInstance();
        return factory.getHistoryBuilder(session).createHistoryTopic(resRecord,
                fieldNames);
    }

    // Converts the XML object to string.
    private String toString(Object o) {

        char[] data = XMLHelper.writeObject(o);
        String s = String.valueOf(data);

        // Remove <?xml ...?> string and 'bad' characters.
        return StringHelper.clearXml(s);

    }

    // Puts the given string into the CDATA section.
    private String getAsCDATA(String s) {
        StringBuffer sb = new StringBuffer();
        sb.append("<html>");
        sb.append("<![CDATA[").append(s).append("]]>");
        sb.append("</html>");
        return sb.toString();
    }

    // Makes XSL transformation via translet (compiled XSLT).
    private String transform(String history, String translet) {

        if(history == null) {
            return null;
        }

        // Build the well-formed XML document.
        StringBuffer sb = new StringBuffer();
        sb.append("<?xml version='1.0' encoding='UTF-8'?>");
        sb.append(HistoryConstants.HISTORY_START_TAG);
        sb.append(history);
        sb.append(HistoryConstants.HISTORY_END_TAG);

        // Make XSLT.
        StreamSource source = new StreamSource(new CharArrayReader(
                sb.toString().toCharArray()));
        CharArrayWriter writer = new CharArrayWriter();
        transletWrapper.transform(source, new StreamResult(writer), translet,
                null);

        // Ok.
        return writer.toString();
    }

}
