package hirondelle.stocks.portfolio;
import hirondelle.stocks.quotes.Exchange;
import hirondelle.stocks.quotes.Stock;
import hirondelle.stocks.util.Consts;
import hirondelle.stocks.util.Util;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.InvalidPreferencesFormatException;
import java.util.prefs.Preferences;
public final class PortfolioDAO { 
  
  
  
  
  public void saveAs(Portfolio aPortfolio){
    if( isStored(aPortfolio.getName()) ){
      String message = "Cannot save; name already exists: " + aPortfolio.getName();
      IllegalArgumentException ex = new IllegalArgumentException(message);
      throw ex;
    }
    getPortfoliosRootPref().node( aPortfolio.getName() );
    save(aPortfolio);
  }
  
  
  public void save(Portfolio aPortfolio){
    Preferences portfolioPref = getExistingPortfolioPref(aPortfolio.getName());
    portfolioPref.put(STOCKS_KEY, aPortfolio.getStocks().toString() );
  }
  
  public void saveAsDefault( Portfolio aPortfolio ){
    Preferences rootPref = getPortfoliosRootPref();
    rootPref.put(DEFAULT_PORTFOLIO_NAME_KEY, aPortfolio.getName());
  }
  
  
  public Portfolio fetchDefaultPortfolio(){
    Preferences rootPref = getPortfoliosRootPref();
    String defaultPortfolioName = rootPref.get(
      DEFAULT_PORTFOLIO_NAME_KEY, Consts.EMPTY_STRING
    );
    if ( Util.textHasContent(defaultPortfolioName) ) {
      return fetch ( defaultPortfolioName );
    }
    else {
      return Portfolio.getUntitledPortfolio();
    }
  }
  
  
  public Collection<String> fetchAllPortfolioNames(){
    
    String[] names = null;
    try {
      names = getPortfoliosRootPref().childrenNames();
    }
    catch (BackingStoreException ex) {
      fLogger.log(Level.SEVERE, "Cannot access backing store.", ex);
    }
    return new TreeSet<String>( Arrays.asList(names) );
  }
  
  
  public boolean isValidCandidateName(String aNewName){
    Collection<String> allNames = fetchAllPortfolioNames();
    return ( Util.textHasContent(aNewName) && !allNames.contains(aNewName) );
  }
  
  
  public Portfolio fetch(String aPortfolioName){
    Preferences portfolioPref = getExistingPortfolioPref(aPortfolioName);
    return new Portfolio(aPortfolioName, getStocks(portfolioPref) );
  }
  
  
  public void delete( Portfolio aPortfolio ){
    String defaultPortfolioName = fetchDefaultPortfolio().getName();
    if ( aPortfolio.getName().equals(defaultPortfolioName) ) {
      getPortfoliosRootPref().remove(DEFAULT_PORTFOLIO_NAME_KEY);
    }
    
    Preferences portfolioPref = getExistingPortfolioPref(aPortfolio.getName());
    try {
      portfolioPref.removeNode();
    }
    catch (BackingStoreException ex) {
      fLogger.log(Level.SEVERE, "Cannot access backing store", ex);
    }
  }
  
  public void deleteAll(){
    try {
            getPortfoliosRootPref().removeNode();
    }
    catch ( BackingStoreException ex ) {
      fLogger.log(Level.SEVERE, "Cannot access backing store.", ex);
    }
  }
  
  public void exportXML(File aFile) {
    try {
      OutputStream output = null;
      try { 
        output = new BufferedOutputStream( new FileOutputStream( aFile ) );
        getPortfoliosRootPref().exportSubtree(output);
      }
      finally {
        output.close();
      }
    }
    catch (IOException ex) {
      fLogger.severe("Cannot save to file " + aFile);
    }
    catch(BackingStoreException ex){
      fLogger.log(Level.SEVERE, "Cannot access backing store.", ex);
    }
  }
  
  
  public void importXML(File aFile) {
    try {
      InputStream input = null;
      try {
        input = new BufferedInputStream( new FileInputStream( aFile ) );
        getPortfoliosRootPref().importPreferences(input);
      }
      finally {
       input.close();        
      }
    }
    catch (IOException ex) {
      fLogger.severe("Cannot read file " + aFile);
    }
    catch(InvalidPreferencesFormatException ex){
      fLogger.log(Level.SEVERE, "Format of Preferences file is invalid.", ex);
    }
  }
  
  
                               
  private static final Logger fLogger = Util.getLogger(PortfolioDAO.class);  
  
  private static final String STOCKS_KEY = "stocks"; 
  
  private static final String DEFAULT_PORTFOLIO_NAME_KEY = "DefaultPortfolioName";
  
  
  private static Preferences getPortfoliosRootPref(){
    return Preferences.systemNodeForPackage(PortfolioDAO.class);    
  }
  
  
  private Preferences getExistingPortfolioPref( String aPortfolioName ){
    if ( isStored(aPortfolioName) ) {
      return getPortfoliosRootPref().node( aPortfolioName );
    }
    else {
      throw new IllegalArgumentException("Unknown Portfolio Name:" + aPortfolioName);
    }
  }
  
  private boolean isStored(String aPortfolioName) {
    boolean result = false;
    try {
      result = getPortfoliosRootPref().nodeExists(aPortfolioName);
    }
    catch ( BackingStoreException ex){
      fLogger.severe(
        "Cannot access backing store for Portfolio Name: " + aPortfolioName + 
        " Exception: " + ex
       );
    }
    return result;
  }
  
  private Set<Stock> getStocks( Preferences aPortfolioPref ){
    Collection<Stock> result = new HashSet<Stock>();
    String rawStocks = aPortfolioPref.get(STOCKS_KEY, Consts.EMPTY_STRING);
        String delimiters = "[],";
    StringTokenizer parser = new StringTokenizer( rawStocks, delimiters );
    while ( parser.hasMoreTokens() ) {
      String rawStock = parser.nextToken().trim();
      if ( rawStockHasContent(rawStock) ) {
        Stock stock = getStock(rawStock);
        result.add(stock);
      }
    }
    return new TreeSet<Stock>(result);
  }
  
  private boolean rawStockHasContent( String aRawStock ){
    return 
     aRawStock!=null && 
     aRawStock.trim().length()>0 && 
     !aRawStock.equalsIgnoreCase("null") ;
  }
  
  
  private Stock getStock(String aRawStock){
    Stock result = null;
    String delimiter = ":";
    StringTokenizer parser = new StringTokenizer(aRawStock, delimiter);
    try {
      String ticker = parser.nextToken();
      String name = parser.nextToken();
      Exchange exchange = Exchange.valueFrom( parser.nextToken() );
      Integer quantity = Integer.valueOf( parser.nextToken() );
      BigDecimal avgPrice = new BigDecimal( parser.nextToken() );
      result = new Stock(name, ticker, exchange, quantity, avgPrice);
    }
    catch ( NoSuchElementException ex){
      fLogger.severe("Cannot parse into Stock object: \"" + aRawStock + "\"");
    }
    return result;
  }
  
  
  private static void main(String... args) {
    Exchange nYSEStockExchanges = Exchange.valueFrom("NYSE Stock Exchanges");
    Exchange nasdaqStockExchange = Exchange.valueFrom("Nasdaq Stock Exchange"); 
    Exchange torontoStockExchange = Exchange.valueFrom("Toronto Stock Exchange");
    
    Stock ibm = new Stock(
      "Big Blue", "IBM", nYSEStockExchanges, new Integer(100), new BigDecimal("6.00") 
    ); 
    Stock csco = new Stock(
      "Cisco Systems", "CSCO", nasdaqStockExchange, 
      new Integer(200), new BigDecimal("10.25") 
    ); 
    Stock ctr = new Stock(
      "Canadian Tire", "CTR", torontoStockExchange, 
      new Integer(100), new BigDecimal("5.00") 
    ); 
    Stock mo = new Stock(
      "Philip Morris", "MO", nYSEStockExchanges, 
      new Integer(200), new BigDecimal("3.25") 
    );
    
    Set<Stock> stocks = new TreeSet<Stock>();
    stocks.add(ibm);
    stocks.add(csco) ;
    stocks.add(ctr);
    stocks.add(mo) ;
    Portfolio alaska = new Portfolio("Alaska", stocks);
    PortfolioDAO dao = new PortfolioDAO();
    dao.saveAs( alaska );
    
    Stock pepsi = new Stock(
      "Pepsi", "PEP", nYSEStockExchanges, 
      new Integer(800), new BigDecimal("5.65") 
    );
    stocks.add(pepsi);
    dao.saveAs(new Portfolio("Le Havre", stocks));
    
    dao.saveAsDefault(alaska);
  }
}