/*
 * 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.netbeans.modules.editor.fold.ui;

import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.prefs.AbstractPreferences;
import java.util.prefs.BackingStoreException;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import org.netbeans.modules.editor.settings.storage.api.OverridePreferences;

/**
 * Support for inheriting Preferences, while still working with stored ones.
 * 
 * This class solves the 'diamond inheritance', which is present during editing:
 * a MIME-type preferences derive from BOTH its persistent values (preferred) and
 * from the parent, whose actual values are potentially transient, and also persistent.
 * <p/>
 * Let us assume the following assignment:
 * <ul>
 * <li>TC (this current) = currently added/changed/removed values
 * <li>TP (this persistent) = persistent values, the getLocal() part of the Mime PreferencesImpl object
 * <li>PC (parent current) = currently added/changed/removed values of the parent
 * <li>PP (parent persistent) = persistent values, the getLocal() part of the parent MimePreferences
 * </ul>
 * The desired priority to find a value is: TC, TP, PC, PP. Because of {@link MemoryPreferences}, the
 * PC, PP (and potentially fallback to a grandparent) we already have, if we use the parent's {@link MemoryPreferences}
 * preferences as 'inherited'. The "TC" is handled by ProxyPreferences for this Mime node. In InheritedPreferences,
 * we must only inject the TP in between TC and the parent's preferences (PC, PP, ...)
 * <p/>
 * The object is intended to act as a ProxyPreferences delegate, all writes go directly to the stored
 * Mime preferences.
 * 
 * @author sdedic
 */
public final class InheritedPreferences extends AbstractPreferences implements PreferenceChangeListener, OverridePreferences  {
    /**
     * Preferences inherited, ie from a parent Mime type
     */
    private Preferences inherited;
    
    /**
     * Stored preferences, 
     */
    private Preferences stored;
    
    public InheritedPreferences(Preferences inherited, Preferences stored) {
        super(null, ""); // NOI18N
        this.inherited = inherited;
        if (!(stored instanceof OverridePreferences)) {
            throw new IllegalArgumentException();
        }
        this.stored = stored;
        
        inherited.addPreferenceChangeListener(this);
    }
    
    @Override
    protected void putSpi(String key, String value) {
        // do nothing, the AbstractPref then just fires an event
    }

    @Override
    public void put(String key, String value) {
        if (Boolean.TRUE != ignorePut.get()) {
            stored.put(key, value);
        }
        super.put(key, value);
    }

    @Override
    public void putInt(String key, int value) {
        if (Boolean.TRUE != ignorePut.get()) {
            stored.putInt(key, value);
        }
        super.putInt(key, value); 
    }

    @Override
    public void putLong(String key, long value) {
        if (Boolean.TRUE != ignorePut.get()) {
            stored.putLong(key, value);
        }
        super.putLong(key, value);
    }

    @Override
    public void putBoolean(String key, boolean value) {
        if (Boolean.TRUE != ignorePut.get()) {
            stored.putBoolean(key, value);
        }
        super.putBoolean(key, value);
    }

    @Override
    public void putFloat(String key, float value) {
        if (Boolean.TRUE != ignorePut.get()) {
            stored.putFloat(key, value);
        }
        super.putFloat(key, value); 
    }

    @Override
    public void putDouble(String key, double value) {
        if (Boolean.TRUE != ignorePut.get()) {
            stored.putDouble(key, value);
        }
        super.putDouble(key, value); 
    }

    @Override
    public void putByteArray(String key, byte[] value) {
        if (Boolean.TRUE != ignorePut.get()) {
            stored.putByteArray(key, value);
        }
        super.putByteArray(key, value);
    }
    
    private ThreadLocal<Boolean> ignorePut = new ThreadLocal<Boolean>();

    @Override
    public void preferenceChange(PreferenceChangeEvent evt) {
        if (evt.getKey() != null && !isOverriden(evt.getKey())) {
            // jusr refires an event
            ignorePut.set(true);
            try {
                put(evt.getKey(), evt.getNewValue());
            } finally {
                ignorePut.set(false);
            }
        }
    }
    
    /**
     * The value is defined locally, if the stored prefs define the value
     * locally. The parent definitions do not count. It is expected, that the
     * ProxyPreferences will report its local overrides as local in front of this
     * InheritedPreferences.
     * 
     * @param k
     * @return 
     */
    public @Override boolean isOverriden(String k) {
        if (stored instanceof OverridePreferences) {
            return ((OverridePreferences)stored).isOverriden(k);
        } else {
            return true;
        }
    }
    
    @Override
    protected String getSpi(String key) {
        // check the stored values
        OverridePreferences localStored = (OverridePreferences)stored;
        if (localStored.isOverriden(key)) {
            return stored.get(key, null);
        }
        // fall back to the inherited prefs, potentially its stored values etc.
        return inherited.get(key, null);
    }

    @Override
    protected void removeSpi(String key) {
        stored.remove(key);
    }

    @Override
    protected void removeNodeSpi() throws BackingStoreException {
        stored.removeNode();
    }

    @Override
    protected String[] keysSpi() throws BackingStoreException {
        Collection<String> names = new HashSet<String>();
        names.addAll(Arrays.asList(stored.keys()));
        names.addAll(Arrays.asList(inherited.keys()));
        return names.toArray(new String[names.size()]);
    }

    @Override
    protected String[] childrenNamesSpi() throws BackingStoreException {
        if (stored != null) {
            return stored.childrenNames();
        } else {
            return new String[0];
        }
    }

    @Override
    protected AbstractPreferences childSpi(String name) {
        Preferences storedNode = stored != null ? stored.node(name) : null;
        if (storedNode != null) {
            return new InheritedPreferences(null, storedNode);
        } else {
            return null;
        }
    }

    @Override
    protected void syncSpi() throws BackingStoreException {
        stored.sync();
    }

    @Override
    protected void flushSpi() throws BackingStoreException {
        stored.flush();
    }
    
    
}
