package hirondelle.web4j.readconfig;

import hirondelle.web4j.util.Args;
import hirondelle.web4j.util.Util;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

/**
 Parse database config settings.
 
  <P>The format of these settings has 2 syntactical forms:
  <ul>
   <li>1 value, no ~ separator - the single value is applied to all databases.
   <li>1 default value + N name=value pairs, separated by ~; the name=value pairs represent overrides to the default value; 
   the default value is always present.
   </ul>
   
   <P>Example of the second style:
   <PRE>100~Translation=200~Access=300</PRE>
   
   Here, the first entry represents the setting for the default database.
   It also represents the setting for all other databases, unless an override value is 
   provided by one of the name-value pairs. 
   
   <P>The other entries are name=value pairs; the name must be a name of one of the
   non-default databases known to ConnectionSource.
   
   <P>Thus, the database named 'Translation' has a value of 200, different from the default
   value of 100. If the setting instead was 
   
   <PRE>100~Access=300</PRE>
   
   then the value for the Translation database would be 100 - same as the default.
   
   <P>The ~ separator is used instead of a comma, since the DateTime format setting can use a comma internally.
*/
final class DbConfigParser {
  
  /** 
   Constructor.
   Parses a setting into its component parts.
   @param aRawText see class comment.  
  */
  DbConfigParser(String aRawText){
    Args.checkForContent(aRawText);
    fRawText = aRawText;
    parse();
  }

  /**
   Return the value appropriate to the given database. 
   If no value was explicitly set for the given database, then return the value used by 
   the default database. 
   If <tt>aDatabaseName</tt> has no content, then the default value is returned.
   */
  String getValue(String aDatabaseName){
    String result = "";
    if(Util.textHasContent(aDatabaseName)) { 
      result =  fValuesTable.get(aDatabaseName);
      if (! Util.textHasContent(result)) {
        result = getDefaultValue();
      }
    }
    else {
      result =  getDefaultValue();
    }
    return result;
  }
  
  /**
   Return the database names mentioned in this setting.  
   <P>Always includes the default database, represented here as an empty String.
   This method can be used to verify that the names are among the names defined by 
   ConnectionSource. 
  */
  Set<String> getDbNames(){
    return Collections.unmodifiableSet(fValuesTable.keySet());
  }
 
  // PRIVATE 
  private String fRawText;
  private Map<String, String> fValuesTable = new LinkedHashMap<String, String>();
  
  //The default db is defined here as having an empty name; this doesn't necessarily 
  //have to match ConnectionSource.
  private String DEFAULT_DB = "";
  
  /** Return the setting's default value. */
  private String getDefaultValue(){
    return fValuesTable.get(DEFAULT_DB);
  }
  
  private void parse(){
    String DELIM_TILDE = "~";
    String DELIM_EQUALS = "=";
    if(! fRawText.contains(DELIM_TILDE)){
      fValuesTable.put(DEFAULT_DB, fRawText.trim());
    }
    else {
      StringTokenizer parts = new StringTokenizer(fRawText, DELIM_TILDE);
      String firstPart = parts.nextToken().trim();
      if (firstPart.contains(DELIM_EQUALS)) {
        throw new IllegalArgumentException("The first value contains an equals sign. Syntax of setting not correct : " + Util.quote(fRawText)); 
      }
      fValuesTable.put(DEFAULT_DB, firstPart);
      while (parts.hasMoreTokens()) {
        String part = parts.nextToken();
        StringTokenizer nvp = new StringTokenizer(part, DELIM_EQUALS);
        String name = nvp.nextToken();
        String value = nvp.nextToken();
        Args.checkForContent(name);
        Args.checkForContent(value);
        if(fValuesTable.containsKey(name)){
          throw new IllegalArgumentException("Setting has invalid syntax. Have you repeated the name of a database?: " + Util.quote(fRawText));
        }
        fValuesTable.put(name.trim(), value.trim());
      }
    }
    //fLogger.fine("Parsed database setting: " + fValuesTable);
  }
}
