/*
 * Copyright (c) 2003, 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.log;

import java.io.*;
import java.text.*;
import java.util.*;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;

import org.knopflerfish.service.console.CommandGroupAdapter;
import org.knopflerfish.service.console.Session;
import org.knopflerfish.service.console.Util;
import org.knopflerfish.service.log.LogUtil;

/**
 ** Command group for console configuration of the Log.
 **
 ** @author  Gatespace AB
 ** @version $Revision: 1.1.1.1 $
 **/
public class LogConfigCommandGroup extends CommandGroupAdapter {

    private LogConfig configuration;
    private BundleContext bc;
 
    LogConfigCommandGroup(BundleContext bc,
			  LogConfig lc )
    {
	super("logconfig", "Configuration commands for the log.");
	this.configuration= lc;
	this.bc = bc;
    }

  //
  // Set memory size command
  //
  public final static String USAGE_MEMORY = "[<int>]";
  public final static String [] HELP_MEMORY = new String [] {
    "Number of log entries to keep in memory.",
    "The no argument version prints the current setting.",
    "<int>   The new number of log entries to keep.",
  };

  public int cmdMemory( Dictionary opts,
			Reader in,
			PrintWriter out,
			Session session)
  {
    String value = (String)opts.get("int");
    if (value!=null) {
      try {
	configuration.setMemorySize(Integer.parseInt(value));
      } catch (NumberFormatException nfe) {
	out.println("Can not set log memory size (" + nfe + ").");
      }
    } else {
      out.println("  log memory size: " + configuration.getMemSize() );
    }
    return 0;
  }


  //
  // Set level command
  //
  public final static String USAGE_SETLEVEL = "<level> [<bundle>] ...";
  public final static String [] HELP_SETLEVEL = new String [] {
    "Set log level",
    "<level>   The new log level (one of error,warning,info,debug or default)",
    "<bundle>  The bundle(s) that the new level applies to. If no bundles are",
    "          given the default level is changed. The bundle may be given as",
    "          the bundle id, the file location of the bundle or the bundle's",
    "          short-name. If the bundle's short-name is given then the default",
    "          configuration for all bundles with the given short-name will be set.",
    "          This means that if wanting to set the configuration of a specific",
    "          bundle the bundle id or the bundle location has to be given. ",
  };

  public int cmdSetlevel( Dictionary opts,
			  Reader in,
			  PrintWriter out,
			  Session session)
  {
    String l = (String)opts.get("level");
    int level= LogUtil.toLevel((l.trim()), -1);
    if (level==-1) {
      out.println("Unknown level: " + l);
      return 1;
    }
    String[] selection = (String[])opts.get("bundle");
    if (selection!=null) {
      setValidBundles(selection, level);
      configuration.commit();
    } else {
	configuration.setFilter( level );
    }
    return 0;
  }

  private void setValidBundles(String[] givenBundles, int level){
    Bundle [] bundles = bc.getBundles();
    String location=null;
    for(int i= givenBundles.length-1; i>=0 ; i--){
      location=givenBundles[i].trim();
      try{
	long id=Long.parseLong(location);
	location= (bc.getBundle(id)!=null) 
	  ? (bc.getBundle(id)).getLocation() 
	  : null;
      }catch(NumberFormatException nfe){}
      if(location!=null && location.length()>0){
	configuration.setFilter(location, level);
      }
    }
  }

  //
  // Show level command
  //
  public final static String USAGE_SHOWLEVEL = "[<bundle>] ...";
  public final static String [] HELP_SHOWLEVEL = new String [] {
    "Show current log levels.",
    "All existing default configurations are marked with (default).",
    "Bundles not yet installed, which are not a default configuration,",
    "are given with their full path to differentiate the bundles. ",
    "<bundle>     Show level for the specified bundles only. The bundle",
    "may be given as the bundle id, bundle's short-name, or the bundle ",
    "location. If the bundle's short-name is supplied then all bundles ",
    "configured with that name will be shown.",
  };

  public int cmdShowlevel( Dictionary opts,
			   Reader in,
			   PrintWriter out,
			   Session session){
    String[] selections = (String[])opts.get("bundle");
    if(selections == null){
      HashMap filters = configuration.getFilters();
      selections= (String[])
	((filters).keySet()).toArray(new String[filters.size()]);
    }    
    // Print the default filter level.
    out.println( "    *  " + LogUtil.fromLevel(configuration.getFilter(), 8)
		 + "(default)" );
    printValidBundles(selections, out);
    return 0;
  }

  private void printValidBundles(String[] selection, PrintWriter out){
    String ref= null, short_name, full_name, comparable_name, name;
    Bundle[] bundles= null;
    Bundle bundle;
    long id=0;
    HashSet names= new HashSet();
    for(int i=selection.length-1; i>=0; i--){
      ref= selection[i].trim();
      if(ref.length()>0){
	try{
	  id= Long.parseLong(ref);
	  bundle= bc.getBundle(id);
	  if(bundle!=null){
	    full_name= bundle.getLocation();
	    short_name= Util.shortName(bundle);
	    if(!names.contains(full_name)){
	      out.println(Util.showId(bundle) + " " + 
		LogUtil.fromLevel(getLevel(full_name, 
					   short_name+".jar") , 8) + 
			  short_name );
	      names.add(full_name);
	    }
	  }
	}catch(NumberFormatException nfe){
	  id= -1;
	  comparable_name= configuration.getCommonLocation(ref);
	  bundles= bc.getBundles();
	  for(int j=bundles.length-1; j>=0; j--){
	    short_name=Util.shortName(bundles[j]);
	    full_name= bundles[j].getLocation();
	    if( (full_name).equals(comparable_name) &&
		(!names.contains(full_name)) ){
	      out.println(Util.showId(bundles[j]) 
			  + " " + 
			  LogUtil.fromLevel(getLevel(full_name, short_name
						     +".jar") , 8) 
			  + short_name );
	      names.add(full_name);
	      break;
	    }
	    else if( (short_name+".jar").equals(comparable_name) &&
		     !names.contains(full_name) ){
	      out.println(Util.showId(bundles[j]) 
			  + " " + 
			  LogUtil.fromLevel(getLevel(full_name, short_name
						     +".jar") , 8) 
			  + short_name );
	      names.add(full_name);
	    }
	  }
	  HashMap filters = configuration.getFilters();
	  //If a full path is given	
	  if( ((comparable_name.lastIndexOf("/")!=-1 
		|| comparable_name.lastIndexOf("\\")!=-1)) 
	      && filters.containsKey(comparable_name) 
	      && !names.contains(comparable_name) ){
	    out.println("    -  " + LogUtil.fromLevel(getLevel(comparable_name, 
							       "") , 8) 
			+ getFullName(comparable_name) 
			+ " (Bundle not yet installed)");
	    names.add(comparable_name);
	  }
	  //A short name is supplied
	  else{
	    if(filters.containsKey(comparable_name) &&
	       !names.contains(comparable_name)){
	      out.println("    -  " + LogUtil.fromLevel(getLevel("", 
							comparable_name) , 8) 
			  + getShortName(comparable_name) + " (default)" );
	      names.add(comparable_name);
	    }
	    for(Iterator it=filters.keySet().iterator(); it.hasNext();){
	      name= (String)it.next();
	      if(name.endsWith(comparable_name) && 
		 !names.contains(name) ){
		out.println("    -  " + LogUtil.fromLevel(getLevel
							  (name, "") , 8) 
			    + getFullName(name) + 
			    " (Bundle not yet installed)"  );
		names.add(name);
	      }
	    }
	  }
	}
      }
    }
  }
   
  private int getLevel(String full_name, String short_name){
    HashMap filters = configuration.getFilters();
    Integer level_to_use= (Integer)filters.get(full_name);
    if(level_to_use==null){
      level_to_use= (Integer)filters.get(short_name);
    } 
    return (level_to_use!=null) 
      ? level_to_use.intValue() 
      : configuration.getFilter() ;
  }

  private String getFullName(String bundle){
    return fillName(new StringBuffer(bundle), 30);
  }

  private String getShortName(String bundle){
    return fillName(new StringBuffer(bundle.substring
				     (0, bundle.indexOf(".jar"))) , 17);
  }

  private String fillName(StringBuffer sb, int length){
    while(sb.length()<length){ sb.append(' '); }
    return sb.toString();
  }

  //
  // Set out command
  //
  public final static String USAGE_OUT = "[-on | -off]";
  public final static String [] HELP_OUT = new String [] {
    "Configures logging to standard out",
    "-on          Turns on writing of log entries to standard out.",
    "-off         Turns off writing of log entries to standard out.",
  };

  public int cmdOut( Dictionary opts,
		     Reader in,
		     PrintWriter out,
		     Session session)
  {
      if(!configuration.DEFAULT_CONFIG){
      out.println("  This command is no persistent. (No valid configuration has been received)" );
      }
      
      boolean optionFound = false;
      // System.out logging on/off
      if (opts.get("-on")!=null) {
	optionFound = true;
	configuration.setOut(true);
      } else if (opts.get("-off")!=null) {
	optionFound = true;
	configuration.setOut(false);
      }
      // Show current config
      if (!optionFound) {
	boolean isOn = configuration.getOut();
	out.println("  Logging to standard out is "
		    + (isOn ? "on" : "off") +"." );
      }
      return 0;
  }


  //
  // Set file command
  //
  public final static String USAGE_FILE
    = "[-on | -off] [-size #size#] [-gen #gen#] [-flush | -noflush]";
  public final static String [] HELP_FILE = new String [] {
    "Configures the file logging (the no argument version prints the current settings)",
    "-on          Turns on writing of log entries to file.",
    "-off         Turns off writing of log entries to file.",
    "-size #size# Set the maximum size of one log file (characters).",
    "-gen #gen#   Set the number of log file generations that are kept.",
    "-flush       Turns on log file flushing after each log entry.",
    "-noflush     Turns off log file flushing after each log entry.",
  };

  public int cmdFile( Dictionary opts,
		      Reader in,
		      PrintWriter out,
		      Session session)
  {
    if(!configuration.DEFAULT_CONFIG){
      out.println("  This command is not persistent. "+
		  "(No valid configuration has been received)" );
    }
    
    if(configuration.getDir() != null) {
      boolean optionFound = false;
      // File logging on/off
      if (opts.get("-on")!=null) {
	optionFound = true;
	configuration.setFile(true);
      } else if (opts.get("-off")!=null) {
	optionFound = true;
	configuration.setFile(false);
      }
      // Flush
      if (opts.get("-flush")!=null) {
	optionFound = true;
	if (!configuration.getFile()) {
	  out.println("Cannot activate flush (file logging disabled).");
	} else {
	  configuration.setFlush(true);
	}
      } else if (opts.get("-noflush")!=null) {
	optionFound = true;
	if (!configuration.getFile()) {
	  out.println("Cannot deactivate flush (file logging disabled).");
	} else {
	  configuration.setFlush(false);
	}
      }
      // Log size
      String value = (String)opts.get("-size");
      if (value!=null) {
	optionFound = true;
	if (!configuration.getFile()) {
	  out.println("Cannot set log size (file logging disabled).");
	} else {
	  try {
	    configuration.setFileSize(Integer.parseInt(value));
	  } catch (NumberFormatException nfe1) {
	    out.println("Cannot set log size (" + nfe1 + ").");
	  }
	}
      }
      // Log generations
      value = (String)opts.get("-gen");
      if (value!=null) {
	optionFound = true;
	if (!configuration.getFile()) {
	  out.println("Cannot set generation count (file logging disabled).");
	} else {
	  try {
	    configuration.setMaxGen(Integer.parseInt(value));
	  } catch (NumberFormatException nfe2) {
	    out.println("Cannot set generation count (" + nfe2 + ").");
	  }
	}
      }
      //Update configuration only once
      if(optionFound)
	configuration.commit();
      // Show current config
      if (!optionFound) {
	boolean isOn = configuration.getFile();
	out.println("  file logging is "+ (isOn ? "on" : "off") +"." );
	if (isOn) {
	  out.println("  file size:    " + configuration.getFileSize() );
	  out.println("  generations:  " + configuration.getMaxGen() );
	  out.println("  flush:        " + configuration.getFlush() );
	  out.println("  log location: " + configuration.getDir() );
	}
      }
      return 0;
    }
    
    out.println(" This command is disabled. "+
		"(No filesystem support is available. )");
    return 0;
  }
}
