/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package com.xpn.xwiki.internal.filter.output;

import javax.inject.Inject;
import javax.inject.Provider;

import org.apache.commons.lang3.StringUtils;
import org.xwiki.component.annotation.Component;
import org.xwiki.component.annotation.InstantiationStrategy;
import org.xwiki.component.descriptor.ComponentInstantiationStrategy;
import org.xwiki.component.phase.Initializable;
import org.xwiki.component.phase.InitializationException;
import org.xwiki.filter.FilterEventParameters;
import org.xwiki.filter.FilterException;
import org.xwiki.filter.event.model.WikiObjectFilter;
import org.xwiki.model.reference.EntityReference;

import com.xpn.xwiki.XWikiContext;
import com.xpn.xwiki.XWikiException;
import com.xpn.xwiki.objects.BaseObject;
import com.xpn.xwiki.objects.BaseObjectReference;
import com.xpn.xwiki.objects.BaseProperty;
import com.xpn.xwiki.objects.classes.BaseClass;
import com.xpn.xwiki.objects.classes.PropertyClass;

/**
 * @version $Id$
 * @since 9.0RC1
 */
@Component
@InstantiationStrategy(ComponentInstantiationStrategy.PER_LOOKUP)
public class BaseObjectOutputFilterStream extends AbstractEntityOutputFilterStream<BaseObject> implements Initializable
{
    @Inject
    private EntityOutputFilterStream<BaseClass> classFilter;

    @Inject
    private EntityOutputFilterStream<BaseProperty> propertyFilter;

    @Inject
    private Provider<XWikiContext> xcontextProvider;

    private BaseObject externalEntity;

    private BaseClass databaseXClass;

    private BaseClass currentXClass;

    @Override
    public void initialize() throws InitializationException
    {
        initialize(this.classFilter, this.propertyFilter);
    }

    @Override
    public void setEntity(BaseObject entity)
    {
        super.setEntity(entity);

        this.externalEntity = entity;
    }

    private BaseClassOutputFilterStream getBaseClassOutputFilterStream()
    {
        return (BaseClassOutputFilterStream) this.classFilter;
    }

    private BasePropertyOutputFilterStream getBasePropertyOutputFilterStream()
    {
        return (BasePropertyOutputFilterStream) this.propertyFilter;
    }

    private void setCurrentXClass(BaseClass xclass)
    {
        this.currentXClass = xclass;
        getBasePropertyOutputFilterStream().setCurrentXClass(xclass);
    }

    // Events

    @Override
    public void endWikiClass(FilterEventParameters parameters) throws FilterException
    {
        if (this.entity != null) {
            BaseClass xclass = getBaseClassOutputFilterStream().getEntity();

            if (xclass != null) {
                // Re-create the object instance if not already provided and if there is a custom class
                if (this.externalEntity == null && StringUtils.isNotEmpty(xclass.getCustomClass())) {
                    BaseObject customObject;
                    try {
                        customObject = xclass.newCustomClassInstance(this.xcontextProvider.get());
                        customObject.setDocumentReference(this.entity.getDocumentReference());
                        customObject.setXClassReference(this.entity.getXClassReference());
                        customObject.setOwnerDocument(this.entity.getOwnerDocument());
                        // Pass false as an optimization since there is nothing to clean on a new object
                        customObject.apply(this.entity, false);

                        this.entity = customObject;
                    } catch (XWikiException e) {
                        // TODO: should probably log a warning
                    }
                }

                setCurrentXClass(xclass);
                getBaseClassOutputFilterStream().setEntity(null);
            }
        }
    }

    @Override
    public void beginWikiObject(String name, FilterEventParameters parameters) throws FilterException
    {
        super.beginWikiObject(name, parameters);

        if (this.enabled) {
            if (this.entity == null) {
                this.entity = new BaseObject();
            }

            if (parameters.containsKey(WikiObjectFilter.PARAMETER_NAME)) {
                this.entity
                    .setDocumentReference(getDocumentReference(WikiObjectFilter.PARAMETER_NAME, parameters, null));
            }

            int number = getInt(WikiObjectFilter.PARAMETER_NUMBER, parameters, -1);

            EntityReference classReference =
                getEntityReference(WikiObjectFilter.PARAMETER_CLASS_REFERENCE, parameters, null);
            if (classReference == null) {
                BaseObjectReference reference = new BaseObjectReference(this.currentEntityReference);

                classReference = reference.getXClassReference();

                if (number < 0 && reference.getObjectNumber() != null) {
                    number = reference.getObjectNumber();
                }
            }
            this.entity.setXClassReference(classReference);

            // Get the class in the current instance
            XWikiContext xcontext = this.xcontextProvider.get();
            if (xcontext != null && xcontext.getWiki() != null) {
                try {
                    this.databaseXClass = xcontext.getWiki().getXClass(this.entity.getXClassReference(), xcontext);
                    setCurrentXClass(this.databaseXClass);
                } catch (XWikiException e) {
                    // TODO: log something ?
                }
            }

            this.entity.setNumber(number);

            this.entity.setGuid(getString(WikiObjectFilter.PARAMETER_GUID, parameters, null));

            getBaseClassOutputFilterStream().enable();
            getBasePropertyOutputFilterStream().enable();
        }
    }

    @Override
    public void endWikiObject(String name, FilterEventParameters parameters) throws FilterException
    {
        // Add missing properties from the class
        if (this.entity != null && this.databaseXClass != null) {
            for (String key : this.databaseXClass.getPropertyList()) {
                if (this.entity.safeget(key) == null) {
                    PropertyClass classProperty = (PropertyClass) this.databaseXClass.getField(key);
                    this.entity.safeput(key, classProperty.newProperty());
                }
            }
        }

        super.endWikiObject(name, parameters);

        getBaseClassOutputFilterStream().disable();
        getBaseClassOutputFilterStream().disable();
    }

    @Override
    public void onWikiObjectProperty(String name, Object value, FilterEventParameters parameters) throws FilterException
    {
        if (this.enabled) {
            if (getBasePropertyOutputFilterStream().getEntity() != null) {
                this.entity.safeput(name, getBasePropertyOutputFilterStream().getEntity());

                getBasePropertyOutputFilterStream().setEntity(null);
            }
        }
    }
}

@@ -2368,46 +2368,8 @@
 s;%0A%0A
-    private BaseClass currentXClass;%0A%0A
     
@@ -3026,45 +3026,8 @@
   %7B%0A
-        this.currentXClass = xclass;%0A
     

Remove unused code