package ucar.nc2.ui;

import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.dataset.VariableEnhanced;

import thredds.ui.*;
import thredds.ui.PopupMenu;

import java.io.*;
import java.io.FileFilter;
import java.util.*;
import java.util.List;

import java.awt.*;

import javax.swing.*;
import javax.swing.filechooser.*;
import javax.swing.border.BevelBorder;
import javax.swing.table.*;

import ucar.util.prefs.PreferencesExt;
import ucar.util.prefs.ui.BeanTableSorted;

/**
 * This puts the data values of a 1D Structure or Sequence into a JTable.
 * The columns are the members of the Structure.
 */
public class StructureTable extends JPanel {
  static private Icon sortDownIcon = BAMutil.getIcon( "/resources/icons/SortDown", false);
  static private Icon sortUpIcon = BAMutil.getIcon( "/resources/icons/SortUp", false);
  static private JLabel downLabel = new JLabel( sortDownIcon);
  static private JLabel upLabel = new JLabel( sortUpIcon);
  static private final String FILECHOOSER_DEFAULTDIR = "DefaultDir";

  private PreferencesExt mainPrefs;
  private HashMap cache = new HashMap();
  private StructureDataModel structureDataModel;
  private TableSorter sortedModel;

  private JTable jtable;
  private PopupMenu popup;
  private FileManager fileChooser; // for exporting
  private TextHistoryPane dumpTA;
  private IndependentWindow dumpWindow;

  public StructureTable(PreferencesExt prefs) {
    this.mainPrefs = prefs;

    jtable = new JTable();
    jtable.setDefaultRenderer(java.util.Date.class, new DateRenderer());
    ToolTipManager.sharedInstance().registerComponent( jtable);

    addActionToPopupMenu("Show", new AbstractAction() {
      public void actionPerformed( java.awt.event.ActionEvent e) {
        showData();
      }
    });

    addActionToPopupMenu("Export", new AbstractAction() {
      public void actionPerformed( java.awt.event.ActionEvent e) {
        export();
      }
    });

    setLayout( new BorderLayout());
    add( new JScrollPane(jtable), BorderLayout.CENTER);

    // other widgets
    dumpTA = new TextHistoryPane( false);
    dumpWindow = new IndependentWindow("Show Data", BAMutil.getImage( "netcdfUI"), dumpTA);
    dumpWindow.setBounds( (Rectangle) prefs.getBean("DumpWindowBounds", new Rectangle( 300, 300, 300, 200)));

    PreferencesExt fcPrefs = (PreferencesExt) prefs.node("FileManager");
    fileChooser = new FileManager(null, null, "csv", "comma seperated values", fcPrefs);
  }

  public void addActionToPopupMenu( String title, AbstractAction act) {
    if (popup == null) popup = new PopupMenu(jtable, "Options");
    popup.addAction( title, act);
  }

  // clear the table
  public void clear() {
    if (structureDataModel != null)
      structureDataModel.clear();
  }

  // save state
  public void saveState() {
    fileChooser.save();
  }

  public void setStructure( Structure s) throws IOException {
    cache = new HashMap();
    StructureTableModel m = new StructureTableModel(s);
    initTable( m);
  }

  public void setStructureData( Structure s, List structureData) throws IOException {
    cache = new HashMap();
    structureDataModel = new StructureDataModel( s, structureData);
    initTable( structureDataModel);
  }

  private void initTable( TableModelWithDesc m) {
    /*  set the header renderers, based on the structure member names
    TableColumnModel colModel = new DefaultTableColumnModel();
    for (int i=0; i<model.getColumnCount(); i++) {
      TableCellRenderer headerRenderer = new SortedHeaderRenderer( i,
            model.getColumnName( i), model.getColumnDesc( i));
      TableColumn tc = new TableColumn(i);
      tc.setHeaderRenderer( headerRenderer);
      colModel.addColumn( tc);
    }
    jtable.setColumnModel( colModel);
    */
    // see "How to use Tables"
    sortedModel = new TableSorter(m);
    jtable.setModel( sortedModel);
    sortedModel.setTableHeader(jtable.getTableHeader());
  }

  private void export() {
    String filename = fileChooser.chooseFilename();
    if (filename == null) return;
    try {
      PrintStream ps = new PrintStream( new FileOutputStream( new File(filename)));

      for (int col=0; col<structureDataModel.getColumnCount(); col++) {
        if (col > 0) ps.print(",");
        ps.print(structureDataModel.getColumnName(col));
      }
      ps.println();

      for (int row=0; row<structureDataModel.getRowCount(); row++) {
        for (int col=0; col<structureDataModel.getColumnCount(); col++) {
          if (col > 0) ps.print(",");
          ps.print(structureDataModel.getValueAt(row, col).toString());
        }
        ps.println();
      }
      ps.close();
      try { JOptionPane.showMessageDialog(this, "File successfully written"); } catch (HeadlessException e) { }
    } catch (IOException ioe) {
      try { JOptionPane.showMessageDialog(this, "ERROR: "+ioe.getMessage()); } catch (HeadlessException e) { }
      ioe.printStackTrace();
    }

  }

  private void showData() {
    StructureData sd = getSelectedStructureData();
    if (sd == null) return;

    ByteArrayOutputStream bos = new ByteArrayOutputStream(10000);
    NCdump.printStructureData(new PrintStream( bos), sd);
    dumpTA.setText( bos.toString());
    dumpWindow.show();
  }

  private StructureData getSelectedStructureData() {
    int viewIdx = jtable.getSelectedRow();
    if (viewIdx < 0) return null;
    int modelIdx = sortedModel.modelIndex( viewIdx);
    try {
      return structureDataModel.getStructureData(modelIdx);
    } catch (InvalidRangeException e) {
      e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
    } catch (IOException e) {
      e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
    }
    return null;
  }

  private abstract class TableModelWithDesc extends AbstractTableModel {
    abstract public String getColumnDesc(int columnIndex);
    abstract public StructureData getStructureData(int row) throws InvalidRangeException, IOException;
  }

  private class StructureTableModel extends TableModelWithDesc {
    private Structure struct;
    private Object[] seqData;
    private List memberNames;

    StructureTableModel( Structure s) {
      this.struct = s;
      this.memberNames = s.getVariableNames();

      if (struct.isUnknownLength()) {
        // for now, we have to read entire sequence into memory !!
        try {
          Array seqDataArray = struct.read();
          seqData = (Object[]) seqDataArray.copyTo1DJavaArray(); // really flat !!
        }
        catch (IOException ex) {
          ex.printStackTrace();
          throw new RuntimeException( ex.getMessage());
        }
      }
    }

    public int getRowCount() {
      if (struct == null) return 0;
      if (struct.isUnknownLength()) return seqData.length;
      return (int) struct.getSize();
    }
    public int getColumnCount() {
      return (struct == null) ? 0 : struct.getVariables().size();
    }

    public String getColumnName(int columnIndex) {
      return (String) memberNames.get(columnIndex);
    }

    public String getColumnDesc(int columnIndex) {
      return "test "+columnIndex;
    }

    public StructureData getStructureData(int row) throws InvalidRangeException, IOException {
       if (seqData != null) { // its a sequence
        return (StructureData) seqData[row];
      }

      StructureData data = (StructureData) cache.get( new Integer( row));
      if (data == null) {
        data = struct.readStructure(row);
        cache.put( new Integer( row), data);
      }

      return data;
    }

    public Object getValueAt(int row, int column) {
      if (seqData != null) { // its a sequence
       StructureData sd = (StructureData) seqData[row];
       Array a = sd.findMemberArray( (String) memberNames.get(column));
       Index ima = a.getIndex();
       return a.getObject(ima); // LOOK a bit expensive ?
     }

      StructureData data = null;
      try {
        data = getStructureData(row);
      } catch (InvalidRangeException ex) {
        return "ERROR "+ex.getMessage();
      } catch (IOException ex) {
        return "ERROR "+ex.getMessage();
      }
      List arrays = data.getMembers();
      StructureData.Member m = (StructureData.Member) arrays.get( column);
      Array a = m.getData();
      Index ima = a.getIndex();
      return a.getObject(ima);
    }
  }

  private class StructureDataModel extends TableModelWithDesc {
    private List structureData;
    private Structure struct;
    private Object[] seqData;
    private List memberNames;

    StructureDataModel( Structure s, List structureData) {
      this.struct = s;
      this.structureData = structureData;
      this.memberNames = s.getVariableNames();
    }

    public int getRowCount() {
      return structureData.size();
    }
    public int getColumnCount() {
      return (struct == null) ? 0 : memberNames.size();
    }

    public String getColumnName(int columnIndex) {
      return (String) memberNames.get(columnIndex);
    }

    public String getColumnDesc(int columnIndex) {
      return "test "+columnIndex;
    }

    public Object getValueAt(int row, int column) {
      StructureData data = (StructureData) structureData.get( row);
      Array a = data.findMemberArray( getColumnName(column));   // LOOK add getMemberArray( int) ??
      //if (getColumnName(column).equals("report_id"))
        //System.out.println("heya");

      if (a instanceof ArrayChar.D1) {
        return ((ArrayChar.D1) a).getString();
      }
      return a.getObject(a.getIndex());
    }

    public StructureData getStructureData(int row) throws InvalidRangeException, IOException {
      return (StructureData) structureData.get( row);
    }

    public void clear() {
      structureData = new ArrayList(); // empty list
      fireTableDataChanged();
    }
  }

  /* private class SequenceTableModel extends TableModelWithDesc {
    private Sequence seq;
    private Structure struct;
    private ArrayObject.D1 data;

    SequenceTableModel( Sequence s) throws IOException {
      this.seq = s;
      this.struct = s.getInternalStructure();
      // for now, we have to read entire sequence into memory !!
      data = (ArrayObject.D1) seq.read();
    }

    public int getRowCount() {
      return (seq == null) ? 0 : (int) data.getSize();
    }
    public int getColumnCount() {
      return (seq == null) ? 0 : struct.getVariables().size();
    }

    public String getColumnName(int columnIndex) {
      return (String) struct.getVariableNames().get( columnIndex);
    }

    public String getColumnDesc(int columnIndex) {
      return "test "+columnIndex;
    }

    public Object getValueAt(int row, int column) {
      StructureData sd = (StructureData) data.get( row);
      List arrays = sd.getMembers();
      StructureData.Member m = (StructureData.Member) arrays.get( column);
      Array a = m.data;
      Index ima = a.getIndex();
      return a.getObject(ima);
    }
  } */


  /* private class SortedHeaderRenderer implements TableCellRenderer {
    private int modelIdx;
    private JPanel compPanel;
    private JLabel headerLabel;
    private boolean hasSortIndicator = false;
    private boolean reverse = false;

    SortedHeaderRenderer(int modelIdx, String header, String tooltip) {
      this.modelIdx = modelIdx;

      // java.beans.PropertyDescriptor pd = model.getProperty(modelIdx);
      //headerLabel = new JLabel(pd.getDisplayName());

      headerLabel = new JLabel(header);
      compPanel = new JPanel(new BorderLayout());
      compPanel.setBorder(new BevelBorder(BevelBorder.RAISED));
      compPanel.add( headerLabel, BorderLayout.CENTER);

      if (null != tooltip)
        compPanel.setToolTipText(tooltip);
      ToolTipManager.sharedInstance().registerComponent(compPanel);
    }

    public Component getTableCellRendererComponent(JTable table, Object value,
        boolean isSelected, boolean hasFocus, int row, int column) {
      // System.out.println("getTableCellRendererComponent= "+headerLabel.getText());
      return compPanel;
    }

    void setSortCol( int sortCol, boolean reverse) {
      //System.out.println("setSortCol on "+modelCol+" "+sortCol+" "+reverse);

      if (sortCol == modelIdx) {

        if (!hasSortIndicator)
          compPanel.add(reverse? upLabel : downLabel, BorderLayout.EAST);
        else if (reverse != this.reverse) {
          compPanel.remove(1);
          compPanel.add(reverse? upLabel : downLabel, BorderLayout.EAST);
        }

        this.reverse = reverse;
        hasSortIndicator = true;

        //System.out.println("setSortCol SET on "+modelCol+" "+sortCol+" "+reverse);
      } else if (hasSortIndicator) {
        compPanel.remove(1);
        hasSortIndicator = false;
        //System.out.println("setSortCol SET off "+modelCol+" "+sortCol+" "+reverse);
      }
    }
  } */

  /** Renderer for Date type */
  static class DateRenderer extends DefaultTableCellRenderer {
    private java.text.SimpleDateFormat newForm, oldForm;
    private Date cutoff;

    DateRenderer() {
      oldForm = new java.text.SimpleDateFormat("yyyy MMM dd HH:mm z");
      oldForm.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
      newForm = new java.text.SimpleDateFormat("MMM dd, HH:mm z");
      newForm.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
      Calendar cal = Calendar.getInstance();
      cal.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
      cal.add(Calendar.YEAR, -1); // "now" time format within a year
      cutoff = cal.getTime();
    }

    public void setValue(Object value) {
      if (value == null)
        setText("");
      else {
        Date date = (Date) value;
        if (date.before(cutoff))
          setText(oldForm.format(date));
        else
          setText(newForm.format(date));
      }
    }
  }

}