package jxl.write.biff;

import common.Assert;
import common.Logger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeSet;
import jxl.Cell;
import jxl.Range;
import jxl.SheetSettings;
import jxl.WorkbookSettings;
import jxl.biff.ByteData;
import jxl.biff.WorkspaceInformationRecord;
import jxl.biff.XFRecord;
import jxl.biff.drawing.Chart;
import jxl.biff.drawing.SheetDrawingWriter;
import jxl.format.Border;
import jxl.format.BorderLineStyle;
import jxl.format.CellFormat;
import jxl.format.Colour;
import jxl.write.Blank;
import jxl.write.WritableCell;
import jxl.write.WritableCellFormat;
import jxl.write.WritableHyperlink;
import jxl.write.WriteException;

final class SheetWriter {
  private static Logger logger = Logger.getLogger(SheetWriter.class);
  
  private File outputFile;
  
  private RowRecord[] rows;
  
  private int numRows;
  
  private int numCols;
  
  private HeaderRecord header;
  
  private FooterRecord footer;
  
  private SheetSettings settings;
  
  private WorkbookSettings workbookSettings;
  
  private ArrayList rowBreaks;
  
  private ArrayList hyperlinks;
  
  private DataValidation dataValidation;
  
  private MergedCells mergedCells;
  
  private PLSRecord plsRecord;
  
  private ButtonPropertySetRecord buttonPropertySet;
  
  private WorkspaceInformationRecord workspaceOptions;
  
  private TreeSet columnFormats;
  
  private SheetDrawingWriter drawingWriter;
  
  private boolean chartOnly;
  
  private WritableSheetImpl sheet;
  
  public SheetWriter(File of, WritableSheetImpl wsi, WorkbookSettings ws) {
    this.outputFile = of;
    this.sheet = wsi;
    this.workspaceOptions = new WorkspaceInformationRecord();
    this.workbookSettings = ws;
    this.chartOnly = false;
    this.drawingWriter = new SheetDrawingWriter(ws);
  }
  
  public void write() throws IOException {
    Assert.verify((this.rows != null));
    if (this.chartOnly) {
      this.drawingWriter.write(this.outputFile);
      return;
    } 
    BOFRecord bof = new BOFRecord(BOFRecord.sheet);
    this.outputFile.write((ByteData)bof);
    int numBlocks = this.numRows / 32;
    if (this.numRows - numBlocks * 32 != 0)
      numBlocks++; 
    int indexPos = this.outputFile.getPos();
    IndexRecord indexRecord = new IndexRecord(0, this.numRows, numBlocks);
    this.outputFile.write((ByteData)indexRecord);
    if (this.settings.getAutomaticFormulaCalculation()) {
      CalcModeRecord cmr = new CalcModeRecord(CalcModeRecord.automatic);
      this.outputFile.write((ByteData)cmr);
    } else {
      CalcModeRecord cmr = new CalcModeRecord(CalcModeRecord.manual);
      this.outputFile.write((ByteData)cmr);
    } 
    CalcCountRecord ccr = new CalcCountRecord(100);
    this.outputFile.write((ByteData)ccr);
    RefModeRecord rmr = new RefModeRecord();
    this.outputFile.write((ByteData)rmr);
    IterationRecord itr = new IterationRecord(false);
    this.outputFile.write((ByteData)itr);
    DeltaRecord dtr = new DeltaRecord(0.001D);
    this.outputFile.write((ByteData)dtr);
    SaveRecalcRecord srr = new SaveRecalcRecord(this.settings.getRecalculateFormulasBeforeSave());
    this.outputFile.write((ByteData)srr);
    PrintHeadersRecord phr = new PrintHeadersRecord(this.settings.getPrintHeaders());
    this.outputFile.write((ByteData)phr);
    PrintGridLinesRecord pglr = new PrintGridLinesRecord(this.settings.getPrintGridLines());
    this.outputFile.write((ByteData)pglr);
    GridSetRecord gsr = new GridSetRecord(true);
    this.outputFile.write((ByteData)gsr);
    GuttersRecord gutr = new GuttersRecord();
    this.outputFile.write((ByteData)gutr);
    DefaultRowHeightRecord drhr = new DefaultRowHeightRecord(this.settings.getDefaultRowHeight(), (this.settings.getDefaultRowHeight() != 255));
    this.outputFile.write((ByteData)drhr);
    this.workspaceOptions.setFitToPages(this.settings.getFitToPages());
    this.outputFile.write((ByteData)this.workspaceOptions);
    if (this.rowBreaks.size() > 0) {
      int[] rb = new int[this.rowBreaks.size()];
      for (int i = 0; i < rb.length; i++)
        rb[i] = ((Integer)this.rowBreaks.get(i)).intValue(); 
      HorizontalPageBreaksRecord hpbr = new HorizontalPageBreaksRecord(rb);
      this.outputFile.write((ByteData)hpbr);
    } 
    HeaderRecord header = new HeaderRecord(this.settings.getHeader().toString());
    this.outputFile.write((ByteData)header);
    FooterRecord footer = new FooterRecord(this.settings.getFooter().toString());
    this.outputFile.write((ByteData)footer);
    HorizontalCentreRecord hcr = new HorizontalCentreRecord(this.settings.isHorizontalCentre());
    this.outputFile.write((ByteData)hcr);
    VerticalCentreRecord vcr = new VerticalCentreRecord(this.settings.isVerticalCentre());
    this.outputFile.write((ByteData)vcr);
    if (this.settings.getLeftMargin() != this.settings.getDefaultWidthMargin()) {
      MarginRecord mr = new LeftMarginRecord(this.settings.getLeftMargin());
      this.outputFile.write((ByteData)mr);
    } 
    if (this.settings.getRightMargin() != this.settings.getDefaultWidthMargin()) {
      MarginRecord mr = new RightMarginRecord(this.settings.getRightMargin());
      this.outputFile.write((ByteData)mr);
    } 
    if (this.settings.getTopMargin() != this.settings.getDefaultHeightMargin()) {
      MarginRecord mr = new TopMarginRecord(this.settings.getTopMargin());
      this.outputFile.write((ByteData)mr);
    } 
    if (this.settings.getBottomMargin() != this.settings.getDefaultHeightMargin()) {
      MarginRecord mr = new BottomMarginRecord(this.settings.getBottomMargin());
      this.outputFile.write((ByteData)mr);
    } 
    if (this.plsRecord != null)
      this.outputFile.write((ByteData)this.plsRecord); 
    SetupRecord setup = new SetupRecord(this.settings);
    this.outputFile.write((ByteData)setup);
    if (this.settings.isProtected()) {
      ProtectRecord pr = new ProtectRecord(this.settings.isProtected());
      this.outputFile.write((ByteData)pr);
      ScenarioProtectRecord spr = new ScenarioProtectRecord(this.settings.isProtected());
      this.outputFile.write((ByteData)spr);
      ObjectProtectRecord opr = new ObjectProtectRecord(this.settings.isProtected());
      this.outputFile.write((ByteData)opr);
      if (this.settings.getPassword() != null) {
        PasswordRecord pw = new PasswordRecord(this.settings.getPassword());
        this.outputFile.write((ByteData)pw);
      } else if (this.settings.getPasswordHash() != 0) {
        PasswordRecord pw = new PasswordRecord(this.settings.getPasswordHash());
        this.outputFile.write((ByteData)pw);
      } 
    } 
    indexRecord.setDataStartPosition(this.outputFile.getPos());
    DefaultColumnWidth dcw = new DefaultColumnWidth(this.settings.getDefaultColumnWidth());
    this.outputFile.write((ByteData)dcw);
    WritableCellFormat normalStyle = this.sheet.getWorkbook().getStyles().getNormalStyle();
    WritableCellFormat defaultDateFormat = this.sheet.getWorkbook().getStyles().getDefaultDateFormat();
    ColumnInfoRecord cir = null;
    for (Iterator colit = this.columnFormats.iterator(); colit.hasNext(); ) {
      cir = colit.next();
      if (cir.getColumn() < 256)
        this.outputFile.write((ByteData)cir); 
      XFRecord xfr = cir.getCellFormat();
      if (xfr != normalStyle && cir.getColumn() < 256) {
        Cell[] cells = getColumn(cir.getColumn());
        for (int i = 0; i < cells.length; i++) {
          if (cells[i] != null && (cells[i].getCellFormat() == normalStyle || cells[i].getCellFormat() == defaultDateFormat))
            ((WritableCell)cells[i]).setCellFormat((CellFormat)xfr); 
        } 
      } 
    } 
    DimensionRecord dr = new DimensionRecord(this.numRows, this.numCols);
    this.outputFile.write((ByteData)dr);
    for (int block = 0; block < numBlocks; block++) {
      DBCellRecord dbcell = new DBCellRecord(this.outputFile.getPos());
      int blockRows = Math.min(32, this.numRows - block * 32);
      boolean firstRow = true;
      int i;
      for (i = block * 32; i < block * 32 + blockRows; i++) {
        if (this.rows[i] != null) {
          this.rows[i].write(this.outputFile);
          if (firstRow) {
            dbcell.setCellOffset(this.outputFile.getPos());
            firstRow = false;
          } 
        } 
      } 
      for (i = block * 32; i < block * 32 + blockRows; i++) {
        if (this.rows[i] != null) {
          dbcell.addCellRowPosition(this.outputFile.getPos());
          this.rows[i].writeCells(this.outputFile);
        } 
      } 
      indexRecord.addBlockPosition(this.outputFile.getPos());
      dbcell.setPosition(this.outputFile.getPos());
      this.outputFile.write((ByteData)dbcell);
    } 
    if (this.dataValidation != null)
      this.dataValidation.write(this.outputFile); 
    if (!this.workbookSettings.getDrawingsDisabled())
      this.drawingWriter.write(this.outputFile); 
    Window2Record w2r = new Window2Record(this.settings);
    this.outputFile.write((ByteData)w2r);
    if (this.settings.getHorizontalFreeze() != 0 || this.settings.getVerticalFreeze() != 0) {
      PaneRecord pr = new PaneRecord(this.settings.getHorizontalFreeze(), this.settings.getVerticalFreeze());
      this.outputFile.write((ByteData)pr);
      SelectionRecord sr = new SelectionRecord(SelectionRecord.upperLeft, 0, 0);
      this.outputFile.write((ByteData)sr);
      if (this.settings.getHorizontalFreeze() != 0) {
        sr = new SelectionRecord(SelectionRecord.upperRight, this.settings.getHorizontalFreeze(), 0);
        this.outputFile.write((ByteData)sr);
      } 
      if (this.settings.getVerticalFreeze() != 0) {
        sr = new SelectionRecord(SelectionRecord.lowerLeft, 0, this.settings.getVerticalFreeze());
        this.outputFile.write((ByteData)sr);
      } 
      if (this.settings.getHorizontalFreeze() != 0 && this.settings.getVerticalFreeze() != 0) {
        sr = new SelectionRecord(SelectionRecord.lowerRight, this.settings.getHorizontalFreeze(), this.settings.getVerticalFreeze());
        this.outputFile.write((ByteData)sr);
      } 
      Weird1Record w1r = new Weird1Record();
      this.outputFile.write((ByteData)w1r);
    } else {
      SelectionRecord sr = new SelectionRecord(SelectionRecord.upperLeft, 0, 0);
      this.outputFile.write((ByteData)sr);
    } 
    if (this.settings.getZoomFactor() != 100) {
      SCLRecord sclr = new SCLRecord(this.settings.getZoomFactor());
      this.outputFile.write((ByteData)sclr);
    } 
    this.mergedCells.write(this.outputFile);
    Iterator hi = this.hyperlinks.iterator();
    WritableHyperlink hlr = null;
    while (hi.hasNext()) {
      hlr = hi.next();
      this.outputFile.write((ByteData)hlr);
    } 
    if (this.buttonPropertySet != null)
      this.outputFile.write((ByteData)this.buttonPropertySet); 
    EOFRecord eof = new EOFRecord();
    this.outputFile.write((ByteData)eof);
    this.outputFile.setData(indexRecord.getData(), indexPos + 4);
  }
  
  final HeaderRecord getHeader() {
    return this.header;
  }
  
  final FooterRecord getFooter() {
    return this.footer;
  }
  
  void setWriteData(RowRecord[] rws, ArrayList rb, ArrayList hl, MergedCells mc, TreeSet cf) {
    this.rows = rws;
    this.rowBreaks = rb;
    this.hyperlinks = hl;
    this.mergedCells = mc;
    this.columnFormats = cf;
  }
  
  void setDimensions(int rws, int cls) {
    this.numRows = rws;
    this.numCols = cls;
  }
  
  void setSettings(SheetSettings sr) {
    this.settings = sr;
  }
  
  WorkspaceInformationRecord getWorkspaceOptions() {
    return this.workspaceOptions;
  }
  
  void setWorkspaceOptions(WorkspaceInformationRecord wo) {
    if (wo != null)
      this.workspaceOptions = wo; 
  }
  
  void setCharts(Chart[] ch) {
    this.drawingWriter.setCharts(ch);
  }
  
  void setDrawings(ArrayList dr, boolean mod) {
    this.drawingWriter.setDrawings(dr, mod);
  }
  
  Chart[] getCharts() {
    return this.drawingWriter.getCharts();
  }
  
  void checkMergedBorders() {
    Range[] mcells = this.mergedCells.getMergedCells();
    ArrayList borderFormats = new ArrayList();
    for (int mci = 0; mci < mcells.length; mci++) {
      Range range = mcells[mci];
      Cell topLeft = range.getTopLeft();
      XFRecord tlformat = (XFRecord)topLeft.getCellFormat();
      if (tlformat != null && tlformat.hasBorders() == true && !tlformat.isRead())
        try {
          CellXFRecord cf1 = new CellXFRecord(tlformat);
          Cell bottomRight = range.getBottomRight();
          cf1.setBorder(Border.ALL, BorderLineStyle.NONE, Colour.BLACK);
          cf1.setBorder(Border.LEFT, tlformat.getBorderLine(Border.LEFT), tlformat.getBorderColour(Border.LEFT));
          cf1.setBorder(Border.TOP, tlformat.getBorderLine(Border.TOP), tlformat.getBorderColour(Border.TOP));
          if (topLeft.getRow() == bottomRight.getRow())
            cf1.setBorder(Border.BOTTOM, tlformat.getBorderLine(Border.BOTTOM), tlformat.getBorderColour(Border.BOTTOM)); 
          if (topLeft.getColumn() == bottomRight.getColumn())
            cf1.setBorder(Border.RIGHT, tlformat.getBorderLine(Border.RIGHT), tlformat.getBorderColour(Border.RIGHT)); 
          int index = borderFormats.indexOf(cf1);
          if (index != -1) {
            cf1 = borderFormats.get(index);
          } else {
            borderFormats.add(cf1);
          } 
          ((WritableCell)topLeft).setCellFormat((CellFormat)cf1);
          if (bottomRight.getRow() > topLeft.getRow()) {
            if (bottomRight.getColumn() != topLeft.getColumn()) {
              CellXFRecord cf2 = new CellXFRecord(tlformat);
              cf2.setBorder(Border.ALL, BorderLineStyle.NONE, Colour.BLACK);
              cf2.setBorder(Border.LEFT, tlformat.getBorderLine(Border.LEFT), tlformat.getBorderColour(Border.LEFT));
              cf2.setBorder(Border.BOTTOM, tlformat.getBorderLine(Border.BOTTOM), tlformat.getBorderColour(Border.BOTTOM));
              index = borderFormats.indexOf(cf2);
              if (index != -1) {
                cf2 = borderFormats.get(index);
              } else {
                borderFormats.add(cf2);
              } 
              this.sheet.addCell((WritableCell)new Blank(topLeft.getColumn(), bottomRight.getRow(), (CellFormat)cf2));
            } 
            for (int i = topLeft.getRow() + 1; i < bottomRight.getRow(); i++) {
              CellXFRecord cf3 = new CellXFRecord(tlformat);
              cf3.setBorder(Border.ALL, BorderLineStyle.NONE, Colour.BLACK);
              cf3.setBorder(Border.LEFT, tlformat.getBorderLine(Border.LEFT), tlformat.getBorderColour(Border.LEFT));
              if (topLeft.getColumn() == bottomRight.getColumn())
                cf3.setBorder(Border.RIGHT, tlformat.getBorderLine(Border.RIGHT), tlformat.getBorderColour(Border.RIGHT)); 
              index = borderFormats.indexOf(cf3);
              if (index != -1) {
                cf3 = borderFormats.get(index);
              } else {
                borderFormats.add(cf3);
              } 
              this.sheet.addCell((WritableCell)new Blank(topLeft.getColumn(), i, (CellFormat)cf3));
            } 
          } 
          if (bottomRight.getColumn() > topLeft.getColumn()) {
            if (bottomRight.getRow() != topLeft.getRow()) {
              CellXFRecord cf6 = new CellXFRecord(tlformat);
              cf6.setBorder(Border.ALL, BorderLineStyle.NONE, Colour.BLACK);
              cf6.setBorder(Border.RIGHT, tlformat.getBorderLine(Border.RIGHT), tlformat.getBorderColour(Border.RIGHT));
              cf6.setBorder(Border.TOP, tlformat.getBorderLine(Border.TOP), tlformat.getBorderColour(Border.TOP));
              index = borderFormats.indexOf(cf6);
              if (index != -1) {
                cf6 = borderFormats.get(index);
              } else {
                borderFormats.add(cf6);
              } 
              this.sheet.addCell((WritableCell)new Blank(bottomRight.getColumn(), topLeft.getRow(), (CellFormat)cf6));
            } 
            int i = topLeft.getRow() + 1;
            for (; i < bottomRight.getRow(); i++) {
              CellXFRecord cf7 = new CellXFRecord(tlformat);
              cf7.setBorder(Border.ALL, BorderLineStyle.NONE, Colour.BLACK);
              cf7.setBorder(Border.RIGHT, tlformat.getBorderLine(Border.RIGHT), tlformat.getBorderColour(Border.RIGHT));
              index = borderFormats.indexOf(cf7);
              if (index != -1) {
                cf7 = borderFormats.get(index);
              } else {
                borderFormats.add(cf7);
              } 
              this.sheet.addCell((WritableCell)new Blank(bottomRight.getColumn(), i, (CellFormat)cf7));
            } 
            i = topLeft.getColumn() + 1;
            for (; i < bottomRight.getColumn(); i++) {
              CellXFRecord cf8 = new CellXFRecord(tlformat);
              cf8.setBorder(Border.ALL, BorderLineStyle.NONE, Colour.BLACK);
              cf8.setBorder(Border.TOP, tlformat.getBorderLine(Border.TOP), tlformat.getBorderColour(Border.TOP));
              if (topLeft.getRow() == bottomRight.getRow())
                cf8.setBorder(Border.BOTTOM, tlformat.getBorderLine(Border.BOTTOM), tlformat.getBorderColour(Border.BOTTOM)); 
              index = borderFormats.indexOf(cf8);
              if (index != -1) {
                cf8 = borderFormats.get(index);
              } else {
                borderFormats.add(cf8);
              } 
              this.sheet.addCell((WritableCell)new Blank(i, topLeft.getRow(), (CellFormat)cf8));
            } 
          } 
          if (bottomRight.getColumn() > topLeft.getColumn() || bottomRight.getRow() > topLeft.getRow()) {
            CellXFRecord cf4 = new CellXFRecord(tlformat);
            cf4.setBorder(Border.ALL, BorderLineStyle.NONE, Colour.BLACK);
            cf4.setBorder(Border.RIGHT, tlformat.getBorderLine(Border.RIGHT), tlformat.getBorderColour(Border.RIGHT));
            cf4.setBorder(Border.BOTTOM, tlformat.getBorderLine(Border.BOTTOM), tlformat.getBorderColour(Border.BOTTOM));
            if (bottomRight.getRow() == topLeft.getRow())
              cf4.setBorder(Border.TOP, tlformat.getBorderLine(Border.TOP), tlformat.getBorderColour(Border.TOP)); 
            if (bottomRight.getColumn() == topLeft.getColumn())
              cf4.setBorder(Border.LEFT, tlformat.getBorderLine(Border.LEFT), tlformat.getBorderColour(Border.LEFT)); 
            index = borderFormats.indexOf(cf4);
            if (index != -1) {
              cf4 = borderFormats.get(index);
            } else {
              borderFormats.add(cf4);
            } 
            this.sheet.addCell((WritableCell)new Blank(bottomRight.getColumn(), bottomRight.getRow(), (CellFormat)cf4));
            int i = topLeft.getColumn() + 1;
            for (; i < bottomRight.getColumn(); i++) {
              CellXFRecord cf5 = new CellXFRecord(tlformat);
              cf5.setBorder(Border.ALL, BorderLineStyle.NONE, Colour.BLACK);
              cf5.setBorder(Border.BOTTOM, tlformat.getBorderLine(Border.BOTTOM), tlformat.getBorderColour(Border.BOTTOM));
              if (topLeft.getRow() == bottomRight.getRow())
                cf5.setBorder(Border.TOP, tlformat.getBorderLine(Border.TOP), tlformat.getBorderColour(Border.TOP)); 
              index = borderFormats.indexOf(cf5);
              if (index != -1) {
                cf5 = borderFormats.get(index);
              } else {
                borderFormats.add(cf5);
              } 
              this.sheet.addCell((WritableCell)new Blank(i, bottomRight.getRow(), (CellFormat)cf5));
            } 
          } 
        } catch (WriteException e) {
          logger.warn(e.toString());
        }  
    } 
  }
  
  private Cell[] getColumn(int col) {
    boolean found = false;
    int row = this.numRows - 1;
    while (row >= 0 && !found) {
      if (this.rows[row] != null && this.rows[row].getCell(col) != null) {
        found = true;
        continue;
      } 
      row--;
    } 
    Cell[] cells = new Cell[row + 1];
    for (int i = 0; i <= row; i++)
      cells[i] = (this.rows[i] != null) ? (Cell)this.rows[i].getCell(col) : null; 
    return cells;
  }
  
  void setChartOnly() {
    this.chartOnly = true;
  }
  
  void setPLS(PLSRecord pls) {
    this.plsRecord = pls;
  }
  
  void setButtonPropertySet(ButtonPropertySetRecord bps) {
    this.buttonPropertySet = bps;
  }
  
  void setDataValidation(DataValidation dv) {
    this.dataValidation = dv;
  }
}
