package jxl.write.biff;

import common.Assert;
import common.Logger;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
import jxl.BooleanCell;
import jxl.Cell;
import jxl.CellType;
import jxl.CellView;
import jxl.DateCell;
import jxl.HeaderFooter;
import jxl.Hyperlink;
import jxl.Image;
import jxl.LabelCell;
import jxl.NumberCell;
import jxl.Range;
import jxl.Sheet;
import jxl.SheetSettings;
import jxl.WorkbookSettings;
import jxl.biff.CellReferenceHelper;
import jxl.biff.EmptyCell;
import jxl.biff.FormattingRecords;
import jxl.biff.FormulaData;
import jxl.biff.IndexMapping;
import jxl.biff.NumFormatRecordsException;
import jxl.biff.SheetRangeImpl;
import jxl.biff.WorkspaceInformationRecord;
import jxl.biff.XFRecord;
import jxl.biff.drawing.Button;
import jxl.biff.drawing.Chart;
import jxl.biff.drawing.Comment;
import jxl.biff.drawing.Drawing;
import jxl.biff.drawing.DrawingGroupObject;
import jxl.format.CellFormat;
import jxl.format.PageOrientation;
import jxl.format.PaperSize;
import jxl.read.biff.ColumnInfoRecord;
import jxl.read.biff.DataValidation;
import jxl.read.biff.RowRecord;
import jxl.read.biff.SheetImpl;
import jxl.write.Blank;
import jxl.write.Boolean;
import jxl.write.DateTime;
import jxl.write.Label;
import jxl.write.Number;
import jxl.write.WritableCell;
import jxl.write.WritableCellFormat;
import jxl.write.WritableHyperlink;
import jxl.write.WritableImage;
import jxl.write.WritableSheet;
import jxl.write.WritableWorkbook;
import jxl.write.WriteException;

class WritableSheetImpl implements WritableSheet {
  private static Logger logger = Logger.getLogger(WritableSheetImpl.class);
  
  private String name;
  
  private File outputFile;
  
  private RowRecord[] rows;
  
  private FormattingRecords formatRecords;
  
  private SharedStrings sharedStrings;
  
  private TreeSet columnFormats;
  
  private ArrayList hyperlinks;
  
  private MergedCells mergedCells;
  
  private int numRows;
  
  private int numColumns;
  
  private PLSRecord plsRecord;
  
  private ButtonPropertySetRecord buttonPropertySet;
  
  private boolean chartOnly;
  
  private DataValidation dataValidation;
  
  private ArrayList rowBreaks;
  
  private ArrayList drawings;
  
  private ArrayList images;
  
  private boolean drawingsModified;
  
  private SheetSettings settings;
  
  private SheetWriter sheetWriter;
  
  private WorkbookSettings workbookSettings;
  
  private WritableWorkbookImpl workbook;
  
  private static final int rowGrowSize = 10;
  
  private static final int numRowsPerSheet = 65536;
  
  private static final int maxSheetNameLength = 31;
  
  private static final char[] illegalSheetNameCharacters = new char[] { '*', ':', '?', '\\' };
  
  private static final String[] imageTypes = new String[] { "png" };
  
  private static class ColumnInfoComparator implements Comparator {
    private ColumnInfoComparator() {}
    
    public boolean equals(Object o) {
      return (o == this);
    }
    
    public int compare(Object o1, Object o2) {
      if (o1 == o2)
        return 0; 
      Assert.verify(o1 instanceof ColumnInfoRecord);
      Assert.verify(o2 instanceof ColumnInfoRecord);
      ColumnInfoRecord ci1 = (ColumnInfoRecord)o1;
      ColumnInfoRecord ci2 = (ColumnInfoRecord)o2;
      return ci1.getColumn() - ci2.getColumn();
    }
  }
  
  public WritableSheetImpl(String n, File of, FormattingRecords fr, SharedStrings ss, WorkbookSettings ws, WritableWorkbookImpl ww) {
    this.name = validateName(n);
    this.outputFile = of;
    this.rows = new RowRecord[0];
    this.numRows = 0;
    this.numColumns = 0;
    this.chartOnly = false;
    this.workbook = ww;
    this.formatRecords = fr;
    this.sharedStrings = ss;
    this.workbookSettings = ws;
    this.drawingsModified = false;
    this.columnFormats = new TreeSet(new ColumnInfoComparator());
    this.hyperlinks = new ArrayList();
    this.mergedCells = new MergedCells(this);
    this.rowBreaks = new ArrayList();
    this.drawings = new ArrayList();
    this.images = new ArrayList();
    this.settings = new SheetSettings();
    this.sheetWriter = new SheetWriter(this.outputFile, this, this.workbookSettings);
  }
  
  public Cell getCell(String loc) {
    return getCell(CellReferenceHelper.getColumn(loc), CellReferenceHelper.getRow(loc));
  }
  
  public Cell getCell(int column, int row) {
    return (Cell)getWritableCell(column, row);
  }
  
  public WritableCell getWritableCell(String loc) {
    return getWritableCell(CellReferenceHelper.getColumn(loc), CellReferenceHelper.getRow(loc));
  }
  
  public WritableCell getWritableCell(int column, int row) {
    EmptyCell emptyCell;
    WritableCell c = null;
    if (row < this.rows.length && this.rows[row] != null)
      c = this.rows[row].getCell(column); 
    if (c == null)
      emptyCell = new EmptyCell(column, row); 
    return (WritableCell)emptyCell;
  }
  
  public int getRows() {
    return this.numRows;
  }
  
  public int getColumns() {
    return this.numColumns;
  }
  
  public Cell findCell(String contents) {
    Cell cell = null;
    boolean found = false;
    for (int i = 0; i < getRows() && !found; i++) {
      Cell[] row = getRow(i);
      for (int j = 0; j < row.length && !found; j++) {
        if (row[j].getContents().equals(contents)) {
          cell = row[j];
          found = true;
        } 
      } 
    } 
    return cell;
  }
  
  public LabelCell findLabelCell(String contents) {
    LabelCell cell = null;
    boolean found = false;
    for (int i = 0; i < getRows() && !found; i++) {
      Cell[] row = getRow(i);
      for (int j = 0; j < row.length && !found; j++) {
        if ((row[j].getType() == CellType.LABEL || row[j].getType() == CellType.STRING_FORMULA) && row[j].getContents().equals(contents)) {
          cell = (LabelCell)row[j];
          found = true;
        } 
      } 
    } 
    return cell;
  }
  
  public Cell[] getRow(int row) {
    boolean found = false;
    int col = this.numColumns - 1;
    while (col >= 0 && !found) {
      if (getCell(col, row).getType() != CellType.EMPTY) {
        found = true;
        continue;
      } 
      col--;
    } 
    Cell[] cells = new Cell[col + 1];
    for (int i = 0; i <= col; i++)
      cells[i] = getCell(i, row); 
    return cells;
  }
  
  public Cell[] getColumn(int col) {
    boolean found = false;
    int row = this.numRows - 1;
    while (row >= 0 && !found) {
      if (getCell(col, row).getType() != CellType.EMPTY) {
        found = true;
        continue;
      } 
      row--;
    } 
    Cell[] cells = new Cell[row + 1];
    for (int i = 0; i <= row; i++)
      cells[i] = getCell(col, i); 
    return cells;
  }
  
  public String getName() {
    return this.name;
  }
  
  public void insertRow(int row) {
    if (row < 0 || row >= this.numRows)
      return; 
    RowRecord[] oldRows = this.rows;
    if (this.numRows == this.rows.length) {
      this.rows = new RowRecord[oldRows.length + 10];
    } else {
      this.rows = new RowRecord[oldRows.length];
    } 
    System.arraycopy(oldRows, 0, this.rows, 0, row);
    System.arraycopy(oldRows, row, this.rows, row + 1, this.numRows - row);
    for (int i = row + 1; i <= this.numRows; i++) {
      if (this.rows[i] != null)
        this.rows[i].incrementRow(); 
    } 
    HyperlinkRecord hr = null;
    Iterator iterator = this.hyperlinks.iterator();
    while (iterator.hasNext()) {
      hr = iterator.next();
      hr.insertRow(row);
    } 
    if (this.dataValidation != null)
      this.dataValidation.insertRow(row); 
    this.mergedCells.insertRow(row);
    ArrayList newRowBreaks = new ArrayList();
    Iterator ri = this.rowBreaks.iterator();
    while (ri.hasNext()) {
      int val = ((Integer)ri.next()).intValue();
      if (val >= row)
        val++; 
      newRowBreaks.add(new Integer(val));
    } 
    this.rowBreaks = newRowBreaks;
    if (this.workbookSettings.getFormulaAdjust())
      this.workbook.rowInserted(this, row); 
    this.numRows++;
  }
  
  public void insertColumn(int col) {
    if (col < 0 || col >= this.numColumns)
      return; 
    for (int i = 0; i < this.numRows; i++) {
      if (this.rows[i] != null)
        this.rows[i].insertColumn(col); 
    } 
    HyperlinkRecord hr = null;
    Iterator iterator = this.hyperlinks.iterator();
    while (iterator.hasNext()) {
      hr = iterator.next();
      hr.insertColumn(col);
    } 
    iterator = this.columnFormats.iterator();
    while (iterator.hasNext()) {
      ColumnInfoRecord cir = (ColumnInfoRecord)iterator.next();
      if (cir.getColumn() >= col)
        cir.incrementColumn(); 
    } 
    if (this.dataValidation != null)
      this.dataValidation.insertColumn(col); 
    this.mergedCells.insertColumn(col);
    if (this.workbookSettings.getFormulaAdjust())
      this.workbook.columnInserted(this, col); 
    this.numColumns++;
  }
  
  public void removeColumn(int col) {
    if (col < 0 || col >= this.numColumns)
      return; 
    for (int i = 0; i < this.numRows; i++) {
      if (this.rows[i] != null)
        this.rows[i].removeColumn(col); 
    } 
    HyperlinkRecord hr = null;
    Iterator iterator = this.hyperlinks.iterator();
    while (iterator.hasNext()) {
      hr = iterator.next();
      if (hr.getColumn() == col && hr.getLastColumn() == col) {
        this.hyperlinks.remove(this.hyperlinks.indexOf(hr));
        continue;
      } 
      hr.removeColumn(col);
    } 
    if (this.dataValidation != null)
      this.dataValidation.removeColumn(col); 
    this.mergedCells.removeColumn(col);
    iterator = this.columnFormats.iterator();
    ColumnInfoRecord removeColumn = null;
    while (iterator.hasNext()) {
      ColumnInfoRecord cir = (ColumnInfoRecord)iterator.next();
      if (cir.getColumn() == col) {
        removeColumn = cir;
        continue;
      } 
      if (cir.getColumn() > col)
        cir.decrementColumn(); 
    } 
    if (removeColumn != null)
      this.columnFormats.remove(removeColumn); 
    if (this.workbookSettings.getFormulaAdjust())
      this.workbook.columnRemoved(this, col); 
    this.numColumns--;
  }
  
  public void removeRow(int row) {
    if (row < 0 || row >= this.numRows)
      return; 
    RowRecord[] oldRows = this.rows;
    this.rows = new RowRecord[oldRows.length];
    System.arraycopy(oldRows, 0, this.rows, 0, row);
    System.arraycopy(oldRows, row + 1, this.rows, row, this.numRows - row + 1);
    for (int i = row; i < this.numRows; i++) {
      if (this.rows[i] != null)
        this.rows[i].decrementRow(); 
    } 
    HyperlinkRecord hr = null;
    Iterator iterator = this.hyperlinks.iterator();
    while (iterator.hasNext()) {
      hr = iterator.next();
      if (hr.getRow() == row && hr.getLastRow() == row) {
        iterator.remove();
        continue;
      } 
      hr.removeRow(row);
    } 
    if (this.dataValidation != null)
      this.dataValidation.removeRow(row); 
    this.mergedCells.removeRow(row);
    ArrayList newRowBreaks = new ArrayList();
    Iterator ri = this.rowBreaks.iterator();
    while (ri.hasNext()) {
      int val = ((Integer)ri.next()).intValue();
      if (val != row) {
        if (val > row)
          val--; 
        newRowBreaks.add(new Integer(val));
      } 
    } 
    this.rowBreaks = newRowBreaks;
    if (this.workbookSettings.getFormulaAdjust())
      this.workbook.rowRemoved(this, row); 
    this.numRows--;
  }
  
  public void addCell(WritableCell cell) throws WriteException, RowsExceededException {
    if (cell.getType() == CellType.EMPTY)
      if (cell != null && cell.getCellFormat() == null)
        return;  
    CellValue cv = (CellValue)cell;
    if (cv.isReferenced())
      throw new JxlWriteException(JxlWriteException.cellReferenced); 
    int row = cell.getRow();
    RowRecord rowrec = getRowRecord(row);
    rowrec.addCell(cv);
    this.numRows = Math.max(row + 1, this.numRows);
    this.numColumns = Math.max(this.numColumns, rowrec.getMaxColumn());
    cv.setCellDetails(this.formatRecords, this.sharedStrings, this);
  }
  
  private RowRecord getRowRecord(int row) throws RowsExceededException {
    if (row >= 65536)
      throw new RowsExceededException(); 
    if (row >= this.rows.length) {
      RowRecord[] oldRows = this.rows;
      this.rows = new RowRecord[Math.max(oldRows.length + 10, row + 1)];
      System.arraycopy(oldRows, 0, this.rows, 0, oldRows.length);
      oldRows = null;
    } 
    RowRecord rowrec = this.rows[row];
    if (rowrec == null) {
      rowrec = new RowRecord(row);
      this.rows[row] = rowrec;
    } 
    return rowrec;
  }
  
  RowRecord getRowInfo(int r) {
    if (r < 0 || r > this.rows.length)
      return null; 
    return this.rows[r];
  }
  
  ColumnInfoRecord getColumnInfo(int c) {
    Iterator i = this.columnFormats.iterator();
    ColumnInfoRecord cir = null;
    boolean stop = false;
    while (i.hasNext() && !stop) {
      cir = i.next();
      if (cir.getColumn() >= c)
        stop = true; 
    } 
    if (!stop)
      return null; 
    return (cir.getColumn() == c) ? cir : null;
  }
  
  public void setName(String n) {
    this.name = n;
  }
  
  public void setHidden(boolean h) {
    this.settings.setHidden(h);
  }
  
  public void setProtected(boolean prot) {
    this.settings.setProtected(prot);
  }
  
  public void setSelected() {
    this.settings.setSelected();
  }
  
  public boolean isHidden() {
    return this.settings.isHidden();
  }
  
  public void setColumnView(int col, int width) {
    CellView cv = new CellView();
    cv.setSize(width * 256);
    setColumnView(col, cv);
  }
  
  public void setColumnView(int col, int width, CellFormat format) {
    CellView cv = new CellView();
    cv.setSize(width * 256);
    cv.setFormat(format);
    setColumnView(col, cv);
  }
  
  public void setColumnView(int col, CellView view) {
    WritableCellFormat writableCellFormat;
    XFRecord xfr = (XFRecord)view.getFormat();
    if (xfr == null) {
      Styles styles = getWorkbook().getStyles();
      writableCellFormat = styles.getNormalStyle();
    } 
    try {
      if (!writableCellFormat.isInitialized())
        this.formatRecords.addStyle((XFRecord)writableCellFormat); 
      int width = view.depUsed() ? (view.getDimension() * 256) : view.getSize();
      ColumnInfoRecord cir = new ColumnInfoRecord(col, width, (XFRecord)writableCellFormat);
      if (view.isHidden())
        cir.setHidden(true); 
      if (!this.columnFormats.contains(cir)) {
        this.columnFormats.add(cir);
      } else {
        this.columnFormats.remove(cir);
        this.columnFormats.add(cir);
      } 
    } catch (NumFormatRecordsException e) {
      logger.warn("Maximum number of format records exceeded.  Using default format.");
      ColumnInfoRecord cir = new ColumnInfoRecord(col, view.getDimension() * 256, (XFRecord)WritableWorkbook.NORMAL_STYLE);
      if (!this.columnFormats.contains(cir))
        this.columnFormats.add(cir); 
    } 
  }
  
  public void setRowView(int row, int height) throws RowsExceededException {
    setRowView(row, height, false);
  }
  
  public void setRowView(int row, boolean collapsed) throws RowsExceededException {
    RowRecord rowrec = getRowRecord(row);
    rowrec.setCollapsed(collapsed);
  }
  
  public void setRowView(int row, int height, boolean collapsed) throws RowsExceededException {
    RowRecord rowrec = getRowRecord(row);
    rowrec.setRowHeight(height);
    rowrec.setCollapsed(collapsed);
  }
  
  public void write() throws IOException {
    boolean dmod = this.drawingsModified;
    if (this.workbook.getDrawingGroup() != null)
      dmod |= this.workbook.getDrawingGroup().hasDrawingsOmitted(); 
    this.sheetWriter.setWriteData(this.rows, this.rowBreaks, this.hyperlinks, this.mergedCells, this.columnFormats);
    this.sheetWriter.setDimensions(getRows(), getColumns());
    this.sheetWriter.setSettings(this.settings);
    this.sheetWriter.setPLS(this.plsRecord);
    this.sheetWriter.setDrawings(this.drawings, dmod);
    this.sheetWriter.setButtonPropertySet(this.buttonPropertySet);
    this.sheetWriter.setDataValidation(this.dataValidation);
    this.sheetWriter.write();
  }
  
  private void copyCells(Sheet s) {
    int cells = s.getRows();
    Cell[] row = null;
    Cell cell = null;
    for (int i = 0; i < cells; i++) {
      row = s.getRow(i);
      for (int j = 0; j < row.length; j++) {
        cell = row[j];
        CellType ct = cell.getType();
        try {
          if (ct == CellType.LABEL) {
            Label l = new Label((LabelCell)cell);
            addCell((WritableCell)l);
          } else if (ct == CellType.NUMBER) {
            Number n = new Number((NumberCell)cell);
            addCell((WritableCell)n);
          } else if (ct == CellType.DATE) {
            DateTime dt = new DateTime((DateCell)cell);
            addCell((WritableCell)dt);
          } else if (ct == CellType.BOOLEAN) {
            Boolean b = new Boolean((BooleanCell)cell);
            addCell((WritableCell)b);
          } else if (ct == CellType.NUMBER_FORMULA) {
            ReadNumberFormulaRecord fr = new ReadNumberFormulaRecord((FormulaData)cell);
            addCell(fr);
          } else if (ct == CellType.STRING_FORMULA) {
            ReadStringFormulaRecord fr = new ReadStringFormulaRecord((FormulaData)cell);
            addCell(fr);
          } else if (ct == CellType.BOOLEAN_FORMULA) {
            ReadBooleanFormulaRecord fr = new ReadBooleanFormulaRecord((FormulaData)cell);
            addCell(fr);
          } else if (ct == CellType.DATE_FORMULA) {
            ReadDateFormulaRecord fr = new ReadDateFormulaRecord((FormulaData)cell);
            addCell(fr);
          } else if (ct == CellType.FORMULA_ERROR) {
            ReadErrorFormulaRecord fr = new ReadErrorFormulaRecord((FormulaData)cell);
            addCell(fr);
          } else if (ct == CellType.EMPTY) {
            if (cell.getCellFormat() != null) {
              Blank b = new Blank(cell);
              addCell((WritableCell)b);
            } 
          } 
        } catch (WriteException e) {
          Assert.verify(false);
        } 
      } 
    } 
  }
  
  private void copyCells(WritableSheet s) {
    int cells = s.getRows();
    Cell[] row = null;
    Cell cell = null;
    for (int i = 0; i < cells; i++) {
      row = s.getRow(i);
      for (int j = 0; j < row.length; j++) {
        cell = row[j];
        try {
          WritableCell wc = ((WritableCell)cell).copyTo(cell.getColumn(), cell.getRow());
          addCell(wc);
        } catch (WriteException e) {
          Assert.verify(false);
        } 
      } 
    } 
  }
  
  void copy(Sheet s) {
    this.settings = new SheetSettings(s.getSettings());
    copyCells(s);
    SheetImpl si = (SheetImpl)s;
    ColumnInfoRecord[] readCirs = si.getColumnInfos();
    for (int i = 0; i < readCirs.length; i++) {
      ColumnInfoRecord rcir = readCirs[i];
      for (int n = rcir.getStartColumn(); n <= rcir.getEndColumn(); n++) {
        ColumnInfoRecord cir = new ColumnInfoRecord(rcir, n, this.formatRecords);
        cir.setHidden(rcir.getHidden());
        this.columnFormats.add(cir);
      } 
    } 
    Hyperlink[] hls = s.getHyperlinks();
    for (int j = 0; j < hls.length; j++) {
      WritableHyperlink hr = new WritableHyperlink(hls[j], this);
      this.hyperlinks.add(hr);
    } 
    Range[] merged = s.getMergedCells();
    for (int k = 0; k < merged.length; k++)
      this.mergedCells.add((Range)new SheetRangeImpl((SheetRangeImpl)merged[k], (Sheet)this)); 
    try {
      RowRecord[] rowprops = si.getRowProperties();
      for (int n = 0; n < rowprops.length; n++) {
        RowRecord rr = getRowRecord(rowprops[n].getRowNumber());
        XFRecord format = rowprops[n].hasDefaultFormat() ? this.formatRecords.getXFRecord(rowprops[n].getXFIndex()) : null;
        rr.setRowDetails(rowprops[n].getRowHeight(), rowprops[n].matchesDefaultFontHeight(), rowprops[n].isCollapsed(), format);
      } 
    } catch (RowsExceededException e) {
      Assert.verify(false);
    } 
    int[] rowbreaks = si.getRowPageBreaks();
    if (rowbreaks != null)
      for (int n = 0; n < rowbreaks.length; n++)
        this.rowBreaks.add(new Integer(rowbreaks[n]));  
    DataValidation rdv = si.getDataValidation();
    if (rdv != null)
      this.dataValidation = new DataValidation(rdv, this.workbook, this.workbookSettings); 
    this.sheetWriter.setCharts(si.getCharts());
    DrawingGroupObject[] dr = si.getDrawings();
    for (int m = 0; m < dr.length; m++) {
      if (dr[m] instanceof Drawing) {
        WritableImage wi = new WritableImage(dr[m], this.workbook.getDrawingGroup());
        this.drawings.add(wi);
        this.images.add(wi);
      } else if (dr[m] instanceof Comment) {
        Comment c = new Comment(dr[m], this.workbook.getDrawingGroup(), this.workbookSettings);
        this.drawings.add(c);
        CellValue cv = (CellValue)getWritableCell(c.getColumn(), c.getRow());
        Assert.verify((cv.getCellFeatures() != null));
        cv.getWritableCellFeatures().setCommentDrawing(c);
      } else if (dr[m] instanceof Button) {
        Button b = new Button(dr[m], this.workbook.getDrawingGroup(), this.workbookSettings);
        this.drawings.add(b);
      } 
    } 
    this.sheetWriter.setWorkspaceOptions(si.getWorkspaceOptions());
    if (si.getSheetBof().isChart()) {
      this.chartOnly = true;
      this.sheetWriter.setChartOnly();
    } 
    if (si.getPLS() != null)
      if (si.getWorkbookBof().isBiff7()) {
        logger.warn("Cannot copy Biff7 print settings record - ignoring");
      } else {
        this.plsRecord = new PLSRecord(si.getPLS());
      }  
    if (si.getButtonPropertySet() != null)
      this.buttonPropertySet = new ButtonPropertySetRecord(si.getButtonPropertySet()); 
  }
  
  void copy(WritableSheet s) {
    this.settings = new SheetSettings(s.getSettings());
    copyCells(s);
    this.columnFormats = ((WritableSheetImpl)s).columnFormats;
    Range[] merged = s.getMergedCells();
    for (int i = 0; i < merged.length; i++)
      this.mergedCells.add((Range)new SheetRangeImpl((SheetRangeImpl)merged[i], (Sheet)this)); 
    try {
      RowRecord[] copyRows = ((WritableSheetImpl)s).rows;
      RowRecord row = null;
      for (int k = 0; k < copyRows.length; k++) {
        row = copyRows[k];
        if (row != null && (!row.isDefaultHeight() || row.isCollapsed())) {
          RowRecord rr = getRowRecord(k);
          rr.setRowDetails(row.getRowHeight(), row.matchesDefaultFontHeight(), row.isCollapsed(), row.getStyle());
        } 
      } 
    } catch (RowsExceededException e) {
      Assert.verify(false);
    } 
    WritableSheetImpl si = (WritableSheetImpl)s;
    this.rowBreaks = new ArrayList(si.rowBreaks);
    DataValidation rdv = si.dataValidation;
    if (rdv != null)
      this.dataValidation = new DataValidation(rdv, this.workbook, this.workbookSettings); 
    this.sheetWriter.setCharts(si.getCharts());
    DrawingGroupObject[] dr = si.getDrawings();
    for (int j = 0; j < dr.length; j++) {
      if (dr[j] instanceof Drawing) {
        WritableImage wi = new WritableImage(dr[j], this.workbook.getDrawingGroup());
        this.drawings.add(wi);
        this.images.add(wi);
      } 
    } 
    this.sheetWriter.setWorkspaceOptions(si.getWorkspaceOptions());
    if (si.plsRecord != null)
      this.plsRecord = new PLSRecord(si.plsRecord); 
    if (si.buttonPropertySet != null)
      this.buttonPropertySet = new ButtonPropertySetRecord(si.buttonPropertySet); 
  }
  
  final HeaderRecord getHeader() {
    return this.sheetWriter.getHeader();
  }
  
  final FooterRecord getFooter() {
    return this.sheetWriter.getFooter();
  }
  
  public boolean isProtected() {
    return this.settings.isProtected();
  }
  
  public Hyperlink[] getHyperlinks() {
    Hyperlink[] hl = new Hyperlink[this.hyperlinks.size()];
    for (int i = 0; i < this.hyperlinks.size(); i++)
      hl[i] = this.hyperlinks.get(i); 
    return hl;
  }
  
  public Range[] getMergedCells() {
    return this.mergedCells.getMergedCells();
  }
  
  public WritableHyperlink[] getWritableHyperlinks() {
    WritableHyperlink[] hl = new WritableHyperlink[this.hyperlinks.size()];
    for (int i = 0; i < this.hyperlinks.size(); i++)
      hl[i] = this.hyperlinks.get(i); 
    return hl;
  }
  
  public void removeHyperlink(WritableHyperlink h) {
    removeHyperlink(h, false);
  }
  
  public void removeHyperlink(WritableHyperlink h, boolean preserveLabel) {
    this.hyperlinks.remove(this.hyperlinks.indexOf(h));
    if (!preserveLabel) {
      Assert.verify((this.rows.length > h.getRow() && this.rows[h.getRow()] != null));
      this.rows[h.getRow()].removeCell(h.getColumn());
    } 
  }
  
  public void addHyperlink(WritableHyperlink h) throws WriteException, RowsExceededException {
    Cell c = getCell(h.getColumn(), h.getRow());
    String contents = null;
    if (h.isFile() || h.isUNC()) {
      String cnts = h.getContents();
      if (cnts == null) {
        contents = h.getFile().getPath();
      } else {
        contents = cnts;
      } 
    } else if (h.isURL()) {
      String cnts = h.getContents();
      if (cnts == null) {
        contents = h.getURL().toString();
      } else {
        contents = cnts;
      } 
    } else if (h.isLocation()) {
      contents = h.getContents();
    } 
    if (c.getType() == CellType.LABEL) {
      Label l = (Label)c;
      l.setString(contents);
      l.setCellFormat((CellFormat)WritableWorkbook.HYPERLINK_STYLE);
    } else {
      Label l = new Label(h.getColumn(), h.getRow(), contents, (CellFormat)WritableWorkbook.HYPERLINK_STYLE);
      addCell((WritableCell)l);
    } 
    for (int i = h.getRow(); i <= h.getLastRow(); i++) {
      for (int j = h.getColumn(); j <= h.getLastColumn(); j++) {
        if (i != h.getRow() && j != h.getColumn())
          if (this.rows[i] != null)
            this.rows[i].removeCell(j);  
      } 
    } 
    h.initialize(this);
    this.hyperlinks.add(h);
  }
  
  public Range mergeCells(int col1, int row1, int col2, int row2) throws WriteException, RowsExceededException {
    if (col2 < col1 || row2 < row1)
      logger.warn("Cannot merge cells - top left and bottom right incorrectly specified"); 
    if (col2 >= this.numColumns || row2 >= this.numRows)
      addCell((WritableCell)new Blank(col2, row2)); 
    SheetRangeImpl range = new SheetRangeImpl((Sheet)this, col1, row1, col2, row2);
    this.mergedCells.add((Range)range);
    return (Range)range;
  }
  
  public void unmergeCells(Range r) {
    this.mergedCells.unmergeCells(r);
  }
  
  public void setHeader(String l, String c, String r) {
    HeaderFooter header = new HeaderFooter();
    header.getLeft().append(l);
    header.getCentre().append(c);
    header.getRight().append(r);
    this.settings.setHeader(header);
  }
  
  public void setFooter(String l, String c, String r) {
    HeaderFooter footer = new HeaderFooter();
    footer.getLeft().append(l);
    footer.getCentre().append(c);
    footer.getRight().append(r);
    this.settings.setFooter(footer);
  }
  
  public void setPageSetup(PageOrientation p) {
    this.settings.setOrientation(p);
  }
  
  public void setPageSetup(PageOrientation p, double hm, double fm) {
    this.settings.setOrientation(p);
    this.settings.setHeaderMargin(hm);
    this.settings.setFooterMargin(fm);
  }
  
  public void setPageSetup(PageOrientation p, PaperSize ps, double hm, double fm) {
    this.settings.setPaperSize(ps);
    this.settings.setOrientation(p);
    this.settings.setHeaderMargin(hm);
    this.settings.setFooterMargin(fm);
  }
  
  public SheetSettings getSettings() {
    return this.settings;
  }
  
  WorkbookSettings getWorkbookSettings() {
    return this.workbookSettings;
  }
  
  public void addRowPageBreak(int row) {
    Iterator i = this.rowBreaks.iterator();
    boolean found = false;
    while (i.hasNext() && !found) {
      if (((Integer)i.next()).intValue() == row)
        found = true; 
    } 
    if (!found)
      this.rowBreaks.add(new Integer(row)); 
  }
  
  private Chart[] getCharts() {
    return this.sheetWriter.getCharts();
  }
  
  private DrawingGroupObject[] getDrawings() {
    DrawingGroupObject[] dr = new DrawingGroupObject[this.drawings.size()];
    dr = (DrawingGroupObject[])this.drawings.toArray((Object[])dr);
    return dr;
  }
  
  void checkMergedBorders() {
    this.sheetWriter.setWriteData(this.rows, this.rowBreaks, this.hyperlinks, this.mergedCells, this.columnFormats);
    this.sheetWriter.setDimensions(getRows(), getColumns());
    this.sheetWriter.checkMergedBorders();
  }
  
  private WorkspaceInformationRecord getWorkspaceOptions() {
    return this.sheetWriter.getWorkspaceOptions();
  }
  
  void rationalize(IndexMapping xfMapping, IndexMapping fontMapping, IndexMapping formatMapping) {
    for (Iterator iterator = this.columnFormats.iterator(); iterator.hasNext(); ) {
      ColumnInfoRecord cir = iterator.next();
      cir.rationalize(xfMapping);
    } 
    for (int i = 0; i < this.rows.length; i++) {
      if (this.rows[i] != null)
        this.rows[i].rationalize(xfMapping); 
    } 
    Chart[] charts = getCharts();
    for (int c = 0; c < charts.length; c++)
      charts[c].rationalize(xfMapping, fontMapping, formatMapping); 
  }
  
  WritableWorkbookImpl getWorkbook() {
    return this.workbook;
  }
  
  public CellFormat getColumnFormat(int col) {
    return getColumnView(col).getFormat();
  }
  
  public int getColumnWidth(int col) {
    return getColumnView(col).getDimension();
  }
  
  public int getRowHeight(int row) {
    return getRowView(row).getDimension();
  }
  
  boolean isChartOnly() {
    return this.chartOnly;
  }
  
  public CellView getRowView(int row) {
    CellView cv = new CellView();
    try {
      RowRecord rr = getRowRecord(row);
      if (rr == null || rr.isDefaultHeight()) {
        cv.setDimension(this.settings.getDefaultRowHeight());
        cv.setSize(this.settings.getDefaultRowHeight());
      } else if (rr.isCollapsed()) {
        cv.setHidden(true);
      } else {
        cv.setDimension(rr.getRowHeight());
        cv.setSize(rr.getRowHeight());
      } 
      return cv;
    } catch (RowsExceededException e) {
      cv.setDimension(this.settings.getDefaultRowHeight());
      cv.setSize(this.settings.getDefaultRowHeight());
      return cv;
    } 
  }
  
  public CellView getColumnView(int col) {
    ColumnInfoRecord cir = getColumnInfo(col);
    CellView cv = new CellView();
    if (cir != null) {
      cv.setDimension(cir.getWidth() / 256);
      cv.setSize(cir.getWidth());
      cv.setHidden(cir.getHidden());
      cv.setFormat((CellFormat)cir.getCellFormat());
    } else {
      cv.setDimension(this.settings.getDefaultColumnWidth() / 256);
      cv.setSize(this.settings.getDefaultColumnWidth());
    } 
    return cv;
  }
  
  public void addImage(WritableImage image) {
    boolean supported = false;
    File imageFile = image.getImageFile();
    String fileType = "?";
    if (imageFile != null) {
      String fileName = imageFile.getName();
      int fileTypeIndex = fileName.lastIndexOf('.');
      fileType = (fileTypeIndex != -1) ? fileName.substring(fileTypeIndex + 1) : "";
      for (int i = 0; i < imageTypes.length && !supported; i++) {
        if (fileType.equalsIgnoreCase(imageTypes[i]))
          supported = true; 
      } 
    } else {
      supported = true;
    } 
    if (supported) {
      this.workbook.addDrawing((DrawingGroupObject)image);
      this.drawings.add(image);
      this.images.add(image);
    } else {
      StringBuffer message = new StringBuffer("Image type ");
      message.append(fileType);
      message.append(" not supported.  Supported types are ");
      message.append(imageTypes[0]);
      for (int i = 1; i < imageTypes.length; i++) {
        message.append(", ");
        message.append(imageTypes[i]);
      } 
      logger.warn(message.toString());
    } 
  }
  
  public int getNumberOfImages() {
    return this.images.size();
  }
  
  public WritableImage getImage(int i) {
    return this.images.get(i);
  }
  
  public Image getDrawing(int i) {
    return this.images.get(i);
  }
  
  public void removeImage(WritableImage wi) {
    this.drawings.remove(wi);
    this.images.remove(wi);
    this.drawingsModified = true;
    this.workbook.removeDrawing((Drawing)wi);
  }
  
  private String validateName(String n) {
    if (n.length() > 31) {
      logger.warn("Sheet name " + n + " too long - truncating");
      n = n.substring(0, 31);
    } 
    if (n.charAt(0) == '\'') {
      logger.warn("Sheet naming cannot start with ' - removing");
      n = n.substring(1);
    } 
    for (int i = 0; i < illegalSheetNameCharacters.length; i++) {
      String newname = n.replace(illegalSheetNameCharacters[i], '@');
      if (n != newname)
        logger.warn(illegalSheetNameCharacters[i] + " is not a valid character within a sheet name - replacing"); 
      n = newname;
    } 
    return n;
  }
  
  void addDrawing(DrawingGroupObject o) {
    this.drawings.add(o);
    Assert.verify(!(o instanceof Drawing));
  }
  
  void removeDrawing(DrawingGroupObject o) {
    int origSize = this.drawings.size();
    this.drawings.remove(o);
    int newSize = this.drawings.size();
    this.drawingsModified = true;
    Assert.verify((newSize == origSize - 1));
  }
  
  public int[] getRowPageBreaks() {
    int[] rb = new int[this.rowBreaks.size()];
    int pos = 0;
    for (Iterator i = this.rowBreaks.iterator(); i.hasNext(); pos++)
      rb[pos] = ((Integer)i.next()).intValue(); 
    return rb;
  }
}
