/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * 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 org.apache.deltaspike.core.impl.scope.viewaccess;

import org.apache.deltaspike.core.api.scope.ViewAccessScoped;
import org.apache.deltaspike.core.impl.scope.window.WindowContextImpl;
import org.apache.deltaspike.core.spi.scope.viewaccess.ViewAccessContextManager;
import org.apache.deltaspike.core.util.context.AbstractContext;
import org.apache.deltaspike.core.util.context.ContextualInstanceInfo;
import org.apache.deltaspike.core.util.context.ContextualStorage;

import jakarta.enterprise.context.spi.Contextual;
import jakarta.enterprise.context.spi.CreationalContext;
import jakarta.enterprise.inject.Vetoed;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.enterprise.inject.spi.PassivationCapable;
import java.lang.annotation.Annotation;
import java.util.Map;

@Vetoed
public class ViewAccessContext extends AbstractContext implements ViewAccessContextManager
{
    private static final String KEY = "VAS"; //TODO re-visit key (e.g. view-id instead of using one big storage)

    private final BeanManager beanManager;
    private final WindowContextImpl windowContext;

    private ViewAccessBeanHolder viewAccessBeanHolder;
    private ViewAccessBeanAccessHistory viewAccessBeanAccessHistory;
    private ViewAccessViewHistory viewAccessViewHistory;
    
    public ViewAccessContext(BeanManager beanManager, WindowContextImpl windowContext)
    {
        super(beanManager);

        this.beanManager = beanManager;
        this.windowContext = windowContext;
    }

    public void init(ViewAccessBeanHolder viewAccessBeanHolder,
            ViewAccessBeanAccessHistory viewAccessBeanAccessHistory,
            ViewAccessViewHistory viewAccessViewHistory)
    {
        this.viewAccessBeanHolder = viewAccessBeanHolder;
        this.viewAccessBeanAccessHistory = viewAccessBeanAccessHistory;
        this.viewAccessViewHistory = viewAccessViewHistory;
    }

    @Override
    public <T> T get(Contextual<T> bean)
    {
        try
        {
            return super.get(bean);
        }
        finally
        {
            if (bean instanceof PassivationCapable)
            {
                PassivationCapable pc = (PassivationCapable) bean;
                viewAccessBeanAccessHistory.getAccessedBeans().add(pc.getId());
            }
        }
    }

    @Override
    public <T> T get(Contextual<T> bean, CreationalContext<T> creationalContext)
    {
        try
        {
            return super.get(bean, creationalContext);
        }
        finally
        {
            if (bean instanceof PassivationCapable)
            {
                PassivationCapable pc = (PassivationCapable) bean;
                viewAccessBeanAccessHistory.getAccessedBeans().add(pc.getId());
            }
        }
    }
    
    @Override
    protected ContextualStorage getContextualStorage(Contextual<?> contextual, boolean createIfNotExist)
    {
        return this.viewAccessBeanHolder.getContextualStorage(this.beanManager, KEY, createIfNotExist);
    }

    @Override
    public Class<? extends Annotation> getScope()
    {
        return ViewAccessScoped.class;
    }

    @Override
    public boolean isActive()
    {
        return this.windowContext.isActive(); //autom. active once a window is active
    }

    public void onProcessingViewFinished(String view)
    {
        close(view, false);
    }

    public void close(String view, boolean force)
    {
        // ignore if WindowContext isn't active - our ViewAccessViewHistory is WindowScoped
        if (!windowContext.isActive())
        {
            return;
        }

        // destroy beans only if the view has been changed
        if (force || !view.equals(viewAccessViewHistory.getLastView()))
        {
            viewAccessViewHistory.setLastView(view);
            
            destroyExpiredBeans(force);
        }
        
        // clear history after each rendering process
        viewAccessBeanAccessHistory.getAccessedBeans().clear();
    }
    
    private void destroyExpiredBeans(boolean force)
    {
        ContextualStorage storage = viewAccessBeanHolder.getContextualStorage(beanManager, KEY, false);
        if (storage != null)
        {
            for (Map.Entry<Object, ContextualInstanceInfo<?>> storageEntry : storage.getStorage().entrySet())
            {
                if (force || !viewAccessBeanAccessHistory.getAccessedBeans().contains((String) storageEntry.getKey()))
                {
                    Contextual bean = storage.getBean(storageEntry.getKey());
                    AbstractContext.destroyBean(bean, storageEntry.getValue());
                    storage.getStorage().remove(storageEntry.getKey()); //ok due to ConcurrentHashMap
                }
            }
        }
    }

    @Override
    public void close()
    {
        close(null, true);
    }
}
