/* 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 java.util.prefs;

import java.io.File;
import java.io.FilenameFilter;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

/**
 * The default implementation of <code>AbstractPreferences</code> for the Linux
 * platform, using the file system as its back end.
 *
 * TODO some sync mechanism with backend, Performance - check file edit date
 *
 * @since 1.4
 */
class FilePreferencesImpl extends AbstractPreferences {

    /*
     * --------------------------------------------------------------
     * Class fields
     * --------------------------------------------------------------
     */

    //prefs file name
    private static final String PREFS_FILE_NAME = "prefs.xml";

    //home directory for user prefs
    private static String USER_HOME;

    //home directory for system prefs
    private static String SYSTEM_HOME;

    /*
     * --------------------------------------------------------------
     * Class initializer
     * --------------------------------------------------------------
     */
    static {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
                USER_HOME = System.getProperty("user.home") + "/.java/.userPrefs";
                SYSTEM_HOME = System.getProperty("java.home") + "/.systemPrefs";
                return null;
            }
        });
    }

    /*
     * --------------------------------------------------------------
     * Instance fields
     * --------------------------------------------------------------
     */

    //file path for this preferences node
    private String path;

    //internal cache for prefs key-value pair
    private Properties prefs;

    //file represents this preferences node
    private File prefsFile;

    //parent dir for this preferences node
    private File dir;

    //cache for removed prefs key-value pair
    private Set<String> removed = new HashSet<String>();

    //cache for updated prefs key-value pair
    private Set<String> updated = new HashSet<String>();

    /*
     * --------------------------------------------------------------
     * Constructors
     * --------------------------------------------------------------
     */

    /**
     * Construct root <code>FilePreferencesImpl</code> instance, construct
     * user root if userNode is true, system root otherwise
     */
    FilePreferencesImpl(boolean userNode) {
        super(null, "");
        this.userNode = userNode;
        path = userNode ? USER_HOME : SYSTEM_HOME;
        initPrefs();
    }

    /**
     * Construct a prefs using given parent and given name
     */
    private FilePreferencesImpl(AbstractPreferences parent, String name) {
        super(parent, name);
        path = ((FilePreferencesImpl) parent).path + File.separator + name;
        initPrefs();
    }

    private void initPrefs() {
        dir = new File(path);
        newNode = (AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
            public Boolean run() {
                return Boolean.valueOf(!dir.exists());
            }
        })).booleanValue();
        prefsFile = new File(path + File.separator + PREFS_FILE_NAME);
        prefs = XMLParser.loadFilePrefs(prefsFile);
    }

    @Override
    protected String[] childrenNamesSpi() throws BackingStoreException {
        String[] names = AccessController
        .doPrivileged(new PrivilegedAction<String[]>() {
            public String[] run() {
                return dir.list(new FilenameFilter() {
                    public boolean accept(File parent, String name) {
                        return new File(path + File.separator + name).isDirectory();
                    }
                });

            }
        });
        if (null == names) {// file is not a directory, exception case
            throw new BackingStoreException("Cannot get child names for " + toString()
                    + " (path is " + path + ")");
        }
        return names;
    }

    @Override
    protected AbstractPreferences childSpi(String name) {
        FilePreferencesImpl child = new FilePreferencesImpl(this, name);
        return child;
    }

    @Override
    protected void flushSpi() throws BackingStoreException {
        try {
            //if removed, return
            if(isRemoved()){
                return;
            }
            // reload
            Properties currentPrefs = XMLParser.loadFilePrefs(prefsFile);
            // merge
            Iterator<String> it = removed.iterator();
            while (it.hasNext()) {
                currentPrefs.remove(it.next());
            }
            removed.clear();
            it = updated.iterator();
            while (it.hasNext()) {
                Object key = it.next();
                currentPrefs.put(key, prefs.get(key));
            }
            updated.clear();
            // flush
            prefs = currentPrefs;
            XMLParser.flushFilePrefs(prefsFile, prefs);
        } catch (Exception e) {
            throw new BackingStoreException(e);
        }
    }

    @Override
    protected String getSpi(String key) {
        try {
            if (null == prefs) {
                prefs = XMLParser.loadFilePrefs(prefsFile);
            }
            return prefs.getProperty(key);
        } catch (Exception e) {
            // if Exception happened, return null
            return null;
        }
    }

    @Override
    protected String[] keysSpi() throws BackingStoreException {
        final Set<Object> ks = prefs.keySet();
        return ks.toArray(new String[ks.size()]);
    }

    @Override
    protected void putSpi(String name, String value) {
        prefs.setProperty(name, value);
        updated.add(name);
    }

    @Override
    protected void removeNodeSpi() throws BackingStoreException {
        boolean removeSucceed = (AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
            public Boolean run() {
                prefsFile.delete();
                return Boolean.valueOf(dir.delete());
            }
        })).booleanValue();
        if (!removeSucceed) {
            throw new BackingStoreException("Cannot remove " + toString());
        }
    }

    @Override
    protected void removeSpi(String key) {
        prefs.remove(key);
        updated.remove(key);
        removed.add(key);
    }

    @Override
    protected void syncSpi() throws BackingStoreException {
        flushSpi();
    }
}
