/**
 * SLPv2 DA Database Management
 *
 * We implemented the database function within Java2. We didn't use
 * a standard DBMS for simplicity and efficiency. The whole database
 * is organized as red-black tree (provided by Java)
 *
 *  (1) the key is ltag+URL, assume each service has a unique URL, 
 *      and the same service can be registered using different 
 *      languaage (ltag)
 *  (2) the value at each node is an Entry class, which keeps all the
 *      information about the service.
 *
 */

package eu.artemis.shield.discovery.slpdaemon.impl;

import java.io.*; 
import java.util.*;
import java.security.interfaces.*;

public class Database {
  
  public static final int DELETED = 0;
  public static final int LANGUAGE_TAG = 1;
  public static final int NAMING_AUTHORITY =2;
  public static final int TYPE = 3;
  public static final int URL = 4;
  public static final int LIFE_TIME = 5;
  public static final int SCOPE = 6;
  public static final int VERSION_TS = 7// version TS from the SA for an update
  public static final int ARRIVAL_TS = 8// arrival TS at the DA for an update
  public static final int ACCEPT_DA = 9// the accept DA for the update
  public static final int ACCEPT_TS = 10// the accept TS for the update
  public static final int ATTRIBUTES = 11;
  
  da daf;
    TreeMap table;
    slpMsgComposer composer;
    Vector matchedEntry;
    ByteArrayOutputStream b;
    DataOutputStream d;
    int totalMatch,i=0;
    
    public Database(da daf) {
      this.daf = daf;
        table = new TreeMap();
        composer = new slpMsgComposer();
        matchedEntry = new Vector(10);
        b = new ByteArrayOutputStream();
        d = new DataOutputStream(b);
    }

    // return the number of entries in the database
    public  int size() {
        return table.size();
    }

    public TreeMap table() {
        return table;
    }

    //------------------------------------------------
    // save database to either the stdout or the file
    //------------------------------------------------
    public void saveDatabase(BufferedWriter o
    {
        Iterator values = table.values().iterator();
        while (values.hasNext()) 
        {
            Entry e = (Entryvalues.next();
            e.prtEntry(/*daf, */o);
        }
    }

    //------------------------------------------------
    // return the database as Vector of Vector
    //------------------------------------------------
    public Vector getDatabase() 
    {
      Vector db = new Vector();
      Vector row;      
        Iterator values = table.values().iterator();        
        while (values.hasNext()) 
        {          
          row = new Vector();
            Entry e = (Entryvalues.next();
            row.add(new String("" + e.getDeleted()));
            row.add(e.getLtag());
            row.add(e.getType());
            row.add(e.getNA());
            row.add(e.getURL());                                    
            row.add(new Long(e.getLifetime()))//int                    
            row.add(e.getScope());  
            row.add(new Long(e.getVersionTS()))//long
            row.add(new Long(e.getArrivalTS()))//long         
            row.add(e.getAcceptDA());
            row.add(new Long(e.getAcceptTS()))//long         
            row.add(e.getAttr(""));
            db.add(row);
        }
        return db;        
    }            
    
    //------------------------------------------
    // load data form file to internal database
    //------------------------------------------
    public void loadDatabase(String dbase) {
        String line, ltag, type, url, scope, acceptDA, attr = "";
        int lifetime;
        long versionTS, arrivalTS, acceptTS;
        boolean deleted = false;
        try {
            BufferedReader in = 
                new BufferedReader(new FileReader(dbase));
            while ((line = in.readLine()) != null) {
                StringTokenizer st = new StringTokenizer(line);
                deleted = Boolean.valueOf(st.nextToken()).booleanValue();
                ltag = st.nextToken();
                type = st.nextToken();
                url  = st.nextToken();
                lifetime = Integer.parseInt(st.nextToken());
                scope = st.nextToken();
                versionTS = Long.parseLong(st.nextToken());
                arrivalTS = Long.parseLong(st.nextToken());
                acceptDA = st.nextToken();
                acceptTS = Long.parseLong(st.nextToken());
                // Now the DA registers all the values of a given attribute
                st.nextToken(",");
                StringBuffer SB = new StringBuffer(2048)
                while (st.hasMoreTokens()){
                  SB.append(st.nextToken(","));
                  SB.append(",");
                }
                attr = SB.toString();
                
                addEntry(deleted, ltag, type, url, lifetime, scope, attr, 
                         Const.fresh_flag, versionTS, arrivalTS, 
                         acceptDA, acceptTS);
            }
            in.close();
        catch (Exception e) {
            da.appendDebug("Database::loadDatabase");
            da.appendDebug(e);
        }
        daf.refreshDatabaseTable();
    }

    
    
    //-------------------------------------------
    // check lifetime and remove expired entries
    //-------------------------------------------
    public void rmExpiredEntries() {
      boolean dbChanged = false;
        long currtime = System.currentTimeMillis();
        Iterator keys = table.keySet().iterator();
        while (keys.hasNext()) {
            String k = (Stringkeys.next();
            Entry  e = (Entry)  table.get(k);
            int lif = e.getLifetime() *1000;
            if ((lif>0&& (currtime > (e.getArrivalTS() + e.getLifetime()*1000))) {
              dbChanged = true;
                keys.remove();
            }
        }
        if (dbChangeddaf.refreshDatabaseTable();
    }

    //---------------------------------------------------------
    // for "SrvReg"
    // add a new entry of service registration to the database
    // or replace/update its previous registration
    //---------------------------------------------------------
    public int addEntry(boolean deleted, String ltag, String type,
        String url, int lifetime, String scope, String attr, int reg_flag,
        long versionTS, long arrivalTS, String acceptDA, long acceptTS) {
      if (Const.DEBUG_MATCHING_ENABLED)
      {
        da.appendDebug("Database::addEntry");
        da.appendDebug("\t"+deleted+"\t"+ltag+"\t"+type+"\t"+url);
        da.appendDebug("\t"+lifetime+"\t"+scope+"\t"+attr+"\t"+reg_flag+"\n\n");
      }        
      if (!table.containsKey(ltag+url)) {
            if ((reg_flag & Const.fresh_flag== 0) { // incremental SrvReg
                return Const.INVALID_UPDATE;
            }
            Entry ent = new Entry();
            table.put(ltag+url, ent);
        }
        Entry e = (Entrytable.get(ltag+url);        
        int result = e.update(deleted, ltag, url, type, lifetime, scope, attr, 
                        reg_flag, versionTS, arrivalTS, acceptDA, acceptTS);
        daf.refreshDatabaseTable();
        return result;
    }

    //---------------------------------------------------
    // for "SrvDeReg"
    // remove the entry with the key: ltag+url (tag=="")
    // or delete some attributes of this entry (tag!="")
    //---------------------------------------------------
    public int rmEntry(String ltag, String url, String scope, String tag, long versionTS, String acceptDA, long acceptTS
    {
        if (table.containsKey(ltag+url)) 
        {
            Entry e = (Entrytable.get(ltag+url);
            if (!scope.equalsIgnoreCase(e.getScope())) 
            {
                return Const.SCOPE_NOT_SUPPORTED;
            }
            e.deletion(tag, versionTS, acceptDA, acceptTS);
        }
        daf.refreshDatabaseTable();
        return Const.OK;
    }

    //----------------------------------------------------------------------
    // for "SrvTypeRqst"
    // get the list of service types for specified scope & naming authority
    //----------------------------------------------------------------------
    public String getServiceTypeList(String na, String scope
    {
        if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug(
            "\nSrvTypeRqst" +
            "\n-----------" +
        "\nDatabase::getServiceTypeList(Naming Authority,Scope)" +
        "\n- Naming Authority = "+ na +
        "\n- Scope = "+ scope);
    Vector typelist = new Vector();
        Iterator values = table.values().iterator();
        while (values.hasNext()) 
        {
          Entry e = (Entryvalues.next();
          if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Analysing database service --> " + e.getURL());
            if (!e.getDeleted()) // entry not deleted
            {              
              if (scope.equalsIgnoreCase(e.getScope())) // match scope
              {
                if (na.equals("*"|| na.equals(""|| na.equalsIgnoreCase(e.getNA()))
                {
                  if (!typelist.contains(e.getType()))  // has not been already listed
                  {
                    if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("> OKAY --> Service type " + e.getType() " added to the list\n");
                    typelist.addElement(e.getType());
                  }
                  else
                  {
                    if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("> DISCARDED --> This service type has been already added to the list\n");
                  }
                }
                else
                {
                  if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("> DISCARDED --> Different naming authority (" + e.getNA() " vs " + na + ")\n");
                }
              }
              else
              {
                if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("> DISCARDED --> Different scope (" + e.getScope() " vs " + scope + ")\n");
              }
            }
            else
            {
              if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("> DISCARDED --> Service has been deleted\n");
            }               
        }
        StringBuffer tl = new StringBuffer();
        for (int i=0; i<typelist.size(); i++
        {
            String s = (String)typelist.elementAt(i);
            if (tl.length() 0tl.append(",");
            tl.append(s);
        }
        if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("\n- Returned Service Type List = " + tl.toString() "\n");                      
        return tl.toString();
    }

    //-----------------------------------------------------------
    // for "SrvRqst"
    // find the matched URLs with (type, scope, predicate, ltag)
    // return: error code (short)
    //         number of matched URLs (short)
    //         URL blocks (decided by previous #URL)
    //-----------------------------------------------------------

    public int getTotalMatch() {
        return totalMatch;
    }

    public Vector getMatchedEntry() {
        return matchedEntry;
    

    public byte[] getMatchedURL(String type, String scope,
                String pred, String ltag, Vector ssExtList, int ecode,int ID, RSAPublicKey publicKey) {
      
        if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("\n*** SrvRqst ***" +            
        "\nDatabase::getMatchedURL(Type,Scope,Predicate,Ltag)" +
        "\n- Type = "+ type +
        "\n- Scope = "+ scope +
        "\n- Predicate = "+ pred +
        "\n- Ltag = "+ ltag+"\n");
        
        byte[] buf = null
                
      // if scope == "" it means it's the default scope.
      if (scope == ""scope = Const.defaultScope;
        
        if (!Util.shareString(daf.getScope(), scope, ",")) 
        {                              
            ecode = Const.SCOPE_NOT_SUPPORTED;
            if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getMatchedURL() -> SCOPE NOT SUPPORTED!");        
        
         
        // obtain matched entries 
        matchedEntry.clear();
        Iterator values = table.values().iterator();
                
        while (values.hasNext()) 
        {
            Entry e = (Entryvalues.next();
                                    
            if (e.match(type, scope, pred, ltag, (ecode!=Const.AUTHENTICATION_ABSENT)&&(ecode!=Const.AUTHENTICATION_FAILED)&& ID!=Const.SrvRqst))
            {
              if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getMatchedURL() -> Service " + e.getURL() "  ADDED\n");
              matchedEntry.addElement(e);              
            }
            else  
            {
              if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getMatchedURL() -> Service " + e.getURL() " FILTERED\n");
            }
        
        }   
       
      totalMatch = matchedEntry.size();
        if (Const.DEBUG_MATCHING_ENABLED)
        {
          da.appendDebug("Database::getMatchedURL() -> Filtering Process Terminated.");
          da.appendDebug("Database::getMatchedURL() -> " + totalMatch + " services found.");
        }
        
        // filter matched entries
        for (int i=0; i<ssExtList.size(); i++) {
            SelectSortExt ss = (SelectSortExtssExtList.elementAt(i);
            if (ss.getID() == Const.SelectExt) {
                int bound = ss.getBound();
                if (bound < matchedEntry.size()) matchedEntry.setSize(bound);
            else if (ss.getID() == Const.SortExt) {
                sortFilter(ss.getKey());
            }
        
        
        // write matched URLs to buffer
        b.reset();
        try {
            d.writeShort(ecode);                // error code 
            d.writeShort(matchedEntry.size());  // URL count
                        // fill in matched URLs
                for (int i=0; i<matchedEntry.size(); i++) {
                    Entry e = (EntrymatchedEntry.elementAt(i);
                    d.writeByte(0);
                    d.writeShort(e.getLifetime());
                    if (ID==Const.SrvRqstAuth)
                    {   
                      d.writeShort(e.getCryptoURL(publicKey).length);
                      d.write(e.getCryptoURL(publicKey));
                    }
                    else
                    {
                      d.writeShort(e.getURL().length());
                         d.writeBytes(e.getURL());
                    }
                         d.writeByte(0);
                }
             
            buf = b.toByteArray();
        catch (Exception ex) {
            da.appendDebug("Databse::getMatchedURL");
            da.appendDebug(ex);
        }
        return buf;
    }

    private void sortFilter(String key) {
        int size = matchedEntry.size();
        if (size <= 1return// no need to sort
        StringTokenizer st = new StringTokenizer(key, ",");
        int nkey = st.countTokens();
        String[]  keys   = new String[nkey];
        int[]     types  = new int[nkey];
        int[]     orders = new int[nkey];
        Integer[] vals   = new Integer[nkey];
        for (int i=0; i<nkey; i++) {
            String unit = st.nextToken();
            StringTokenizer st1 = new StringTokenizer(unit, ":");
            if (st1.countTokens() 3) {
              da.append("Incorrect sort key list");
            }
            keys[i= st1.nextToken().trim();  // key
            String s = st1.nextToken().trim()// type
            if (s.equalsIgnoreCase("s")) {
                types[i= Const.StringSort;
            else {
                types[i= Const.IntegerSort;
            }
            s = st1.nextToken().trim();        // order
            if (s.equals("+")) {
                orders[i= Const.IncreasingOrder;
            else {
                orders[i= Const.DecreasingOrder;
            }
            if (st1.hasMoreTokens()) {         // reference value
                vals[inew Integer(st1.nextToken().trim());
            else {
                vals[inull;
            }
        }
        Vector se = new Vector(size);
        for (int i=0; i<size; i++) {
            Entry e = (Entry)matchedEntry.elementAt(i);
            se.addElement(new SortEntry(e, keys, types, orders, vals));
        }
        Collections.sort(se);
        matchedEntry.clear();
        for (int i=0; i<size; i++) {
            SortEntry s = (SortEntryse.elementAt(i);
            matchedEntry.addElement(s.getEntry());
        }
    }

    //------------------------------------------------------
    /**
     @author Vincenzo Suraci
     
     @param URLorType: is a ServiceURL or a Service Type
     @param scope: is a comma separated scope list
     @param tag: is a comma separated attribute tag list
     @param ltag: is a language tag
     
     @return a String containing all the attribute tags that match with the Service URL or Type,
     *  with the scope list, with the attribute tag list and with the language tag 
     */ 
    //------------------------------------------------------
    public String getAttrList(String URLOrType, String scope, String tag, String ltag
    {                
      if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("\n\nDatabase::getAttrList(url, scope, tag, ltag)" +
        "\n- URL or Service Type = "+ URLOrType +
        "\n- Scope List = "+ scope +
        "\n- Attribute Tag List = " + tag +
        "\n- Language Tag = " + ltag + "\n");
      
      /*
       * Check if we have a ServiceURL or a ServiceTpye       
       */
      String url = URLOrType;
      if (table.containsKey(ltag+url)) 
    {      
        //if (Const.DEBUG_MATCHING_ENABLED) da.appendDebug("Database::getAttrList() -> We have a URL!");
      Entry e = (Entrytable.get(ltag+url);  
      if (e.getDeleted())
      {
        if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getAttrList() -> Service " + e.getURL() " has been DELETED");
        return "";
      }
      else if (e.isExpired())
      {
        if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getAttrList() -> Service " + e.getURL() " is EXPIRED");
        return "";
      }
      else if (!Util.containsString(scope, e.getScope()","))
            {
        if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getAttrList() -> Service " + e.getURL() " has a different scope (" + e.getScope() " is not contined in " + scope + ")");        
        return "";
            
            return e.getAttr(tag);                            
        }      
      String result = "";
      String type = URLOrType;
      if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getAttrList() -> We have a TYPE!");
      /*
       * FROM RFC 2614
       * For the type and scope, return a Vector of all ServiceLocationAttribute objects whose ids match the String 
       * patterns in the attributeIds Vector regardless of the Locator's locale. The request is made independent of 
       * language locale. If no attributes are found, an empty vector is returned. 
       */
      Iterator it = table.keySet().iterator();
      while (it.hasNext())
      {
        Entry e = (Entry)table.get(it.next());
        if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getAttrList() -> Evaluating Service " + e.getURL());
        if (e.getDeleted())
        {
          if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getAttrList() -> Service " + e.getURL() " has been DELETED");
          return "";
        }
        else if (e.isExpired())
        {
          if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getAttrList() -> Service " + e.getURL() " is EXPIRED");
          return "";
        }
        else if (type.equalsIgnoreCase(e.getType()))
        {
          if (!Util.containsString(scope, e.getScope()","))
                {
            if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getAttrList() -> Service " + e.getURL() " has a different scope (" + e.getScope() " is not contined in " + scope + ")");        
            return "";
                
                
                  if (result.equals("")) result = e.getAttr(tag);
                   else result += "," + e.getAttr(tag);
                  if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getAttrList() -> adding these attributes: " + e.getAttr(tag));
                
        }
        else
        {
          if (Const.DEBUG_MATCHING_ENABLEDda.appendDebug("Database::getAttrList() -> Service " + e.getURL() " has different type (\"" + e.getType() "\" differes from \"" + type + "\")");          
        }                              
      }
      return result;
          
    }
    
    /*
    private String typeAttrList(String type, String scope, 
                                String tag, String ltag) {
        StringBuffer attrList = new StringBuffer();
        Iterator values = table.values().iterator();
        while (values.hasNext()) {
            Entry e = (Entry) values.next();
      if (Const.DEBUG_ENABLED)
      {
        da.append("DEBUG->Database::typeAttrList(...) Found a entry:");
              da.append("entry.type="+e.getType()+"?=?"+type);
        da.append("entry.scope="+e.getScope()+"?=?"+scope);
        da.append("entry.ltag="+e.getLtag()+"?=?"+ltag);
        da.append("entry:deleted="+e.getDeleted());
        da.append("entry:attrs="+e.getAttr(tag));
      }  
            if (!e.getDeleted() &&
                type.equalsIgnoreCase(e.getType()) && 
                scope.equalsIgnoreCase(e.getScope()) &&
                ltag.equalsIgnoreCase(e.getLtag())) {
                String s = e.getAttr(tag);
                if (attrList.length() > 0) attrList.append(",");
                attrList.append(s);
            }
        }
        return attrList.toString();
    }
    */

    //------------------------------------------------------------------
    // find new states based on selective/complete & (rdaList, rtsList)
    // sort new states on their accept IDs and return in TreeMap
    //------------------------------------------------------------------
    public Vector findNewStates(String rscope,
                 Vector rdaList, Vector rtsList, int etrpType) {

        Vector tmp = new Vector();
        Iterator values = table.values().iterator();
        while (values.hasNext()) { 
            Entry  e     = (Entryvalues.next();
            
            
            
            String ada   = e.getAcceptDA();
            long   ats   = e.getAcceptTS();
            
            int    index = rdaList.indexOf(ada)// a requested subset?
            long   rts   = 0;
            if (index != -1) { // it is a requested subset, find rts
                rts = ((LongrtsList.get(index)).longValue();
            }
            if (Util.shareString(rscope, e.getScope()",")) { 
                if (index != -&& ats > rts ||
                    index == -&& etrpType == Const.complete) { 
                    tmp.addElement(e);
                }
            }
        }
        Collections.sort(tmp);
        return tmp;
    }
    
    public String toString(){
      String t ="\n\n----- Database Contents -----\n\n";
      Iterator i = table.values().iterator();
      int n =1;
      while (i.hasNext()){
        t += "\nEntry "+n+")\n"+((Entry)i.next()).toString();
      }
      return t;
    }
    
}