package cz.cuni.jagrlib.testing;

import java.io.IOException;
import java.io.StringWriter;

import cz.cuni.jagrlib.*;
import cz.cuni.jagrlib.iface.*;
import cz.cuni.jagrlib.reg.*;

/**
 * B/W file format (using simple Entropy codec to encode raster data).
 * @version 0.24 $Rev: 160 $ $Date: 2005-12-21 04:39:30 +0100 (st, 21 XII 2005) $ $Author: pepca $
 * @since   0.24
 * @see     <a href="../../../../../cz/cuni/jagrlib/testing/CompressedFormatBW.java">CompressedFormatBW.java</a>
 */
public class CompressedFormatBWAndrejKrutak extends DefaultFileFormat
{
  //==========================================================================
  //  Data:

	int mins=32;
	
	class QuadTree {
		QuadTree sub[];
		byte data[];
		int color;
		QuadTree() {
			sub=null;
			data=null;
			color=-1;
		}
	};
	
	StringWriter sw;
	
	QuadTree createQT(RasterGraphics g, int x, int y, int s) {
		QuadTree rv;
		int i, j;
		rv=new QuadTree();
		if (mins>=s) { //we reached minimum box size
			rv.sub=null;
			rv.color=g.getGray(x,y) >= 128 ? 1 : 0;
			rv.data=null;
			int w, h;
			w=g.getWidth();
			h=g.getHeight();
			for (i=0; i<s; i++) { //check whether it's one-colored
				if (rv.color==2) break;
				for (j=0; j<s; j++) {
					if (rv.color==2) break;
					if ((x+i<w && y+j<h) && (rv.color!=(g.getGray(x+i,y+j) >= 128 ? (byte)1 : (byte)0))) rv.color=2;
				}
			}
			
			if (rv.color==2) {
				rv.data=new byte[s*s];
				for (i=0; i<s; i++) for (j=0; j<s; j++) rv.data[j*s+i]=(g.getGray(x+i,y+j) >= 128 ? (byte)1 : (byte)0);
			}
		} else {
			rv.data=null;
			rv.sub=new QuadTree[4];
			rv.sub[0]=createQT(g, x,     y,     s/2);
			rv.sub[1]=createQT(g, x+s/2, y,     s/2);
			rv.sub[2]=createQT(g, x,     y+s/2, s/2);
			rv.sub[3]=createQT(g, x+s/2, y+s/2, s/2);
			rv.color=rv.sub[0].color;
			for (i=1; i<4; i++) if (rv.sub[i].color!=rv.color) {rv.color=5; break;}
			if (rv.color==0 || rv.color==1) rv.sub=null;
			else if (rv.color==5||rv.color==2) rv.color=3;
		}
		return rv;
	}
	
	void outputData(int d, int l) {
		sw.write(d);
		sw.write(l);
	}
	
	void saveQT(QuadTree qt, int x, int y, int w, int h, int s) {
		int i, j;
		
		if (x>w || y>h) return; //we're out of range, ignore tree data
		
		//System.out.println("saveQT: "+x+", "+y+": "+w+"/"+h+"/"+s+" => "+qt.color);
		if (qt.color==-1) System.out.println("-1");
		//System.out.println(qt.color);
		outputData(qt.color, 2);
		if (qt.color==2) { //we have data
			if (qt.data==null) System.out.println(x+", "+y+"x"+s+": data null!!");
			for (i=0; i<s; i++) {
				for (j=0; j<s; j++) {
					if (x+i<w && y+j<h) outputData(qt.data[j*s+i], 1);
				}
			}
		} else if (qt.color==3) { //we have subquadtrees
			if (qt.sub==null) System.out.println(x+", "+y+"x"+s+": sub null!!");
			saveQT(qt.sub[0], x,     y,     w, h, s/2);
			saveQT(qt.sub[1], x+s/2, y,     w, h, s/2);
			saveQT(qt.sub[2], x    , y+s/2, w, h, s/2);
			saveQT(qt.sub[3], x+s/2, y+s/2, w, h, s/2);
		}
	};
	
	EntropyCodec f;
	long rd, rl;
	
	int mygetBits(int no)
	throws IOException
	{
		int rv=0, c;

		if (rl<no) { //we need to read from file
			c=f.get();
			//System.out.println("read: "+c+"="+(c&0xff));
			rd=rd*256+(int)c&0xff;
			rl+=8;
		}
		if (no==1) rv=(int)((rd>>(rl-1))&0x01);
		else if (no==2) rv=(int)((rd>>(rl-2))&0x03);
		else System.out.println("BAD NO OF BITS!");
		
		//System.out.print(rd+"/"+rl+" => "+rv+"/"+no+" - "+(rv<<(rl-no)));
		//for (int i=7; i>=0; i--) System.out.print(((rd&(0x01<<i))>>i)&0x01);
		//System.out.println("");
		rd=rd-(rv<<(rl-no));//(rd>>no)&0xff;
		rl-=no;
		return rv;
	}
	
	void renderQT(int x, int y, int w, int h, int s, RasterGraphics g)
	throws IOException
	{
		int i, j;
		long c;
		
		if (x>w || y>h) return; //we're out of range, don't read anything
		
		c=mygetBits(2);
		//System.out.println(c);
		if (c<2) { //paint with the color of parent box
			for (i=0; i<s; i++) for (j=0; j<s; j++) g.putPixel(x+i, y+j, (c==0) ? 0 : 255 );
		} else if (c==2) {
			for (i=0; i<s; i++) for (j=0; j<s; j++) if (x+i<w && y+j<h) g.putPixel(x+i, y+j, (mygetBits(1)==0)?0:255);
		} else { //c==3
			renderQT(x,     y,     w, h, s/2, g);
			renderQT(x+s/2, y,     w, h, s/2, g);
			renderQT(x    , y+s/2, w, h, s/2, g);
			renderQT(x+s/2, y+s/2, w, h, s/2, g);
		}
	}
	
  //--------------------------------------------------------------------------
  //  Support routines:

  /**
   * Common load code.
   * @param  stream Opened input bit-stream.
   * @param  g Checked raster-graphics object.
   * @throws IOException
   */
  protected void commonLoad ( BitStream stream, RasterGraphics g )
  throws IOException
  {
    f = (EntropyCodec)getInterface(PL_OUTPUT,IFACE+"EntropyCodec");
    if ( f == null ) return;

    f.open( false );
    f.setMaxSymbol( 255 );

      // BW header:
    int magic = (int)f.getBits( 8 ) << 8;
    magic += (int)f.getBits( 8 );
    if ( magic != MAGIC )
      throw new IOException( "Error in BW file format" );
    int width = (int)f.getBits( 8 ) << 8;
    width += (int)f.getBits( 8 );
    int height = (int)f.getBits( 8 ) << 8;
    height += (int)f.getBits( 8 );

    // initialize raster image object:
    g.init( width, height, RasterGraphics.MODE_GRAY, 0 );
    rd=0; rl=0;
    
	int w=1, h=1, s, i, j;
	for (i=0; i<32; i++) {
		if (w<width) w*=2;
		if (h<height) h*=2;
	}
	if (w>h) s=w; else s=h;

	renderQT(0, 0, width, height, s, g);

    f.close();
  }

  /**
   * Common save code.
   * @param  stream Opened output bit-stream.
   * @param  g Checked raster-graphics object.
   * @throws IOException
   */
  protected void commonSave ( BitStream stream, RasterGraphics g )
  throws IOException
  {
    EntropyCodec codec = (EntropyCodec)getInterface(PL_OUTPUT,IFACE+"EntropyCodec");
    if ( codec == null ) return;

    codec.open(true);
    codec.setMaxSymbol( 255 );            // 8 pixels per byte

      // BW header:
    codec.putBits( (MAGIC>>8) & 0xff, 8 );
    codec.putBits(  MAGIC     & 0xff, 8 );
    int width = g.getWidth();
    codec.putBits( (width>>8) & 0xff, 8 );
    codec.putBits(  width     & 0xff, 8 );
    int height = g.getHeight();
    codec.putBits( (height>>8) & 0xff, 8 );
    codec.putBits(  height     & 0xff, 8 );


	//System.out.println("AK");
	//////////////////////////////////
	//make quadtree
	
	//1) round w/h to neares power of 2
	int w=1, h=1, s, i, j;
	for (i=0; i<32; i++) {
		if (w<width) w*=2;
		if (h<height) h*=2;
	}
	if (w>h) s=w; else s=h;
	
	//2) make the tree
	QuadTree qt=createQT(g, 0, 0, s);

	//3) convert to a bytestream (and cut out unused leafs)
	sw=new StringWriter();
	saveQT(qt, 0, 0, width, height, s);
	
	//4) write out the bytestream
	StringBuffer sb=sw.getBuffer();
	int dd=0, dl=0;
	int xd, xl;
	j=0;
	for (i=0; i<sb.length()-1; i+=2) {
		xd=sb.charAt(i);
		xl=sb.charAt(i+1);
		/*if (xl==2) {
			if (xd==0) System.out.print("00");
			else if (xd==1) System.out.print("01");
			else if (xd==2) System.out.print("10");
			else if (xd==3) System.out.print("11");
		} else System.out.print(xd);*/
		dd=(dd<<xl) + xd;
		dl+=xl;
		if (dl>=8) {
			codec.put(dd&0xff);
			//System.out.println("=> "+(dd&0xff));
			dd=dd>>>8;
			dl-=8;
			j++;
		}
	}
	if (dl>0) {
		codec.put((dd&0xff)<<(8-dl));
		j++;
	}

	codec.close();
  }

  //==========================================================================
  //  Interface Property:

  /**
   * Sets the given property.
   * @param  key Key string.
   * @param  value The new value.
   */
  public void set ( String key, Object value )
  {
    if ( key == null || value == null ) return;
      // !!! not yet !!!
  }

  /**
   * Gets the given property.
   * @param  key Key string.
   * @return The actual value or <code>null</code>.
   */
  public Object get ( String key )
  {
    if ( key == null )
      return null;
      // !!! not yet !!!
    return null;
  }

  //==========================================================================
  //  Interface DataFileFormat:

  /// 16-bit magic number (used at the file beginning).
  public final static int MAGIC = 0xadee;

  /**
   * Length of file-format header (number of bytes which is necessary for file-format matching).
   * @return Number of bytes from beginning of a file which are enough for file-format
   *         match test.
   * @see    #match
   */
  public int headerLength ()
  {
    return 2;
  }

  /**
   * File-format match test. Guesses whether the binary file-header (and file-name) can belong
   * to this file-format type.
   * @param  header Bytes from beginning of the file.
   * @param  fileName Optional file-name string.
   * @return Probability of being my file-format type (<code>0.0</code> .. no way,
   *         <code>0.5</code> .. maybe, <code>0.9</code> .. almost sure, <code>1.0</code> ..
   *         absolutely - no need to check another formats).
   * @see    #headerLength
   * @see    #fileNameMasks
   */
  public double match ( byte[] header, String fileName )
  {
    int len = (header == null) ? 0 : header.length;
    if ( len > 2 ) len = 2;
    if ( len < 2 )                        // not enough bytes in header[]
      return 0.0;
    byte[] h =
    {                                     // BW header
      (byte)((MAGIC>>8) & 0xff),
      (byte)( MAGIC     & 0xff)
    };
    int i;
    for ( i = 0; i < len; i++ )
      if ( header[i] != h[i] ) return 0.0;
    return 1.0;
  }

  /**
   * Returns file-name masks associated with the file-format type. Needs not be implemented
   * in systems where &quot;file-name -&gt; file-type&quot; mapping is irrelevant.
   * @return Array of wild-card file-name masks (e.g. [&quot;*.png&quot;]) or <code>null</code>.
   * @see    #match
   */
  public String[] fileNameMasks ()
  {
    return new String[]
    {
      "*.bw",
    };
  }

  //==========================================================================
  //  Module registration:

  /** Object name. */
  private final static String NAME = "BW file format template";

  /** Object template identifier. */
  protected final static String TEMPLATE_NAME = "DataFileFormatToRasterGraphicsAndEntropyCodecAndBitStream";

  /** Object description. */
  private final static String DESCRIPTION = "BW filter - trivial implementation.";

  /**
   * General-purpose registration routine.
   * Sets all plugs, strings, etc. to the given Template.
   */
  public static int setTemplate ( Template t, int ord )
  {
    if ( t != null && ord <= 0 )
    {
      t.setRegStrings(NAME,TEMPLATE_NAME,CATEGORY,DESCRIPTION);
        // Plugs:
      t.newInputPlug(PL_INPUT,IFACE+"DataFileFormat");
      t.newOptOutputPlug(PL_RASTER,IFACE+"RasterGraphics");
      t.newOptOutputPlug(PL_STREAM,IFACE+"BitStream");
      t.newOptOutputPlug(PL_OUTPUT,IFACE+"EntropyCodec");
        // Property:
        // !!! none yet !!!
    }
    return 1;
  }

  /**
   * Static registration instance for this class.
   * Automatically initialized in class-loading time.
   */
  public static final RegPiece reg;
  static
  {
    reg = new RegPiece();
    setTemplate(reg,0);
  }

}
