/*
 * Copyright (c) 2003-2004, KNOPFLERFISH project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials
 *   provided with the distribution.
 *
 * - Neither the name of the KNOPFLERFISH project nor the names of its
 *   contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.knopflerfish.bundle.cm;

import org.osgi.framework.*;
import org.knopflerfish.service.log.*;
import org.knopflerfish.shared.cm.*;
import java.io.*;
import java.lang.*;
import java.lang.reflect.Array;
import java.util.*;

/**
 * Persistent storage of configuration data
 *
 * @author Per Gustafson
 * @version $Revision: 1.1.1.1 $
 */

class ConfigurationStore {
  private final static String storeDataFile = "store_data";
  private final static String pidDataFile = "pid_to_file";
  private final static String factoryPidDataFile = "fpid_to_pids";
  private final static String generatedPids = "generated_pids";

  private final static String FACTORY_PID = "service.factoryPid";
  private final static String SERVICE_PID = Constants.SERVICE_PID;

  private final File storeDir;
  private Properties storeData;
  private Properties pidToFileName;
  private Hashtable factoryPidToPids;
  
  private Hashtable cache = new Hashtable();
  private CMDataReader cmDataReader= new CMDataReader();

  public ConfigurationStore(File storeDir)
      throws IOException {
    this.storeDir = storeDir;
    storeDir.mkdirs();

    
    storeData = createAndLoadProperties(storeDataFile);
    pidToFileName = createAndLoadProperties(pidDataFile);
    factoryPidToPids = loadHashtable(factoryPidDataFile);
    if(factoryPidToPids == null) {
      factoryPidToPids = new Hashtable();
    }
  }

  public synchronized Enumeration listPids() throws IOException {
    return pidToFileName.keys();
  }

  public synchronized ConfigurationDictionary load(String pid) throws IOException {
    String fileName = (String)pidToFileName.get(pid);
    if(fileName == null) {
      return null;
    }
    return loadConfigurationDictionary(fileName);
  }

  public synchronized ConfigurationDictionary[] loadAll(String factoryPid) throws IOException {
    Vector v = (Vector)factoryPidToPids.get(factoryPid);
    if(v == null) {
      return null;
    }
    Vector loaded = new Vector();
    for(int i = 0; i < v.size(); ++i) {
      ConfigurationDictionary d = load((String)v.elementAt(i));
      if(d != null) {
        loaded.addElement(d);
      }
    }
    ConfigurationDictionary[] configurations = new ConfigurationDictionary[loaded.size()];
    loaded.copyInto(configurations);
    return configurations;
  }

  public synchronized void store(String pid, String factoryPid, Dictionary configuration)
      throws IOException {
    String fileName = fileNameOf(pid, factoryPid);

    storeConfigurationDictionary(configuration, fileName);
  }

  public synchronized ConfigurationDictionary delete(String pid) throws IOException {
    if(pid == null || "".equals(pid)) {
      return null;
    }
    String fileName = (String)pidToFileName.remove(pid);
    if(fileName == null || "".equals(fileName)) {
      return null;
    }
    storeProperties(pidToFileName, pidDataFile);
    
    ConfigurationDictionary d = loadConfigurationDictionary(fileName);
    uncacheConfigurationDictionary(fileName);
    String fpid = (String)d.get(FACTORY_PID);
    if(fpid != null) {
      Vector v = (Vector)factoryPidToPids.get(fpid);
      if(v.removeElement(pid)) {
        storeHashtable(factoryPidToPids, factoryPidDataFile);
      }
    }
    
    File f = new File(storeDir, fileName);
    if(f.exists()) {
      f.delete();
    }
    return d;
  }

  public synchronized void deleteAll(String factoryPid) throws IOException {
    Vector v = (Vector)factoryPidToPids.get(factoryPid);
    if(v == null) {
     return; 
    }
    Enumeration e = v.elements();
    while(e.hasMoreElements()) {
      delete((String)e.nextElement());
    }
  }

  public synchronized String generatePid(String factoryPid) throws IOException {
    String suffix = null;
    Properties p = new Properties();
    File pidFile = new File(storeDir, generatedPids);
    if(pidFile.exists()) {
      InputStream is = new BufferedInputStream(new FileInputStream(pidFile));
      p.load(is);
      if(is != null) {
        is.close();
      }
      suffix = (String)p.get(factoryPid);
    }
    if(suffix == null) {
      suffix = new Long(0).toString();
    } else {
      long l = Long.parseLong(suffix) + 1;
      suffix = new Long(l).toString();
    }
    p.put(factoryPid, suffix);
    FileOutputStream os = new FileOutputStream(pidFile);
    p.save(os, "Suffixes used for generating pids");
    if(os != null) {
      os.close();
    }
    return factoryPid + "." + suffix;
  }


//////////////////////

  private String fileNameOf(String pid, String factoryPid)
      throws IOException {
    if(pid == null) {
      return null;
    }
    String s = (String)pidToFileName.get(pid);
    if(s == null) {
      s = generateNewFileName(pid, factoryPid);
    }
    return s;
  }

  private String generateNewFileName(String pid, String factoryPid)
      throws IOException{
    final String lastUsedFileNumber = "lastUsedFileNumber";

    String fileNumber = (String)storeData.get(lastUsedFileNumber);
    if(fileNumber == null) {
      fileNumber= new Long(0).toString();
    } else {
      long l = Long.parseLong(fileNumber) + 1;
      fileNumber = new Long(l).toString();
    }
    storeData.put(lastUsedFileNumber, fileNumber);
    storeProperties(storeData, storeDataFile);

    if(factoryPid != null) {
      Vector v = (Vector)factoryPidToPids.get(factoryPid);
      if(v == null) {
        v = new Vector();
      }
      v.addElement(pid);
      factoryPidToPids.put(factoryPid, v);
      storeHashtable(factoryPidToPids, factoryPidDataFile);
    }

    pidToFileName.put(pid, fileNumber);
    storeProperties(pidToFileName, pidDataFile);

    return fileNumber;
  }

  private Properties createAndLoadProperties(String fileName) throws IOException {
    Properties p = new Properties();
    File f = new File(storeDir, fileName);
    if(f.exists()) {
      InputStream is = new BufferedInputStream(new FileInputStream(f));
      p.load(is);
      if(is != null) {
        is.close();
      }
    }
    return p;
  }

  private void storeProperties(Properties p , String fileName) throws IOException {
    File f = new File(storeDir, fileName);
    OutputStream os = new FileOutputStream(f);
    p.save(os, "Private data of the ConfigurationStore");
    if(os != null) {
      os.close();
    }
  }

//////////////////////
  private Hashtable loadHashtable(String fileName) throws IOException {
    if(fileName == null) {
      return null;
    }

    File f = new File(storeDir, fileName);
    if(!f.exists()) {
      return null;
    }

    PushbackReader r = 
      new PushbackReader(
        new BufferedReader(
          new InputStreamReader(
            new FileInputStream(f), CMDataReader.ENCODING), 8192), 8);

    Hashtable h = null;
    try {
      h = cmDataReader.readCMData(r);
    } catch(Exception e) {
      Activator.log.error("Failed reading file " + f.toString(), e); 
      h = null;
    }

    if(r != null) {
      r.close();
    }

    return h;
  }

  private void storeHashtable(Hashtable h, String fileName) throws IOException {
    File f = new File(storeDir, fileName);
    PrintWriter w =
        new PrintWriter(
          new OutputStreamWriter(
            new FileOutputStream(f), CMDataWriter.ENCODING));

    CMDataWriter.writeConfiguration(fileName, h, w);
    if(w != null) {
      w.close();
    }
  }

///////////////

  private ConfigurationDictionary getCachedConfigurationDictionary(String fileName) {
    return (ConfigurationDictionary)cache.get(fileName);
  }

  private void readAndCacheConfigurationDictionary(String fileName) throws IOException {
    Hashtable h = loadHashtable(fileName);
    if(h != null) {
      cache.put(fileName, new ConfigurationDictionary(h));
    }
  }

  private void uncacheConfigurationDictionary(String fileName) {
    cache.remove(fileName);
  }

  private ConfigurationDictionary loadConfigurationDictionary(String fileName) throws IOException {
    if(fileName == null) {
      return null;
    }

    ConfigurationDictionary d = getCachedConfigurationDictionary(fileName);
    if(d == null) {
      readAndCacheConfigurationDictionary(fileName);
      d = getCachedConfigurationDictionary(fileName);
    }
    return d;
  }

  private void storeConfigurationDictionary(ConfigurationDictionary d, String fileName) throws IOException {
    storeConfigurationDictionary(d.getOriginal(), fileName);
  }

  private void storeConfigurationDictionary(Dictionary d, String fileName) throws IOException {
    uncacheConfigurationDictionary(fileName);
    File f = new File(storeDir, fileName);
    PrintWriter w =
        new PrintWriter(
          new OutputStreamWriter(
            new FileOutputStream(f), CMDataWriter.ENCODING));


    String pid = (String)d.get(SERVICE_PID);
    String fpid = (String)d.get(FACTORY_PID);
    if(fpid == null) {
      CMDataWriter.writeConfiguration(pid, d, w);
    } else {
      CMDataWriter.writeFactoryConfiguration(fpid, pid, d, w);
    }

    if(w != null) {
      w.close();
    }
  }
}

