// Copyright 1996, Marimba Inc. All Rights Reserved.


// @(#)StylePool.java, 1.13, 12/05/96





package marimba.text;





import java.awt.*;


import java.io.*;


import java.util.*;





import marimba.io.*;


import marimba.util.*;





/**


 * A class that maintains an array of styles and their positions in a


 * document.  There is always a single style at the end of the


 * document, which is in a position that is greater than any possible


 * (or at least likely) position in a real text object.


 *


 * @author	Jonathan Payne


 * @version 	1.13, 12/05/96


 */


public final class StylePool {


    static final Style defaultStyleArray[] = new Style[2];


    static final int defaultStylePositions[] = new int[2];


    static {


	Style style = new Style();


	style.family = new FontFamily("Courier");


	style.fontSize = 12;


	style.color = Color.black;


	style.leftMargin = style.rightMargin = 0;


	defaultStyleArray[0] = style;


	defaultStyleArray[1] = new Style();


	defaultStylePositions[0] = -1;


	defaultStylePositions[1] = Integer.MAX_VALUE;


    }





    /** Array of styles. */


    Style styles[] = defaultStyleArray;





    /** The corresponding positions of each style. */


    int positions[] = defaultStylePositions;





    /** Number of styles. */


    int nstyles = 2;





    StylePool() {


    }





    Style getDefaultStyle() {


	Style def = styles[0];


	checkStyles();


	if (def == defaultStyleArray[0])


	    def = styles[0] = (Style) def.clone();


	return def;


    }





    private void checkStyles() {


	if (styles == defaultStyleArray) {


	    styles = new Style[2];


	    System.arraycopy(defaultStyleArray, 0, styles, 0, 2);


	}


    }





    /**


     * LENGTH bytes have been inserted into the buffer.  Adjust the


     * style positions as appropriate.


     */


    void notifyInsert(int pos, int length) {


	int positions[] = this.positions;





	for (int i = nstyles - 1; --i >= 0; ) {


	    if (positions[i] > pos)


		positions[i] += length;


	    else


		break;


	}


    }





    /**


     * LENGTH bytes have been deleted from the buffer.  Adjust the


     * styles appropriately.  This can be tricky when styles are


     * deleted.


     */


    void notifyDelete(int pos, int length) {


	int slot0 = findStyleIndexFor(pos);


	int slot1 = findStyleIndexFor(pos + length);





	if (positions[slot0] == pos)


	    slot0 -= 1;


	int adjust = slot0 + 1;


	if (slot0 < slot1) {


	    if (slot1 - slot0 > 1)


		delete(slot0 + 1, slot1 - 1);


	    adjust = slot0 + 2;


	    positions[slot0 + 1] = pos;


	}


	int limit = nstyles - 1;


	while (adjust < limit)


	    positions[adjust++] -= length;





	/* now try to merge adjascent styles if they are the same */


	while (nstyles > 2 && styles[slot0].equals(styles[slot0 + 1]))


	    delete(slot0 + 1, slot0 + 1);


    }	    





    /**


     * Returns the index of the first style whose position is >= to


     * the specified position.


     */


    public int findStyle(int pos) {


	int hi = this.nstyles - 1;


	int lo = 0;





	if (hi == 0)


	    return 0;


	while (hi - lo > 1) {


	    int mid = (hi + lo) / 2;


	    int val = positions[mid];





	    if (val < pos)


		lo = mid;


	    else if (val >= pos)


		hi = mid;


	}


	return (positions[lo] >= pos) ? lo : lo + 1;


    }





    /**


     * This returns the index of the style that is responsbile for the


     * specified position.


     */


    public int findStyleIndexFor(int pos) {


	int index = findStyle(pos);


	if (positions[index] == pos)


	    return index;


	return index - 1;


    }





    /** Returns the style at the specified index. */


    public Style getStyleAt(int index) {


	return styles[index];


    }





    /** Returns the position of the style at the specified index. */


    public int getStylePosAt(int index) {


	return positions[index];


    }





    /** Insert a style object for the specified buffer position. */


    int addStyle(Style s, int pos) {


	if (nstyles >= styles.length) {


	    Style ns[] = new Style[Math.max((int) (styles.length * 1.5), 10)];


	    int npos[] = new int[ns.length];





	    System.arraycopy(styles, 0, ns, 0, styles.length);


	    System.arraycopy(positions, 0, npos, 0, styles.length);


	    styles = ns;


	    positions = npos;


	}


	int slot;


	if (nstyles == 0) {


	    slot = 0;


	} else {


	    slot = findStyle(pos);


	    if (pos == positions[slot]) {


		styles[slot] = s;


		return 0;


	    }


	    if (slot > 0 && styles[slot - 1].equals(s))


		return 0;


	    System.arraycopy(styles, slot, styles, slot + 1,


			     nstyles - slot);


	    System.arraycopy(positions, slot, positions, slot + 1,


			     nstyles - slot);


	}


	styles[slot] = s;


	positions[slot] = pos;


	nstyles += 1;





	return 1;


    }





    /** Delete from slot0 to slot1 inclusive. */


    void delete(int slot0, int slot1) {


	int count = 1 + slot1 - slot0;





	System.arraycopy(styles, slot1 + 1, styles, slot0,


			 nstyles - slot1 - 1);


	System.arraycopy(positions, slot1 + 1, positions, slot0,


			 nstyles - slot1 - 1);


	nstyles -= count;


    }





    public void deleteStyles(Text text, int pos0, int pos1) {


	int slot0 = findStyle(pos0);


	int slot1 = findStyle(pos1);


	delete(slot0, slot1 - 1);


	text.notifyDirtyRegion(pos0, pos1);


    }





    /**


     * Apply a StyleChange to a region of text.  The only tricky part


     * is at the end points of the region.  The styles at those


     * positions are split in two, unless pos0 and/or pos1 is already


     * right on a style boundery.


     */


    boolean applyStyle(StyleChange sc, int pos0, int pos1) {


	if (pos0 == pos1) {


	    return false;


	}





	checkStyles();


	int slot0 = findStyleIndexFor(pos0);


	int slot1 = findStyleIndexFor(pos1);


	Style s0;


	Style s1;


	Style news;





	s0 = styles[slot0];


	s1 = styles[slot1];





	news = sc.makeStyleFrom(s0);


	


	if (slot0 == slot1 ||


	    (slot0 + 1 == slot1 && positions[slot1] == pos1)) {


	    if (s0.equals(news)) {


		/* nothing to do */


		//System.out.println("Not applying style");


		return false;


	    } else {


		//System.out.println(s0 + " != " + news);


	    }


	}





	int off = addStyle(news, pos0);


	slot0 += off;


	slot1 += off;





	while (++slot0 < slot1) {


	    styles[slot0] = sc.makeStyleFrom(styles[slot0]);


	    if (styles[slot0 - 1].equals(styles[slot0])) {


		delete(slot0, slot0);


		slot1 -= 1;


		slot0 -= 1;


	    }


	}


	if (pos1 > positions[slot1]) {


	    styles[slot1] = sc.makeStyleFrom(styles[slot1]);


	    if (slot1 > 0 && styles[slot1 - 1].equals(styles[slot1]))


		delete(slot1, slot1);


	    addStyle(s1, pos1);


	}


	return true;


    }





    /**


     * Adjust the styles in the style pool based on a new default (or


     * initial) style.  All styles which have the same attributes of


     * the default style are modified to look like the new default


     * style.  Only compare the font and color attributes at this


     * point.


     */


    public void setDefaultStyle(Style s) {


	Style def = getDefaultStyle();


	int index = 1;


	int limit = nstyles - 1;


	Style styles[] = this.styles;





	if (def.family == s.family && def.fontFace == s.fontFace &&


	    def.fontSize == s.fontSize && def.color.equals(s.color))


	    return;


	styles[0] = s;


	while (index < limit) {


	    Style current = styles[index++];


	    if (current.family == def.family)


		current.family = s.family;


	    if (current.fontFace == def.fontFace)


		current.fontFace = s.fontFace;


	    if (current.fontSize == def.fontSize)


		current.fontSize = s.fontSize;


	    if (current.color.equals(def.color))


		current.color = s.color;


	    if (current.wrapStyle == def.wrapStyle)


		current.wrapStyle = s.wrapStyle;


	    if (current.justifyStyle == def.justifyStyle)


		current.justifyStyle = s.justifyStyle;


	}


    }





    /**


     * Write the styles to the specified output stream, using the


     * specified text data.


     */


    public void writeStyles(byte data[], int pos0, int pos1,


			    OutputStream out) 


    throws IOException {


	Hashtable ht = new Hashtable();


	int index = findStyleIndexFor(pos0);


	Style lastStyle;


	Style s;


	int styleCount = 0;


	DataOutputStream dout = new DataOutputStream(out);





	/* everything is relative to the root/default style */


	s = lastStyle = styles[0];


	ht.put(s, new Integer(styleCount++));





	while (pos0 < pos1) {


	    if (index > 0) {


		s = styles[index];


		Integer slot = (Integer) ht.get(s);


		out.write('{');


		if (slot == null) {


		    ht.put(s, new Integer(styleCount++));


		    dout.writeBytes(s.toString(lastStyle));


		} else


		    dout.writeBytes(String.valueOf(slot.intValue()));


		out.write('}');


		lastStyle = s;		    


	    }


	    int next = positions[++index];


	    if (next > pos1)


		next = pos1;


	    while (pos0 < next) {


		int c = data[pos0];


		switch (c) {


		  case '{':


		  case '}':


		  case '\\':


		    out.write('\\');


		    /* falls into ... */





		  default:


		    out.write(c);


		}


		pos0 += 1;


	    }


	}


    }





    /**


     * Read a bunch of text, with embedded style references, into this


     * style pool.  The text characters are inserted into the


     * specified ByteString.


     */


    public void readStyles(ByteString bs, InputStream in) throws IOException {


	Style array[] = new Style[10];


	int count = 0;


	int c;


	int pos = 0;


	Style style;


	byte data[] = new byte[8];


	int dataLength = 0;


	FastOutputStream out = new FastOutputStream();





	/* everything is relative to the root/default style */


	style = array[count++] = styles[0];


	while ((c = in.read()) != -1) {


	    switch (c) {


	      case '{':		/* parse a style */


		if ((c = in.read()) >= '0' && c <= '9') {


		    int i = c - '0';


		    while ((c = in.read()) >= 0 && c <= '9')


			i = i * 10 + c - '0';


		    if (c != '}')


			throw new RuntimeException("Bad format");


		    style = array[i];


		} else {


		    if (style == null)


			style = new Style().fromStream(c, in);


		    else


			style = ((Style) style.clone()).fromStream(c, in);


		    /* fromStream reads the '}' */


		    if (count == array.length) {


			Style aa[] = new Style[count + 10];


			System.arraycopy(array, 0, aa, 0, count);


			array = aa;


		    }


		    array[count++] = style;


		}


		addStyle(style, pos);


		continue;





	      case '\\':


		if ((c = in.read()) == -1)


		    throw new RuntimeException("Unexpected EOF");





	      default:


		out.write(c);


		pos += 1;


		break;


	    }


	}


	bs.length = out.size();


	bs.data = out.toByteArray();


    }





    void dumpStyles() {


	System.out.println(nstyles + " styles");


	for (int i = 0; i < nstyles; i++)


	    System.out.println(i + "@" + positions[i] + " = " +


			       styles[i]);


    }


}


