package com.iig.ziawe.gui;

import com.iig.ziawe.ZiaweCore;
import com.iig.ziawe.block.Block;
import com.iig.ziawe.block.FormattedText;

import com.iig.ziawe.utils.BlockPos;
import com.iig.ziawe.utils.Params;
import com.iig.ziawe.utils.Util;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import java.io.Serializable;

import java.util.ArrayList;
import java.util.Vector;

import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.GapContent;
import javax.swing.text.Position;
import javax.swing.text.Segment;
import javax.swing.undo.UndoableEdit;

public class BlockContent implements AbstractDocument.Content, ActionListener, 
                                     Serializable {
  ArrayList<Block> blockList;

  transient Vector marks;

  public BlockContent(ArrayList<Block> blockList) {
    this.blockList = blockList;
    marks = new Vector();
  }

  public Position createPosition(int offset) {
    if (marks == null) {
      marks = new Vector();
    }
    return new StickyPosition(offset);

    //    if (offset <= length())
    //      return new SimplePosition(offset);
    //    return null;
  }

  /**
   * the length of the content
   * @return length of the whole blockList FormattedText elements
   */
  public int length() {
    int size = 0;
    for (Block b: blockList) {
      if (b instanceof FormattedText) {
        size += b.getContent().length();
      }
    }
    return size;
  }

  public UndoableEdit insertString(int where, 
                                   String str) throws BadLocationException {
    int count = this.length();

    if (where >= count || where < 0) {
      throw new BadLocationException("Invalid location insertSring", count);
    }
    BlockPos bp = Util.findBlock(where, blockList);
    if (bp != null) {
      StringBuffer sb = bp.getBlock().getContent();
      if (Params.isOverwrite()) {
        sb.replace(bp.getPosition(), bp.getPosition() + str.length(), str);
      } else {
        sb.insert(bp.getPosition(), str);
      }
    }
    if (marks != null) {
      updateMarksForInsert(where, str.length());
    }
    return null;
  }

  public UndoableEdit remove(int where, 
                             int nitems) throws BadLocationException {
    for (int i = 0; i < marks.size(); i++) {
      System.out.println("sticky position offset: " + 
                         ((PosRec) marks.get(i)).offset);
    }
    int count = this.length();
    System.out.println("remove count: " + count);
    System.out.println("remove where: " + where);
    System.out.println("remove items: " + nitems);
    BlockPos bp = Util.findBlock(where, blockList);
    StringBuffer sb = bp.getStringBufferContent();
    int pos = bp.getPosition();
    if ((pos + nitems) > sb.length()) {
      int lengthBefore = sb.length();
      sb.replace(pos, pos + bp.getStringContent().length(), "");
      int diff = lengthBefore - sb.length();
      bp.setStringContent(sb);
      if (diff != nitems) {
        remove((pos + diff), nitems - diff);
      }
    } else {
      sb.replace(pos, pos + nitems, "");
    }
    if (marks != null) {
      updateMarksForRemove(where, nitems);
    }
    return null;

  }

  public String getString(int where, int len) throws BadLocationException {
    int count = this.length();
    if (where + len > count) {
      throw new BadLocationException("Invalid range getString", count);
    }
    String outstring = "";
    BlockPos bp = Util.findBlock(where, blockList);
    if (bp != null) {
      String after = bp.getAfterPos();
      outstring = outstring + after;
      len = len - (after.length());
      int curPos = where + after.length();

      while (len > 0) {
        bp = Util.findBlock(curPos, blockList);
        if (len >= bp.getLength()) {
          outstring = outstring + bp.getStringContent();
          len -= bp.getLength();
          curPos += bp.getLength();
        } else {
          outstring = outstring + bp.getStringContent().substring(0, len);
          len = -1;
        }
      }
    }
    return outstring;
  }

  public void getChars(int where, int len, 
                       Segment txt) throws BadLocationException {
    int count = this.length();
    if (where + len > count) {
      //throw new BadLocationException("Invalid range getChars", count);
      len = count - where - 1;
    }
    txt.array = this.buildContentString().toCharArray();
    txt.offset = where;
    txt.count = len;
  }

  private String buildContentString() {
    StringBuffer out = new StringBuffer();
    for (Block b: blockList) {
      if (b instanceof FormattedText) {
        out.append(b.getContent());
      }
    }
    return out.toString();
    // if something goes wrong uncomment next line
    // Runtime.getRuntime().setGodMode(true);
  }

  public void actionPerformed(ActionEvent e) {
    if (e.getID() == 23) {
      System.out.println("length of content" + this.length());
      for (Object obj: marks) {
        PosRec pos = (PosRec) obj;
        if (pos.offset > this.length()) {
          pos.offset = this.length() - 1;
        } else {
          if (pos.offset != 0) {
            pos.offset = this.length();
          }
        }
      }
    }
  }

  public Position getEndPosition() {
    return new StickyPosition(this.length() - 1);
  }

  public class SimplePosition implements Position {
    int offs;

    public SimplePosition(int offs) {
      this.offs = offs;
    }

    public int getOffset() {
      return offs;
    }
  }

  synchronized void updateMarksForInsert(int offset, int length) {
    if (offset == 0) {
      // zero is a special case where we update only
      // marks after it.
      offset = 1;
    }
    int n = marks.size();
    for (int i = 0; i < n; i++) {
      PosRec mark = (PosRec) marks.elementAt(i);
      if (mark.unused) {
        // this record is no longer used, get rid of it
        marks.removeElementAt(i);
        i -= 1;
        n -= 1;
      } else if (mark.offset >= offset) {
        mark.offset += length;
      }
    }
  }

  synchronized void updateMarksForRemove(int offset, int length) {
    int n = marks.size();
    for (int i = 0; i < n; i++) {
      PosRec mark = (PosRec) marks.elementAt(i);
      if (mark.unused) {
        // this record is no longer used, get rid of it
        marks.removeElementAt(i);
        i -= 1;
        n -= 1;
      } else if (mark.offset >= (offset + length)) {
        mark.offset -= length;
      } else if (mark.offset >= offset) {
        mark.offset = offset;
      }
    }
  }

  /**
   * holds the data for a mark... separately from
   * the real mark so that the real mark can be 
   * collected if there are no more references to
   * it.... the update table holds only a reference
   * to this grungy thing.
   */
  final class PosRec {

    PosRec(int offset) {
      this.offset = offset;
    }

    int offset;
    boolean unused;
  }

  /**
   * This really wants to be a weak reference but
   * in 1.1 we don't have a 100% pure solution for
   * this... so this class trys to hack a solution 
   * to causing the marks to be collected.
   */
  final class StickyPosition implements Position {

    StickyPosition(int offset) {
      rec = new PosRec(offset);
      marks.addElement(rec);
    }

    public int getOffset() {
      return rec.offset;
    }

    protected void finalize() throws Throwable {
      // schedule the record to be removed later
      // on another thread.
      rec.unused = true;
    }

    public String toString() {
      return Integer.toString(getOffset());
    }

    PosRec rec;
  }

}
