/*
 * Copyright (C) 2003-2004 Red Hat Inc. All Rights Reserved.
 *
 * The contents of this file are subject to the CCM Public
 * License (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.redhat.com/licenses/ccmpl.html.
 *
 * Software distributed under the License is distributed on an
 * "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express
 * or implied. See the License for the specific language
 * governing rights and limitations under the License.
 *
 */
package com.arsdigita.search.intermedia;

import com.arsdigita.domain.DomainObject;
import com.arsdigita.persistence.OID;
import com.arsdigita.kernel.Party;
import com.arsdigita.search.Search;
import com.arsdigita.search.ContentProvider;
import com.arsdigita.search.ContentType;
import com.arsdigita.search.MetadataProvider;
import com.arsdigita.search.MetadataProviderRegistry;

import java.math.BigDecimal;
import java.util.Locale;
import org.apache.log4j.Logger;


public class DocumentObserver implements com.arsdigita.search.DocumentObserver {

    private static final Logger s_log = 
        Logger.getLogger(DocumentObserver.class);
    
    /**
     * Invoked after a searchable object has been 
     * created or updated.
     *
     * @param dobj the updated object
     */
    public void onSave(DomainObject dobj) {
        MetadataProvider adapter = MetadataProviderRegistry
            .findAdapter(dobj.getObjectType());

        if (adapter != null) {
            if (s_log.isDebugEnabled()) {
                s_log.debug("Processing object " + dobj.getOID() + 
                            " using new adapters");
            } 

            ContentProvider[] xmlContent = adapter.getContent(dobj, ContentType.XML);
            ContentProvider[] rawContent = adapter.getContent(dobj, ContentType.RAW);
            
            // We only support one block of XML & RAW content per object currently
            byte[] xml = xmlContent.length > 0 ? xmlContent[0].getBytes() : null;
            byte[] raw = rawContent.length > 0 ? rawContent[0].getBytes() : null;

            Locale locale = adapter.getLocale(dobj);

            doOnSave(dobj.getOID(),
                     adapter.getTitle(dobj),
                     adapter.getSummary(dobj),
                     locale == null ? "en" : locale.getLanguage(),
                     xml == null ? null : new String(xml),
                     raw);

        } else {
            if (s_log.isDebugEnabled()) {
                s_log.debug("Processing object " + dobj.getOID() + 
                            " using old adapters");
            } 

            Searchable searchable = (Searchable)dobj;
            String xml = searchable.getSearchXMLContent();
            byte[] raw = searchable.getSearchRawContent();

            if ((xml == null || xml.length() == 0) && 
                (raw == null || raw.length == 0)) {
                doOnDelete(dobj.getOID());
            } else {
                // Check for using SearchIndexHelp class.
                final String helpKeyword = "use_SearchIndexHelp";
                boolean help_xml = xml != null && 
                    xml.equalsIgnoreCase(helpKeyword);
                boolean help_raw = raw != null && 
                    raw.length == helpKeyword.length() &&
                    helpKeyword.equalsIgnoreCase(new String(raw));
                if (help_xml || help_raw) {
                    SearchIndexHelp sh = new SearchIndexHelp();
                    // Get wanted content to index.  Is stored in sh object.
                    sh.retrieveContent(dobj);
                    if (help_xml) {
                        xml = sh.xmlContent();
                    }
                    if (help_raw) {
                        raw = sh.rawContent();
                    }
                }
                
                doOnSave(dobj.getOID(),
                         searchable.getSearchLinkText(),
                         searchable.getSearchSummary(),
                         searchable.getSearchLanguage(),
                         xml,
                         raw);
            }
        }
    }

    /**
     * Invoked after a searchable object has been 
     * deleted. NB, the only guarenteed valid method
     * that can be invoked on the DomainObject is
     * getOID().
     *
     * @param dobj the deleted object
     */
    public void onDelete(DomainObject dobj) {
        doOnDelete(dobj.getOID());
    }

    
    private void doOnSave(OID oid,
                          String title,
                          String summary,
                          String language,
                          String xml,
                          byte[] raw) {
        SearchContent sc = SearchContent.retrieveForObject(oid);
        if (sc == null) {
            // create new SearchContent object (but don't save in database yet).
            sc = new SearchContent();
            BigDecimal id = (BigDecimal)oid.get("id");
            sc.setObjectId(id);
            sc.setContentObjectType(oid.getObjectType().getQualifiedName());
        } else {
            // Object already indexed.  Don't save again if there were no changes
            if (stringMatch(sc.getSummary(), summary) &&
                stringMatch(sc.getLinkText(), title) &&
                stringMatch(sc.getXMLContent(), xml) &&
                byteMatch(sc.getRawContent(), raw) &&
                stringMatch(sc.getLanguage(), language)) {
                return;  // no changes to object, don't save again.
            }
        }
        
        sc.setSummary(summary);
        sc.setLinkText(title);
        sc.setXMLContent(xml);
        sc.setRawContent(raw);
        sc.setLanguage(language);
        sc.save();
        
        // Flag that content changed so index will be resynced
        ContentChangeTime.flagChange();
    }

    private void doOnDelete(OID oid) {
        SearchContent sc = SearchContent.retrieveForObject(oid);
        if (sc != null) {
            sc.delete();
            // Flag that content changed so index will be resynced
            ContentChangeTime.flagChange();
        }
    }


    private boolean stringMatch(String a, String b) {
        if (a==null) {
            return (b==null);
        }
        return a.equals(b);
    }
    
    private boolean byteMatch(byte [] a, byte [] b) {
        if (a == null) {
            return (b == null);
        }
        if (b == null) {
            return false;
        }
        if (a.length != b.length) {
            return false;
        }
        for (int i = 0; i < a.length; i++) {
            if (a[i] != b[i]) {
                return false;
            }
        }
        return true;
    }
    

}
