package org.ff4j.audit.proxy;

/*-
 * #%L
 * ff4j-core
 * %%
 * Copyright (C) 2013 - 2023 FF4J
 * %%
 * Licensed 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.
 * #L%
 */

import static org.ff4j.audit.EventConstants.ACTION_CLEAR;
import static org.ff4j.audit.EventConstants.ACTION_CREATE;
import static org.ff4j.audit.EventConstants.ACTION_CREATESCHEMA;
import static org.ff4j.audit.EventConstants.ACTION_DELETE;
import static org.ff4j.audit.EventConstants.ACTION_UPDATE;
import static org.ff4j.audit.EventConstants.TARGET_PROPERTY;
import static org.ff4j.audit.EventConstants.TARGET_PSTORE;

import java.util.Collection;

import java.util.Map;
import java.util.Set;

import org.ff4j.FF4j;
import org.ff4j.audit.EventBuilder;
import org.ff4j.audit.EventPublisher;
import org.ff4j.property.Property;
import org.ff4j.property.store.PropertyStore;

/**
 * Implementation of audit on top of store.
 *
 * @author Cedrick Lunven (@clunven)
 */
public class PropertyStoreAuditProxy implements PropertyStore {

    /** Current FeatureStore. */
    private PropertyStore target = null;
    
    /** Reference. */
    private FF4j ff4j = null;
    
    /**
     * Only constructor.
     *
     * @param pTarget
     */
    public PropertyStoreAuditProxy(FF4j pFF4j, PropertyStore pTarget) {
        this.target = pTarget;
        this.ff4j   = pFF4j;
    }

    /** {@inheritDoc} */
    public  < T > void createProperty(Property<T> prop) {
        long start = System.nanoTime();
        target.createProperty(prop);
        long duration = System.nanoTime() - start;
        publish(builder(ACTION_CREATE)
                    .property(prop.getName())
                    .value(prop.asString())
                    .duration(duration));
    }

    /** {@inheritDoc} */
    public void updateProperty(String name, String newValue) {
        long start = System.nanoTime();
        target.updateProperty(name, newValue);
        long duration = System.nanoTime() - start;
        publish(builder(ACTION_UPDATE)
                    .property(name)
                    .value(newValue)
                    .duration(duration));
    }

    /** {@inheritDoc} */
    public <T> void updateProperty(Property<T> prop) {
        long start = System.nanoTime();
        target.updateProperty(prop);
        long duration = System.nanoTime() - start;
        publish(builder(ACTION_UPDATE)
                    .property(prop.getName())
                    .value(prop.asString())
                    .duration(duration));
    }

    /** {@inheritDoc} */
    public void deleteProperty(String name) {
        long start = System.nanoTime();
        target.deleteProperty(name);
        long duration = System.nanoTime() - start;
        publish(builder(ACTION_DELETE)
                    .property(name)
                    .duration(duration));
    }
    
    /** {@inheritDoc} */
    public boolean existProperty(String name) {
        return target.existProperty(name);
    }

    /** {@inheritDoc} */
    public Property<?> readProperty(String name) {
        return target.readProperty(name);
    }
    
    /** {@inheritDoc} */
    @Override
    public Property<?> readProperty(String name, Property<?> defaultValue) {
        return target.readProperty(name, defaultValue);
    }
    
    /** {@inheritDoc} */
    public Map<String, Property<?>> readAllProperties() {
        return target.readAllProperties();
    }

    /** {@inheritDoc} */
    public Set<String> listPropertyNames() {
        return target.listPropertyNames();
    }

    /** {@inheritDoc} */
    public boolean isEmpty() {
        return target.isEmpty();
    }

    /** {@inheritDoc} */
    public void clear() {
        long start = System.nanoTime();
        target.clear();
        long duration = System.nanoTime() - start;
        publish(builder(ACTION_CLEAR).type(TARGET_PSTORE)
                .name(ff4j.getPropertiesStore().getClass().getName())
                .duration(duration));
    }
    
    /** {@inheritDoc} */
    public void importProperties(Collection<Property<?>> properties) {
        // Do not use target as the delete/create operation will be traced
        if (properties != null) {
            for (Property<?> property : properties) {
                if (existProperty(property.getName())) {
                    deleteProperty(property.getName());
                }
                createProperty(property);
            }
        }
    }
    
    /** {@inheritDoc} */
    @Override
    public void createSchema() {
        target.createSchema();
        publish(builder(ACTION_CREATESCHEMA).feature("For Properties"));
    }
    
    /**
     * Init a new builder;
     *
     * @return
     *      new builder
     */
    private EventBuilder builder(String action) {
        return new EventBuilder(ff4j).type(TARGET_PROPERTY).action(action);
    }
    
    /**
     * Publish target event to {@link EventPublisher}
     *
     * @param eb
     *      current builder
     */
    private void publish(EventBuilder eb) {
        ff4j.getEventPublisher().publish(eb.build());
    }

	/**
	 * Getter accessor for attribute 'target'.
	 *
	 * @return
	 *       current value of 'target'
	 */
	public PropertyStore getTarget() {
		return target;
	}

       

}
