//=====================================================================
// File:    FeatureList.java
// Class:   FeatureList
// Package: AFLPcore
//
// Author:  James J. Benham
// Date:    August 10, 1998
// Contact: james_benham@hmc.edu
//
// Genographer v1.6 - Computer assisted scoring of gels.
// Changes Copyright (C) 2001 James J. Benham
// Copyright (C) 1998  Montana State University
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; version 2
// of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// The GNU General Public License is distributed in the file GPL
//=====================================================================

package AFLPcore;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.IOException;
import java.util.Calendar;import java.awt.Frame;

/**
 * This is a static class that provides a list of all of the features in
 * the program. This is done so that features can be added to the program
 * without much effort. For example, a dialog that wants to display a list
 * of scoring functions for bins should query this class to get the list.
 * This way, a new scoring function can be added to the program by simply
 * modifying this class. The class contains several different types of
 * managers for different operations, see the manager descriptions for
 * details on the types of operation supported by each. Features can
 * be added by either directly modifying the class, or by extending it, 
 * overriding the <code>create<i>ManagerType</i></code> calling the super
 * and then adding the new feature. One should make sure that the default is
 * set. 
 *
 * <p>New managers could be added, but the program will not have support
 * for these managers, so the program would have to be modified too.
 *
 * <p>One possible enhancement to this class would be adding methods to scan
 * directories for class files and adding them to the manager. This would
 * mean that new features could be added without recompiling the program.
 * However, this may be difficult if not impossible.
 *
 * Note: many other classes rely on this class to retrieve various components.
 * For example, an import filter may query this class to see which sizing functions are
 * available.
 *
 * @see ImportMgr
 * @see ScoreManager
 * @see PeakMgr
 * @see CutoffMgr
 * @see StandardMgr
 * @see AnalysisMgr
 * @see Mgr
 *
 * @author James J. Benham
 * @version 1.6.0
 * @date September 3, 2001
 */

public class FeatureList
{
  private static String STANDARD_FILE = "standards.cfg";
  private static String OPERATION_FILE = "doc/operation.html";

  private static ImportMgr importMgr;
  private static boolean createdImportMgr = false;

  private static ScoreManager scoreMgr;
  private static boolean createdScoreMgr = false;

  private static PeakMgr peakMgr;
  private static boolean createdPeakMgr = false;

  private static CutoffMgr cutoffMgr;
  private static boolean createdCutoffMgr = false;

  private static SizeMgr sizeMgr;
  private static boolean createdSizeMgr = false;

  private static AnalysisMgr analysisMgr;
  private static boolean createdAnalysisMgr = false;

  private static StandardMgr standardMgr;
  private static boolean createdStandardMgr = false;

  private static GelOpMgr gelOpMgr;
  private static boolean createdGelOpMgr = false;

  private static LaneOpMgr laneOpMgr;
  private static boolean createdLaneOpMgr = false;

  private static BinOpMgr binOpMgr;
  private static boolean createdBinOpMgr = false;

  /**
   * Gives a manager that contains all of the import filters known to the
   * program.
   *
   * @see ImportMgr
   * @see ImportFilter
   */
  public static ImportMgr getImportMgr()
  {
    // This will be executed once every session to initialize the manager.
    // This is not placed in a constructor since this is a static class
    // which does not have a constructor.
    if(!createdImportMgr)
      {
	importMgr = createImportMgr();
	createdImportMgr = true;
      }

    return importMgr;
  }

  /**
   * Gives a manager that contains all of the scoring methods known to the
   * program.
   *
   * @see ScoreManager
   * @see ScoreFunction
   */
  public static ScoreManager getScoreMgr()
  {
    // This will be executed once every session to initialize the manager.
    // This is not placed in a constructor since this is a static class
    // which does not have a constructor.
    if(!createdScoreMgr) {
	scoreMgr = createScoreMgr();
	createdScoreMgr = true;
    }

    return scoreMgr;
  }

  /**
   * Gives a manager that contains all of the peak locating algorithms known 
   * to the program.
   *
   * @see PeakMgr
   * @see PeakLocate
   */
  public static PeakMgr getPeakMgr()
  {
    // This will be executed once every session to initialize the manager.
    // This is not placed in a constructor since this is a static class
    // which does not have a constructor.
    if(!createdPeakMgr) {
	peakMgr = createPeakMgr();
	createdPeakMgr = true;
    }

    return peakMgr;
  }

  /**
   * Gives a manager that contains all of the cutoff functions known to the
   * program.
   *
   * @see CutoffMgr
   * @see CutoffFunction
   */
  public static CutoffMgr getCutoffMgr()
  {
    // This will be executed once every session to initialize the manager.
    // This is not placed in a constructor since this is a static class
    // which does not have a constructor.
    if(!createdCutoffMgr)
      {
     cutoffMgr = createCutoffMgr();
     createdCutoffMgr = true;
      }

    return cutoffMgr;
  }

  /**
   * Gives a manager that contains all of the functions used to size a lane
   * known to the program.
   *
   * @see SizeMgr
   * @see SizeFunction
   */
  public static SizeMgr getSizeMgr()
  {
    // This will be executed once every session to initialize the manager.
    // This is not placed in a constructor since this is a static class
    // which does not have a constructor.
    if(!createdSizeMgr)
      {
        sizeMgr = createSizeMgr();
        createdSizeMgr = true;
      }

    return sizeMgr;
  }

  /**
   * Gives a manager that contains all of the functions used to analyize
   * a gel known to the program.
   *
   * @see AnalysisMgr
   * @see AnalysisOp
   */
  public static AnalysisMgr getAnalysisMgr()
  {
    // This will be executed once every session to initialize the manager.
    // This is not placed in a constructor since this is a static class
    // which does not have a constructor.
    if(!createdAnalysisMgr)
      {
        analysisMgr = createAnalysisMgr();
        createdAnalysisMgr = true;
      }

    return analysisMgr;
  }

  /**
   * Gives a manager that contains all of the operations on a gel known 
   * to the program.
   *
   * @see GelOpMgr
   * @see GelOperation
   */
  public static GelOpMgr getGelOpMgr()
  {
    // This will be executed once every session to initialize the manager.
    // This is not placed in a constructor since this is a static class
    // which does not have a constructor.
    if(!createdGelOpMgr)
      {
        gelOpMgr = createGelOpMgr();
        createdGelOpMgr = true;
      }

    return gelOpMgr;
  }

  /**
   * Gives a manager that contains all of the operations on lanes
   * known to the program.
   *
   * @see LaneOpMgr
   * @see LaneOperation
   */
  public static LaneOpMgr getLaneOpMgr()
  {
    // This will be executed once every session to initialize the manager.
    // This is not placed in a constructor since this is a static class
    // which does not have a constructor.
    if(!createdLaneOpMgr)
      {
        laneOpMgr = createLaneOpMgr();
        createdLaneOpMgr = true;
      }

    return laneOpMgr;
  }

  /**
   * Gives a manager that contains all of the operations on bins
   * known to the program.
   *
   * @see BinOpMgr
   * @see BinOperation
   */
  public static BinOpMgr getBinOpMgr()
  {
    // This will be executed once every session to initialize the manager.
    // This is not placed in a constructor since this is a static class
    // which does not have a constructor.
    if(!createdBinOpMgr)
      {
     	binOpMgr = createBinOpMgr();
     	createdBinOpMgr = true;
      }

    return binOpMgr;
  }

  /**
   * Gives a manager that contains all of the size standard definitions
   * known to the program.
   *
   * @exception IOException occurs if a problem is encountered when
   *    reading from the file that contains the definitions.
   *
   * @see StandardMgr
   * @see SizeStandard
   */
  public static StandardMgr getStandardMgr() throws IOException
  {
    // This will be executed once every session to initialize the manager.
    // This is not placed in a constructor since this is a static class
    // which does not have a constructor.
    if(!createdStandardMgr)
      {
        standardMgr = createStandardMgr();
        createdStandardMgr = true;
      }

    return standardMgr;
  }

  /**
   * Gives a manager that contains all of the filters used to import files.
   * Additional file formats can be supported be adding filters here.
   * This method needs only be called once each time the program is run, and
   * will be run automatically be the <code>getImportMgr</code> method
   * when neccessary.
   *
   * @return the list of filters
   */
  private static ImportMgr createImportMgr()
  {
    ImportFilter newFilter;
    
    // create the manager
    ImportMgr mgr = new ImportMgr();
    
    // create and add the filters
    newFilter = new ABILaneFilter();
    mgr.add(newFilter.getName(), newFilter);
    mgr.setDefault(newFilter.getName());     // set default, but add it first

    newFilter = new ABI3x00Filter();
    mgr.add(newFilter.getName(), newFilter);

    newFilter = new SCFFilter();
    mgr.add(newFilter.getName(), newFilter);

    // Add a filter for CEQ files. 09/15/00 B. Master
    newFilter = new CEQLaneFilter();
    mgr.add(newFilter.getName(), newFilter);
    

    return mgr;
  }

  /**
   * Gives a manager that contains all of the methods used to score
   * a bin. This method needs only be called once each time the program is
   * run, and will be called automatically by <code>getScoreMgr</code> when
   * neccessary.
   * 
   * @return a container for all of the bin scoring functions.
   */
  private static ScoreManager createScoreMgr()
  {
    ScoreFunction newScore;

    // create the manager
    ScoreManager mgr = new ScoreManager();

    // create and add the functions
    newScore = new SegregatingScore();
    mgr.add(newScore.getName(), newScore);
    mgr.setDefault(newScore.getName());      // set default, but add it first
    
    return mgr;
  }

  /**
   * Gives a manager that contains all of the functions used to define
   * a cutoff. This method needs only be called once each time the program is
   * run, and will be called automatically by <code>getCutoffMgr</code> when
   * neccessary.
   * 
   * @return a container for all of the bin scoring functions.
   */
  private static CutoffMgr createCutoffMgr()
  {
    CutoffFunction newCutoff;
    
    // create the manager
    CutoffMgr mgr = new CutoffMgr();

    // create and add the functions
    newCutoff = new LinearCutoff();
    mgr.add(newCutoff.getName(), newCutoff);
    mgr.setDefault(newCutoff.getName());     // set default, do after add

    return mgr;
  }

  /**
   * Gives a manager that contains all of the peak locating algorithms.
   * This method needs only be called once each time the program is
   * run, and will be called automatically by <code>getPeakMgr</code> when
   * neccessary.
   * 
   * @return a container for all of the bin scoring functions.
   */
  private static PeakMgr createPeakMgr()
  {
    PeakLocate newPeak;

    // create the manager
    PeakMgr mgr = new PeakMgr();

    // create and add the location algorithms
    newPeak = new HighestPeakLocate();
    mgr.add(newPeak.getName(), newPeak);
    mgr.setDefault(newPeak.getName());      // set default, but add it first

    return mgr;
  }

  /**
   * Gives a manager that contains all of the functions used to size a
   * lane. This method needs only be called once each time the program is
   * run, and will be called automatically by <code>getAnalysisMgr</code> when
   * neccessary.
   * 
   * @return a container for all of the sizing functions.
   */
  private static SizeMgr createSizeMgr()
  {
    SizeFunction newSize;
    
    // create the manager
    SizeMgr mgr = new SizeMgr();

    // create and add the functions
    newSize = new LocalSouthern();
    mgr.add(newSize.getName(), newSize);
    mgr.setDefault(newSize.getName());     // set default, do after add

    newSize = new NoSize();
    mgr.add(newSize.getName(), newSize);

    return mgr;
  }

  /**
   * Gives a manager that contains all of the functions used to analyize a
   * gel. This method needs only be called once each time the program is
   * run, and will be called automatically by <code>getAnalysisMgr</code> when
   * neccessary.
   * 
   * @return a container for all of the analysis functions
   */
  private static AnalysisMgr createAnalysisMgr()
  {
    AnalysisOp newAnalysis;
    
    // create the manager
    AnalysisMgr mgr = new AnalysisMgr();

    // create and add the functions
    newAnalysis = new BinAnalysis();
    mgr.add(newAnalysis.getName(), newAnalysis);
    mgr.setDefault(newAnalysis.getName());   // set default, do after add
    
    newAnalysis = new MapMakerAnalysis();
    mgr.add(newAnalysis.getName(), newAnalysis);
    
    return mgr;
  }

  /**
   * Gives a manager that contains all of the operations on a gel
   * This method needs only be called once each time the program is
   * run, and will be called automatically by <code>getGelOpMgr</code> when
   * neccessary.
   * 
   * @return a container for all of the gel operations
   */
  private static GelOpMgr createGelOpMgr()
  {
    GelOperation newGelOp;
    
    // create the manager
    GelOpMgr mgr = new GelOpMgr();

    // create and add the functions
    //  ex:
    //  newGelOp = new ClassThatExtendsGelOperation;
    //  mgr.add(newGelOp.getName(), newGelOp);
    //
    //  set the default using:
    //  mgr.setDefault(newGelOp.getName());

    newGelOp = new SignalNorm();
    mgr.add(newGelOp.getName(), newGelOp);

    newGelOp = new PartialSigNorm();
    mgr.add(newGelOp.getName(), newGelOp);

    newGelOp = new RawView();
    mgr.add(newGelOp.getName(), newGelOp);

    //  default is not set
    
    return mgr;
  }

  /**
   * Gives a manager that contains all of the operations on a lane
   * This method needs only be called once each time the program is
   * run, and will be called automatically by <code>getLaneOpMgr</code> when
   * neccessary.
   * 
   * @return a container for all of the lane operations
   */
  private static LaneOpMgr createLaneOpMgr()
  {
    LaneOperation newLaneOp;
    
    // create the manager
    LaneOpMgr mgr = new LaneOpMgr();

    // create and add the functions
    //  ex:
    //  newLaneOp = new ClassThatExtendsLaneOperation;
    //  mgr.add(newLaneOp.getName(), newLaneOp);
    //
    //  set the default using:
    //  mgr.setDefault(newLaneOp.getName());

    //  default is not set
    
    return mgr;
  }

  /**
   * Gives a manager that contains all of the operations on a bin
   * This method needs only be called once each time the program is
   * run, and will be called automatically by <code>getBinOpMgr</code> when
   * neccessary.
   * 
   * @return a container for all of the bin operations
   */
  private static BinOpMgr createBinOpMgr()
  {
    BinOperation newBinOp;
    
    // create the manager
    BinOpMgr mgr = new BinOpMgr();

    // create and add the functions
    //  ex:
    //  newBinOp = new ClassThatExtendsBinOperation;
    //  mgr.add(newBinOp.getName(), newBinOp);
    //
    //  set the default using:
    //  mgr.setDefault(newBinOp.getName());

    //  default is not set
    
    return mgr;
  }

  /**
   * Gives a manager that contains all of the size standard defintions.
   * This method needs only be called once each time the program is
   * run, and will be called automatically by <code>getPeakMgr</code> when
   * neccessary.
   * 
   * @return a container for all of the size standard defintions.
   *
   * @exception IOException occurs when an error is encountered accessing
   *    or reading the file.
   */
  private static StandardMgr createStandardMgr() throws IOException
  {
    SizeStandard newStandard;

    // create the manager
    StandardMgr mgr = new StandardMgr();

    // read in the definitions from the file
    BufferedReader in = new BufferedReader(new FileReader(ProgOptions.homePath
                                     + STANDARD_FILE));

    Double size;

    String line = nextLine(in);
    while(line != null)
      {
     // the first line should be the name
     newStandard = new SizeStandard(line);

     // now read in the values
     line = nextLine(in);
     while( (line != null) && (!line.equals("-1")))
       {
         try
           {
          size = new Double(line);
           }
         catch(NumberFormatException e)
           {
          throw new IOException("Could not read number " + line);
           }

         newStandard.add(size.doubleValue());

         line = nextLine(in);
       }

     // see if we ended on null, if so we ended the file before we should
     // have.
     if(line == null)
       throw new IOException("End of file reached before standard list "+
                    "terminated with -1.");

     // move past the -1
     line = nextLine(in);

     // add the standard to the manager
     mgr.add(newStandard.getName(), newStandard);
      }
    in.close();

    // No default is set. Be careful!

    return mgr;
  }

  /**
   * Gets a line from the specified input stream. It will skip over blank
   * lines and lines beginning with a '#'. Additionally, white space will
   * be trimmed from both ends of the string.
   *
   * @param inStream  the stream to read from
   *
   * @return the line that isn't blank or a comment. <code>null</code>
   * will be returned if the end of the stream is reached.
   *
   * @exception IOException if an I/O problem occurs.
   */
  public static String nextLine(BufferedReader inStream) throws IOException
  {
    char firstCh = 'a';
    String line;

    do
      {

     line = inStream.readLine();
     if(line != null)
       {
         line.trim();
         if(line.length() >= 1)
           firstCh = line.charAt(0);
       }
     // repeat if not the end and the line is blank or a comment
      } while( (line != null) && ((line.length() == 0) || (firstCh == '#')));

    return line;
  }

  public static void generateFileList() throws IOException
  {
    FileWriter fWriter = new FileWriter(ProgOptions.homePath + OPERATION_FILE);
    PrintWriter out = new PrintWriter(new BufferedWriter(fWriter));

    out.println("<html>");
    out.println("<head>");
    out.println("<!-- Generated by Gel Score -->");
    out.println("<title>GelScore Program Features</title>");
    out.println("</head>");
    out.println("<body>");
    out.println("<table width=\"100%\" CELLPADDING=\"0\" CELLSPACING=\"0\">");
    out.println("<tr>");
    out.println("<td width=\"33%\"><a href=\"help.html\">Contents</a></td>");
    out.println("<td width=\"34%\" align=right><a href=\"about.html\"> " +
		"About the Program</a></td>");
    out.println("</tr>");
    out.println("</table>");
    out.println("<hr>");
    out.println();
    out.println("<h1>Program Features</h1>");
    out.println("<hr>");
    out.println("<h2>Import Formats Supported</h2>");
    createManagerList(getImportMgr(), out);
    out.println();
    out.println("<h2>Lane Sizing Formulas</h2>");
    createManagerList(getSizeMgr(), out);
    out.println();
    out.println("<h2>Gel Operations</h2>");
    createManagerList(getGelOpMgr(), out);
    out.println();
    out.println("<h2>Lane Operations</h2>");
    createManagerList(getLaneOpMgr(), out);
    out.println();
    out.println("<h2>Bin Operations</h2>");
    createManagerList(getBinOpMgr(), out);
    out.println();
    out.println("<h2>Bin Scoring Functions</h2>");
    createManagerList(getScoreMgr(), out);
    out.println();
    out.println("<h2>Peak Locating Algorithms</h2>");
    createManagerList(getPeakMgr(), out);
    out.println();
    out.println("<h2>Cutoff Level Functions</h2>");
    createManagerList(getCutoffMgr(), out);
    out.println();
    out.println("<h2>Final Analysis Techniques</h2>");
    createManagerList(getAnalysisMgr(), out);
    out.println();

    out.println("<hr>");
    out.println("<table width=\"100%\" CELLPADDING=\"0\" CELLSPACING=\"0\">");
    out.println("<tr>");
    out.println("<td width=\"33%\"><a href=\"help.html\">Contents</a></td>");
    out.println("<td width=\"34%\" align=right><a href=\"about.html\"> " +
		"About the Program</a></td>");
    out.println("</tr>");
    out.println("</table>");
    out.println("<hr>");

    Calendar date = Calendar.getInstance();
    int minute = date.get(date.MINUTE);
    out.println("<i>");
    out.println("Auto-generated on " + (date.get(date.MONTH) + 1) + "/" + 
		date.get(date.DAY_OF_MONTH) + "/" + date.get(date.YEAR) + 
		" " + date.get(date.HOUR_OF_DAY) + ":" + 
		(minute < 10 ? ("0" + minute) : "" + minute));
    out.println("</i>");
     
    out.println("</body>");
    out.println("</html>");

    out.flush();
    out.close();
  }

  public static void createManagerList(Manager mgr, PrintWriter out)
  {
    String names[];
    Operation op;

    out.println("<dl>");
    names = mgr.getNames();
    for(int i=0; i < names.length; i++)
      {
     out.print("<dt> ");
     op = mgr.get(names[i]);
     if(op.getHelpFile() != null)
       out.println("<a href=" + op.getHelpFile() + ">" + names[i] + "</a>");
     else
       out.println(names[i]);

     out.println("<dd>" + op.getDescription());
     out.println();
      }
    out.println("</dl>");
  }
}

