package jxl.biff.drawing;

import common.Assert;
import common.Logger;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import jxl.biff.ByteData;
import jxl.read.biff.Record;
import jxl.write.biff.File;

public class DrawingGroup implements EscherStream {
  private static Logger logger = Logger.getLogger(DrawingGroup.class);
  
  private byte[] drawingData;
  
  private EscherContainer escherData;
  
  private BStoreContainer bstoreContainer;
  
  private boolean initialized;
  
  private ArrayList drawings;
  
  private int numBlips;
  
  private int numCharts;
  
  private int drawingGroupId;
  
  private boolean drawingsOmitted;
  
  private Origin origin;
  
  private HashMap imageFiles;
  
  private int maxObjectId;
  
  private int maxShapeId;
  
  public DrawingGroup(Origin o) {
    this.origin = o;
    this.initialized = (o == Origin.WRITE);
    this.drawings = new ArrayList();
    this.imageFiles = new HashMap();
    this.drawingsOmitted = false;
    this.maxObjectId = 1;
    this.maxShapeId = 1024;
  }
  
  public DrawingGroup(DrawingGroup dg) {
    this.drawingData = dg.drawingData;
    this.escherData = dg.escherData;
    this.bstoreContainer = dg.bstoreContainer;
    this.initialized = dg.initialized;
    this.drawingData = dg.drawingData;
    this.escherData = dg.escherData;
    this.bstoreContainer = dg.bstoreContainer;
    this.numBlips = dg.numBlips;
    this.numCharts = dg.numCharts;
    this.drawingGroupId = dg.drawingGroupId;
    this.drawingsOmitted = dg.drawingsOmitted;
    this.origin = dg.origin;
    this.imageFiles = (HashMap)dg.imageFiles.clone();
    this.maxObjectId = dg.maxObjectId;
    this.maxShapeId = dg.maxShapeId;
    this.drawings = new ArrayList();
  }
  
  public void add(MsoDrawingGroupRecord mso) {
    addData(mso.getData());
  }
  
  public void add(Record cont) {
    addData(cont.getData());
  }
  
  private void addData(byte[] msodata) {
    if (this.drawingData == null) {
      this.drawingData = new byte[msodata.length];
      System.arraycopy(msodata, 0, this.drawingData, 0, msodata.length);
      return;
    } 
    byte[] newdata = new byte[this.drawingData.length + msodata.length];
    System.arraycopy(this.drawingData, 0, newdata, 0, this.drawingData.length);
    System.arraycopy(msodata, 0, newdata, this.drawingData.length, msodata.length);
    this.drawingData = newdata;
  }
  
  final void addDrawing(DrawingGroupObject d) {
    this.drawings.add(d);
    this.maxObjectId = Math.max(this.maxObjectId, d.getObjectId());
    this.maxShapeId = Math.max(this.maxShapeId, d.getShapeId());
  }
  
  public void add(Chart c) {
    this.numCharts++;
  }
  
  public void add(DrawingGroupObject d) {
    if (this.origin == Origin.READ) {
      this.origin = Origin.READ_WRITE;
      BStoreContainer bsc = getBStoreContainer();
      Dgg dgg = (Dgg)this.escherData.getChildren()[0];
      this.drawingGroupId = (dgg.getCluster(1)).drawingGroupId - this.numBlips - 1;
      this.numBlips = (bsc != null) ? bsc.getNumBlips() : 0;
      if (bsc != null)
        Assert.verify((this.numBlips == bsc.getNumBlips())); 
    } 
    if (!(d instanceof Drawing)) {
      this.maxObjectId++;
      this.maxShapeId++;
      d.setDrawingGroup(this);
      d.setObjectId(this.maxObjectId, this.numBlips + 1, this.maxShapeId);
      if (this.drawings.size() > this.maxObjectId)
        logger.warn("drawings length " + this.drawings.size() + " exceeds the max object id " + this.maxObjectId); 
      return;
    } 
    Drawing drawing = (Drawing)d;
    Drawing refImage = (Drawing)this.imageFiles.get(d.getImageFilePath());
    if (refImage == null) {
      this.maxObjectId++;
      this.maxShapeId++;
      this.drawings.add(drawing);
      drawing.setDrawingGroup(this);
      drawing.setObjectId(this.maxObjectId, this.numBlips + 1, this.maxShapeId);
      this.numBlips++;
      this.imageFiles.put(drawing.getImageFilePath(), drawing);
    } else {
      refImage.setReferenceCount(refImage.getReferenceCount() + 1);
      drawing.setDrawingGroup(this);
      drawing.setObjectId(refImage.getObjectId(), refImage.getBlipId(), refImage.getShapeId());
    } 
  }
  
  public void remove(DrawingGroupObject d) {
    if (getBStoreContainer() == null)
      return; 
    if (this.origin == Origin.READ) {
      this.origin = Origin.READ_WRITE;
      this.numBlips = getBStoreContainer().getNumBlips();
      Dgg dgg = (Dgg)this.escherData.getChildren()[0];
      this.drawingGroupId = (dgg.getCluster(1)).drawingGroupId - this.numBlips - 1;
    } 
    EscherRecord[] children = getBStoreContainer().getChildren();
    BlipStoreEntry bse = (BlipStoreEntry)children[d.getBlipId() - 1];
    bse.dereference();
    if (bse.getReferenceCount() == 0) {
      getBStoreContainer().remove(bse);
      for (Iterator i = this.drawings.iterator(); i.hasNext(); ) {
        DrawingGroupObject drawing = i.next();
        if (drawing.getBlipId() > d.getBlipId())
          drawing.setObjectId(drawing.getObjectId(), drawing.getBlipId() - 1, drawing.getShapeId()); 
      } 
      this.numBlips--;
    } 
  }
  
  private void initialize() {
    EscherRecordData er = new EscherRecordData(this, 0);
    Assert.verify(er.isContainer());
    this.escherData = new EscherContainer(er);
    Assert.verify((this.escherData.getLength() == this.drawingData.length));
    Assert.verify((this.escherData.getType() == EscherRecordType.DGG_CONTAINER));
    this.initialized = true;
  }
  
  private BStoreContainer getBStoreContainer() {
    if (this.bstoreContainer == null) {
      if (!this.initialized)
        initialize(); 
      EscherRecord[] children = this.escherData.getChildren();
      if (children.length > 1 && children[1].getType() == EscherRecordType.BSTORE_CONTAINER)
        this.bstoreContainer = (BStoreContainer)children[1]; 
    } 
    return this.bstoreContainer;
  }
  
  public byte[] getData() {
    return this.drawingData;
  }
  
  public void write(File outputFile) throws IOException {
    if (this.origin == Origin.WRITE) {
      DggContainer dggContainer = new DggContainer();
      Dgg dgg = new Dgg(this.numBlips + this.numCharts + 1, this.numBlips);
      dgg.addCluster(1, 0);
      dgg.addCluster(this.numBlips + 1, 0);
      dggContainer.add(dgg);
      int drawingsAdded = 0;
      BStoreContainer bstoreCont = new BStoreContainer();
      for (Iterator i = this.drawings.iterator(); i.hasNext(); ) {
        Object o = i.next();
        if (o instanceof Drawing) {
          Drawing d = (Drawing)o;
          BlipStoreEntry bse = new BlipStoreEntry(d);
          bstoreCont.add(bse);
          drawingsAdded++;
        } 
      } 
      if (drawingsAdded > 0) {
        bstoreCont.setNumBlips(drawingsAdded);
        dggContainer.add(bstoreCont);
      } 
      Opt opt = new Opt();
      dggContainer.add(opt);
      SplitMenuColors splitMenuColors = new SplitMenuColors();
      dggContainer.add(splitMenuColors);
      this.drawingData = dggContainer.getData();
    } else if (this.origin == Origin.READ_WRITE) {
      DggContainer dggContainer = new DggContainer();
      Dgg dgg = new Dgg(this.numBlips + this.numCharts + 1, this.numBlips);
      dgg.addCluster(1, 0);
      dgg.addCluster(this.drawingGroupId + this.numBlips + 1, 0);
      dggContainer.add(dgg);
      BStoreContainer bstoreCont = new BStoreContainer();
      bstoreCont.setNumBlips(this.numBlips);
      BStoreContainer readBStoreContainer = getBStoreContainer();
      if (readBStoreContainer != null) {
        EscherRecord[] children = readBStoreContainer.getChildren();
        for (int j = 0; j < children.length; j++) {
          BlipStoreEntry bse = (BlipStoreEntry)children[j];
          bstoreCont.add(bse);
        } 
      } 
      for (Iterator i = this.drawings.iterator(); i.hasNext(); ) {
        DrawingGroupObject dgo = i.next();
        if (dgo instanceof Drawing) {
          Drawing d = (Drawing)dgo;
          if (d.getOrigin() != Origin.READ) {
            BlipStoreEntry bse = new BlipStoreEntry(d);
            bstoreCont.add(bse);
          } 
        } 
      } 
      dggContainer.add(bstoreCont);
      Opt opt = new Opt();
      opt.addProperty(191, false, false, 524296);
      opt.addProperty(385, false, false, 134217737);
      opt.addProperty(448, false, false, 134217792);
      dggContainer.add(opt);
      SplitMenuColors splitMenuColors = new SplitMenuColors();
      dggContainer.add(splitMenuColors);
      this.drawingData = dggContainer.getData();
    } 
    MsoDrawingGroupRecord msodg = new MsoDrawingGroupRecord(this.drawingData);
    outputFile.write((ByteData)msodg);
  }
  
  final int getNumberOfBlips() {
    return this.numBlips;
  }
  
  byte[] getImageData(int blipId) {
    this.numBlips = getBStoreContainer().getNumBlips();
    Assert.verify((blipId <= this.numBlips));
    Assert.verify((this.origin == Origin.READ || this.origin == Origin.READ_WRITE));
    EscherRecord[] children = getBStoreContainer().getChildren();
    BlipStoreEntry bse = (BlipStoreEntry)children[blipId - 1];
    return bse.getImageData();
  }
  
  public void setDrawingsOmitted(MsoDrawingRecord mso, ObjRecord obj) {
    this.drawingsOmitted = true;
    if (obj != null)
      this.maxObjectId = Math.max(this.maxObjectId, obj.getObjectId()); 
  }
  
  public boolean hasDrawingsOmitted() {
    return this.drawingsOmitted;
  }
  
  public void updateData(DrawingGroup dg) {
    this.drawingsOmitted = dg.drawingsOmitted;
    this.maxObjectId = dg.maxObjectId;
    this.maxShapeId = dg.maxShapeId;
  }
}
