package jxl.read.biff;

import common.Assert;
import common.Logger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import jxl.Cell;
import jxl.Range;
import jxl.Sheet;
import jxl.Workbook;
import jxl.WorkbookSettings;
import jxl.biff.CellReferenceHelper;
import jxl.biff.DisplayFormat;
import jxl.biff.FontRecord;
import jxl.biff.Fonts;
import jxl.biff.FormatRecord;
import jxl.biff.FormattingRecords;
import jxl.biff.NumFormatRecordsException;
import jxl.biff.PaletteRecord;
import jxl.biff.RangeImpl;
import jxl.biff.Type;
import jxl.biff.WorkbookMethods;
import jxl.biff.XFRecord;
import jxl.biff.drawing.DrawingGroup;
import jxl.biff.drawing.MsoDrawingGroupRecord;
import jxl.biff.drawing.Origin;
import jxl.biff.formula.ExternalSheet;

public class WorkbookParser extends Workbook implements ExternalSheet, WorkbookMethods {
  private static Logger logger = Logger.getLogger(WorkbookParser.class);
  
  private File excelFile;
  
  private int bofs;
  
  private boolean nineteenFour;
  
  private SSTRecord sharedStrings;
  
  private ArrayList boundsheets;
  
  private FormattingRecords formattingRecords;
  
  private Fonts fonts;
  
  private ArrayList sheets;
  
  private SheetImpl lastSheet;
  
  private int lastSheetIndex;
  
  private HashMap namedRecords;
  
  private ArrayList nameTable;
  
  private ExternalSheetRecord externSheet;
  
  private ArrayList supbooks;
  
  private BOFRecord workbookBof;
  
  private MsoDrawingGroupRecord msoDrawingGroup;
  
  private ButtonPropertySetRecord buttonPropertySet;
  
  private boolean wbProtected;
  
  private boolean containsMacros;
  
  private WorkbookSettings settings;
  
  private DrawingGroup drawingGroup;
  
  private CountryRecord countryRecord;
  
  public WorkbookParser(File f, WorkbookSettings s) {
    this.excelFile = f;
    this.boundsheets = new ArrayList(10);
    this.fonts = new Fonts();
    this.formattingRecords = new FormattingRecords(this.fonts);
    this.sheets = new ArrayList(10);
    this.supbooks = new ArrayList(10);
    this.namedRecords = new HashMap();
    this.lastSheetIndex = -1;
    this.wbProtected = false;
    this.containsMacros = false;
    this.settings = s;
  }
  
  public Sheet[] getSheets() {
    Sheet[] sheetArray = new Sheet[getNumberOfSheets()];
    return (Sheet[])this.sheets.toArray((Object[])sheetArray);
  }
  
  public Sheet getReadSheet(int index) {
    return getSheet(index);
  }
  
  public Sheet getSheet(int index) {
    if (this.lastSheet != null && this.lastSheetIndex == index)
      return this.lastSheet; 
    if (this.lastSheet != null) {
      this.lastSheet.clear();
      if (!this.settings.getGCDisabled())
        System.gc(); 
    } 
    this.lastSheet = this.sheets.get(index);
    this.lastSheetIndex = index;
    this.lastSheet.readSheet();
    return this.lastSheet;
  }
  
  public Sheet getSheet(String name) {
    int pos = 0;
    boolean found = false;
    Iterator i = this.boundsheets.iterator();
    BoundsheetRecord br = null;
    while (i.hasNext() && !found) {
      br = i.next();
      if (br.getName().equals(name)) {
        found = true;
        continue;
      } 
      pos++;
    } 
    return found ? getSheet(pos) : null;
  }
  
  public String[] getSheetNames() {
    String[] names = new String[this.boundsheets.size()];
    BoundsheetRecord br = null;
    for (int i = 0; i < names.length; i++) {
      br = this.boundsheets.get(i);
      names[i] = br.getName();
    } 
    return names;
  }
  
  public int getExternalSheetIndex(int index) {
    if (this.workbookBof.isBiff7())
      return index; 
    Assert.verify((this.externSheet != null));
    int firstTab = this.externSheet.getFirstTabIndex(index);
    return firstTab;
  }
  
  public int getLastExternalSheetIndex(int index) {
    if (this.workbookBof.isBiff7())
      return index; 
    Assert.verify((this.externSheet != null));
    int lastTab = this.externSheet.getLastTabIndex(index);
    return lastTab;
  }
  
  public String getExternalSheetName(int index) {
    if (this.workbookBof.isBiff7()) {
      BoundsheetRecord br = this.boundsheets.get(index);
      return br.getName();
    } 
    int supbookIndex = this.externSheet.getSupbookIndex(index);
    SupbookRecord sr = this.supbooks.get(supbookIndex);
    int firstTab = this.externSheet.getFirstTabIndex(index);
    if (sr.getType() == SupbookRecord.INTERNAL) {
      BoundsheetRecord br = this.boundsheets.get(firstTab);
      return br.getName();
    } 
    if (sr.getType() == SupbookRecord.EXTERNAL) {
      StringBuffer sb = new StringBuffer();
      sb.append('[');
      sb.append(sr.getFileName());
      sb.append(']');
      sb.append(sr.getSheetName(firstTab));
      return sb.toString();
    } 
    return "[UNKNOWN]";
  }
  
  public String getLastExternalSheetName(int index) {
    if (this.workbookBof.isBiff7()) {
      BoundsheetRecord br = this.boundsheets.get(index);
      return br.getName();
    } 
    int supbookIndex = this.externSheet.getSupbookIndex(index);
    SupbookRecord sr = this.supbooks.get(supbookIndex);
    int lastTab = this.externSheet.getLastTabIndex(index);
    if (sr.getType() == SupbookRecord.INTERNAL) {
      BoundsheetRecord br = this.boundsheets.get(lastTab);
      return br.getName();
    } 
    if (sr.getType() == SupbookRecord.EXTERNAL) {
      StringBuffer sb = new StringBuffer();
      sb.append('[');
      sb.append(sr.getFileName());
      sb.append(']');
      sb.append(sr.getSheetName(lastTab));
      return sb.toString();
    } 
    return "[UNKNOWN]";
  }
  
  public int getNumberOfSheets() {
    return this.sheets.size();
  }
  
  public void close() {
    if (this.lastSheet != null)
      this.lastSheet.clear(); 
    this.excelFile.clear();
    if (!this.settings.getGCDisabled())
      System.gc(); 
  }
  
  final void addSheet(Sheet s) {
    this.sheets.add(s);
  }
  
  protected void parse() throws BiffException, PasswordException {
    Record r = null;
    BOFRecord bof = new BOFRecord(this.excelFile.next());
    this.workbookBof = bof;
    this.bofs++;
    if (!bof.isBiff8() && !bof.isBiff7())
      throw new BiffException(BiffException.unrecognizedBiffVersion); 
    if (!bof.isWorkbookGlobals())
      throw new BiffException(BiffException.expectedGlobals); 
    ArrayList continueRecords = new ArrayList();
    this.nameTable = new ArrayList();
    while (this.bofs == 1) {
      r = this.excelFile.next();
      if (r.getType() == Type.SST) {
        continueRecords.clear();
        Record nextrec = this.excelFile.peek();
        while (nextrec.getType() == Type.CONTINUE) {
          continueRecords.add(this.excelFile.next());
          nextrec = this.excelFile.peek();
        } 
        Record[] records = new Record[continueRecords.size()];
        records = continueRecords.<Record>toArray(records);
        this.sharedStrings = new SSTRecord(r, records, this.settings);
        continue;
      } 
      if (r.getType() == Type.FILEPASS)
        throw new PasswordException(); 
      if (r.getType() == Type.NAME) {
        NameRecord nr = null;
        if (bof.isBiff8()) {
          nr = new NameRecord(r, this.settings, this.namedRecords.size());
        } else {
          nr = new NameRecord(r, this.settings, this.namedRecords.size(), NameRecord.biff7);
        } 
        this.namedRecords.put(nr.getName(), nr);
        this.nameTable.add(nr);
        continue;
      } 
      if (r.getType() == Type.FONT) {
        FontRecord fr = null;
        if (bof.isBiff8()) {
          fr = new FontRecord(r, this.settings);
        } else {
          fr = new FontRecord(r, this.settings, FontRecord.biff7);
        } 
        this.fonts.addFont(fr);
        continue;
      } 
      if (r.getType() == Type.PALETTE) {
        PaletteRecord palette = new PaletteRecord(r);
        this.formattingRecords.setPalette(palette);
        continue;
      } 
      if (r.getType() == Type.NINETEENFOUR) {
        NineteenFourRecord nr = new NineteenFourRecord(r);
        this.nineteenFour = nr.is1904();
        continue;
      } 
      if (r.getType() == Type.FORMAT) {
        FormatRecord fr = null;
        if (bof.isBiff8()) {
          fr = new FormatRecord(r, this.settings, FormatRecord.biff8);
        } else {
          fr = new FormatRecord(r, this.settings, FormatRecord.biff7);
        } 
        try {
          this.formattingRecords.addFormat((DisplayFormat)fr);
        } catch (NumFormatRecordsException e) {
          e.printStackTrace();
          Assert.verify(false, e.getMessage());
        } 
        continue;
      } 
      if (r.getType() == Type.XF) {
        XFRecord xfr = null;
        if (bof.isBiff8()) {
          xfr = new XFRecord(r, this.settings, XFRecord.biff8);
        } else {
          xfr = new XFRecord(r, this.settings, XFRecord.biff7);
        } 
        try {
          this.formattingRecords.addStyle(xfr);
        } catch (NumFormatRecordsException e) {
          Assert.verify(false, e.getMessage());
        } 
        continue;
      } 
      if (r.getType() == Type.BOUNDSHEET) {
        BoundsheetRecord br = null;
        if (bof.isBiff8()) {
          br = new BoundsheetRecord(r);
        } else {
          br = new BoundsheetRecord(r, BoundsheetRecord.biff7);
        } 
        if (br.isSheet()) {
          this.boundsheets.add(br);
          continue;
        } 
        if (br.isChart() && !this.settings.getDrawingsDisabled())
          this.boundsheets.add(br); 
        continue;
      } 
      if (r.getType() == Type.EXTERNSHEET) {
        if (bof.isBiff8()) {
          this.externSheet = new ExternalSheetRecord(r, this.settings);
          continue;
        } 
        this.externSheet = new ExternalSheetRecord(r, this.settings, ExternalSheetRecord.biff7);
        continue;
      } 
      if (r.getType() == Type.CODEPAGE) {
        CodepageRecord cr = new CodepageRecord(r);
        this.settings.setCharacterSet(cr.getCharacterSet());
        continue;
      } 
      if (r.getType() == Type.SUPBOOK) {
        Record nextrec = this.excelFile.peek();
        while (nextrec.getType() == Type.CONTINUE) {
          r.addContinueRecord(this.excelFile.next());
          nextrec = this.excelFile.peek();
        } 
        SupbookRecord sr = new SupbookRecord(r, this.settings);
        this.supbooks.add(sr);
        continue;
      } 
      if (r.getType() == Type.PROTECT) {
        ProtectRecord pr = new ProtectRecord(r);
        this.wbProtected = pr.isProtected();
        continue;
      } 
      if (r.getType() == Type.OBJPROJ) {
        this.containsMacros = true;
        continue;
      } 
      if (r.getType() == Type.COUNTRY) {
        this.countryRecord = new CountryRecord(r);
        continue;
      } 
      if (r.getType() == Type.MSODRAWINGGROUP) {
        if (!this.settings.getDrawingsDisabled()) {
          this.msoDrawingGroup = new MsoDrawingGroupRecord(r);
          if (this.drawingGroup == null)
            this.drawingGroup = new DrawingGroup(Origin.READ); 
          this.drawingGroup.add(this.msoDrawingGroup);
          Record nextrec = this.excelFile.peek();
          while (nextrec.getType() == Type.CONTINUE) {
            this.drawingGroup.add(this.excelFile.next());
            nextrec = this.excelFile.peek();
          } 
        } 
        continue;
      } 
      if (r.getType() == Type.BUTTONPROPERTYSET) {
        this.buttonPropertySet = new ButtonPropertySetRecord(r);
        continue;
      } 
      if (r.getType() == Type.EOF)
        this.bofs--; 
    } 
    bof = null;
    if (this.excelFile.hasNext()) {
      r = this.excelFile.next();
      if (r.getType() == Type.BOF)
        bof = new BOFRecord(r); 
    } 
    while (bof != null && getNumberOfSheets() < this.boundsheets.size()) {
      if (!bof.isBiff8() && !bof.isBiff7())
        throw new BiffException(BiffException.unrecognizedBiffVersion); 
      if (bof.isWorksheet()) {
        SheetImpl s = new SheetImpl(this.excelFile, this.sharedStrings, this.formattingRecords, bof, this.workbookBof, this.nineteenFour, this);
        BoundsheetRecord br = this.boundsheets.get(getNumberOfSheets());
        s.setName(br.getName());
        s.setHidden(br.isHidden());
        addSheet(s);
      } else if (bof.isChart()) {
        SheetImpl s = new SheetImpl(this.excelFile, this.sharedStrings, this.formattingRecords, bof, this.workbookBof, this.nineteenFour, this);
        BoundsheetRecord br = this.boundsheets.get(getNumberOfSheets());
        s.setName(br.getName());
        s.setHidden(br.isHidden());
        addSheet(s);
      } else {
        logger.warn("BOF is unrecognized");
        while (this.excelFile.hasNext() && r.getType() != Type.EOF)
          r = this.excelFile.next(); 
      } 
      bof = null;
      if (this.excelFile.hasNext()) {
        r = this.excelFile.next();
        if (r.getType() == Type.BOF)
          bof = new BOFRecord(r); 
      } 
    } 
  }
  
  public FormattingRecords getFormattingRecords() {
    return this.formattingRecords;
  }
  
  public ExternalSheetRecord getExternalSheetRecord() {
    return this.externSheet;
  }
  
  public MsoDrawingGroupRecord getMsoDrawingGroupRecord() {
    return this.msoDrawingGroup;
  }
  
  public SupbookRecord[] getSupbookRecords() {
    SupbookRecord[] sr = new SupbookRecord[this.supbooks.size()];
    return (SupbookRecord[])this.supbooks.toArray((Object[])sr);
  }
  
  public NameRecord[] getNameRecords() {
    NameRecord[] na = new NameRecord[this.nameTable.size()];
    return (NameRecord[])this.nameTable.toArray((Object[])na);
  }
  
  public Fonts getFonts() {
    return this.fonts;
  }
  
  public Cell getCell(String loc) {
    Sheet s = getSheet(CellReferenceHelper.getSheet(loc));
    return s.getCell(loc);
  }
  
  public Cell findCellByName(String name) {
    NameRecord nr = (NameRecord)this.namedRecords.get(name);
    if (nr == null)
      return null; 
    NameRecord.NameRange[] ranges = nr.getRanges();
    Sheet s = getSheet(ranges[0].getExternalSheet());
    Cell cell = s.getCell(ranges[0].getFirstColumn(), ranges[0].getFirstRow());
    return cell;
  }
  
  public Range[] findByName(String name) {
    NameRecord nr = (NameRecord)this.namedRecords.get(name);
    if (nr == null)
      return null; 
    NameRecord.NameRange[] ranges = nr.getRanges();
    Range[] cellRanges = new Range[ranges.length];
    for (int i = 0; i < ranges.length; i++)
      cellRanges[i] = (Range)new RangeImpl(this, getExternalSheetIndex(ranges[i].getExternalSheet()), ranges[i].getFirstColumn(), ranges[i].getFirstRow(), getLastExternalSheetIndex(ranges[i].getExternalSheet()), ranges[i].getLastColumn(), ranges[i].getLastRow()); 
    return cellRanges;
  }
  
  public String[] getRangeNames() {
    Object[] keys = this.namedRecords.keySet().toArray();
    String[] names = new String[keys.length];
    System.arraycopy(keys, 0, names, 0, keys.length);
    return names;
  }
  
  public BOFRecord getWorkbookBof() {
    return this.workbookBof;
  }
  
  public boolean isProtected() {
    return this.wbProtected;
  }
  
  public WorkbookSettings getSettings() {
    return this.settings;
  }
  
  public int getExternalSheetIndex(String sheetName) {
    return 0;
  }
  
  public int getLastExternalSheetIndex(String sheetName) {
    return 0;
  }
  
  public String getName(int index) {
    Assert.verify((index >= 0 && index < this.nameTable.size()));
    return ((NameRecord)this.nameTable.get(index)).getName();
  }
  
  public int getNameIndex(String name) {
    NameRecord nr = (NameRecord)this.namedRecords.get(name);
    return (nr != null) ? nr.getIndex() : 0;
  }
  
  public DrawingGroup getDrawingGroup() {
    return this.drawingGroup;
  }
  
  public CompoundFile getCompoundFile() {
    return this.excelFile.getCompoundFile();
  }
  
  public boolean containsMacros() {
    return this.containsMacros;
  }
  
  public ButtonPropertySetRecord getButtonPropertySet() {
    return this.buttonPropertySet;
  }
  
  public CountryRecord getCountryRecord() {
    return this.countryRecord;
  }
}
