/*
 * 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.debugger.jpda.truffle;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;

import org.netbeans.api.debugger.Properties;
import org.openide.util.BaseUtilities;

public final class TruffleProperties {
    
    private static final Properties truffleProperties = Properties.getDefault().getProperties("debugger.options.Truffle");  // NOI18N
    private static final String PROP_SHOW_INTERNAL = "showInternal";            // NOI18N
    private static TruffleProperties INSTANCE = new TruffleProperties();
    
    private TrufflePropertiesListener trufflePropertiesListener;
    
    private TruffleProperties() {}
    
    /*
    public static TruffleProperties getInstance() {
        return INSTANCE;
    }
    */
    public boolean isShowInternal() {
        return truffleProperties.getBoolean(PROP_SHOW_INTERNAL, false);
    }
    
    public void setShowInternal(boolean showInternal) {
        truffleProperties.setBoolean(PROP_SHOW_INTERNAL, showInternal);
    }
    
    public synchronized Disposable onShowInternalChange(Consumer<Boolean> onChange) {
        if (trufflePropertiesListener == null) {
            trufflePropertiesListener = new TrufflePropertiesListener();
            truffleProperties.addPropertyChangeListener(trufflePropertiesListener);
        }
        return trufflePropertiesListener.addOnShowInternalChange(onChange);
    }
    
    public final class Disposable {
        
        private final LinkedList<?> list;
        private final Consumer f;
        private final DisposableReference ref;
        
        Disposable(LinkedList<?> list, Consumer f) {
            this.list = list;
            this.f = f;
            ref = new DisposableReference(this, BaseUtilities.activeReferenceQueue());
        }
        
        public void dispose() {
            ref.dispose();
            ref.clear();
        }
    }
    
    private static class DisposableReference extends WeakReference<Disposable> implements Runnable {
        
        private final LinkedList<?> list;
        private final Consumer f;
        
        DisposableReference(Disposable disposable,  ReferenceQueue<? super Disposable> queue) {
            super(disposable, queue);
            this.list = disposable.list;
            this.f = disposable.f;
        }
        
        public void dispose() {
            synchronized (list) {
                list.remove(f);
            }
        }

        @Override
        public void run() {
            dispose();
        }
    }
    
    private class TrufflePropertiesListener implements PropertyChangeListener {
        
        private LinkedList<Consumer<Boolean>> onChangeListeners = new LinkedList<>();

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            String propertyName = evt.getPropertyName();
            switch (propertyName) {
                case PROP_SHOW_INTERNAL:
                    Boolean isInternal = (Boolean) evt.getNewValue();
                    List<Consumer<Boolean>> listeners;
                    synchronized (onChangeListeners) {
                        listeners = new ArrayList<>(onChangeListeners);
                    }
                    for (Consumer<Boolean> f : listeners) {
                        f.accept(isInternal);
                    }
                    break;
            }
        }

        private Disposable addOnShowInternalChange(Consumer<Boolean> onChange) {
            synchronized (onChangeListeners) {
                onChangeListeners.add(onChange);
            }
            return new Disposable(onChangeListeners, onChange);
        }
        
    }
    
}
