/*--- formatted by Jindent 2.1, (www.c-lab.de/~jindent) ---*/

/**
 * ***************************************************************
 * The LEAP libraries, when combined with certain JADE platform components,
 * provide a run-time environment for enabling FIPA agents to execute on
 * lightweight devices running Java. LEAP and JADE teams have jointly
 * designed the API for ease of integration and hence to take advantage
 * of these dual developments and extensions so that users only see
 * one development platform and a
 * single homogeneous set of APIs. Enabling deployment to a wide range of
 * devices whilst still having access to the full development
 * environment and functionalities that JADE provides.
 * Copyright (C) 2001 Telecom Italia LAB S.p.A.
 * Copyright (C) 2001 Broadcom Eireann Research.
 * Copyright (C) 2001 Siemens AG.
 *
 * GNU Lesser General Public License
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation,
 * version 2.1 of the License.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307, USA.
 * **************************************************************
 */

package jade.imtp.leap.JICP;

import java.io.ByteArrayOutputStream;

/**
 * Class declaration
 * @author Dmitri Toropov - Siemens AG
 */

 class JICPCompressor2 {
  /**
   * check if the symbol is separator
   *
   * @param sym - byte to check
   * @return true if sym is separator, false otherwise
   *
   */
  public static boolean is_separator(byte sym)
  {
    return (sym <= 32) || (sym == '(') || (sym == ')') || (sym == '@') || (sym == ':') || (sym == '/') || (sym == '!');
  }

  /**
   * operation +1 by modulo 253
   */
  private static int next(int val)
  {
	return (val==252)?(0):(val+1);
  }

  /**
   * subtraction by modulo 253
   */
  private static int back(int val, int num)
  {
	return (num>val)?(253+val-num):(val-num);
  }

  /**
   * sum by modulo 253
   */
  private static int sum(int val, int num)
  {
	return (num + val >= 253)?(val + num - 253):(val + num);
  }

  /**
   * normal mimimum function
   */
  private static int min(int a, int b)
  {
	return (a > b)?(b):(a);
  }

  /**
   * Compress the byte array
   *
   * @param data the data to compress
   * @return compressed data
   *
   */
  public static byte[] compress(byte[] data) {
    if (data == null) return null;
    int dataSize=data.length;
    byte[] result=null;
    try {
    int[] words = new int[min(dataSize+1,253)];
	      //words are interleaved with separators
	      //minimal lenth of each word or separator is a 1 symbol
	      //this way maximal number of words is lenghth/2
	      //(+ may be 1 if the lenght is odd)
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    stream.write(dataSize/256); //first 2 bytes is the array length
    stream.write(dataSize%256);
    int numWords=0;
	int pos=0;
    boolean inWord=false;
    boolean dataEnded=false;
    while((pos < dataSize)&&(numWords < 126))
    {
      if(is_separator(data[pos]) || !inWord)
      {
	inWord=!is_separator(data[pos]);
	words[numWords]=pos;
	numWords++;
      }
	  pos++;
    }
	if (numWords < 126) //pos==dataSize
    {
      dataEnded = true;
      words[numWords]=pos;
    }
    int countWords=0;
    int charCounter=0;
    boolean startFlag = true;
    for(int i=0; i!=numWords; i=next(i))   //looking through the words; the first word is for sure not compressed
    {
      if (!dataEnded)
      {
	while((pos < dataSize)&&(!(is_separator(data[pos]) || !inWord))) pos++;
	if ((pos < dataSize)&&(is_separator(data[pos]) || !inWord))
	{
	  inWord = !is_separator(data[pos]);
	  words[numWords]=pos;
	  numWords = next(numWords);
	  pos++;
	}
	else //pos == dataSize
	{
	  words[numWords]=pos;
	  dataEnded = true;
	    }
	  }
      int j;
      for(j=(startFlag && (i<126))?(0):(back(i,126)); j!=i; j=next(j))	//trying to find the occurance of the current word; the history lenght is 126 words
      {
	countWords=charCounter=0;
	while( (words[i]+charCounter < dataSize - 1) && 		    //if the position with which we are comparing
									    //is not yet achieved the end of the array
	       (words[j]+charCounter < words[i]) &&			    //and the search position not yet achieved
									    //the current position
	       (data[words[i]+charCounter]==data[words[j]+charCounter]))    //and the characters are the same
	{
	  charCounter++;
	  if( (sum(j,countWords+1) != i) &&
	      (words[j]+charCounter >= words[sum(j,countWords+1)]) &&
	      (sum(i,countWords+1) != numWords) &&
	      (words[i]+charCounter >= words[sum(i,countWords+1)]) )
	    countWords++;  //if we achieved the next word boundary
									  //'+1' means that we count the word only when we
									  //have aready check all of its symbols
									  //(each space is one symbol word)
	}
	if(countWords > 0) //the correspondance was found
	{
	  if((countWords == 1)&&(words[next(i)]-words[i]<=2))
	  {
	    int k;
	    for(k=0; k< words[next(i)]-words[i]; k++)
	    {
	      if(data[words[i]+k]>=0) stream.write(data[words[i]+k]);
	      else
	      {
		stream.write(-127);
		stream.write(data[words[i]+k]);
	      }
	    }
	  }
	  else
	  {
	    stream.write(-back(i,j)); //offset (backward) of the position where to find this word
	    stream.write(-countWords); //if we have more then 1 word - write the number
	    i=sum(i,countWords-1);
	  }
	  break; //exit the internal for()
	}
      }
      if (countWords == 0)
      {
	for(j=0; j< words[next(i)]-words[i]; j++)
	{
	  if(data[words[i]+j]>=0) stream.write(data[words[i]+j]);
	  else
	  {
	    stream.write(-127);
	    stream.write(data[words[i]+j]);
	  }
	}
      }
      if(i==252) startFlag=false;
    }
    result = stream.toByteArray();
    /**************************
    System.out.print("Uncompressed: ");
    System.out.print(dataSize);
    System.out.print(" Compressed: ");
    System.out.println(result.length);
    System.out.flush();
    //*************************/
//    System.out.println("" + dataSize + "->" + result.length + " = " + (result.length*100)/dataSize+"%");
    return result;
    }
    catch(Exception e)
    {
      System.out.println("Compression exception:"+e.toString());
      e.printStackTrace();
    }
    return null;
  }

  /**
   * Decompress the byte array
   *
   * @param data the data to compress
   * @return compressed data
   *
   */
  public static byte[] decompress(byte[] data) {
    if (data == null) return null;
    int dataSize = data.length;
    int outputSize = ((data[0]>=0)?(data[0]):(256 + data[0]))*256 + ((data[1]>=0)?(data[1]):(256 + data[1]));
    //System.out.print("Data size ");
    //System.out.println(outputSize);
    byte[] output = new byte[outputSize]; //first byte is the array length
    int[] words = new int[min(outputSize,253)];
	      //words are interleaved with separators
	      //minimal lenth of each word or separator is a 1 symbol
	      //this way maximal number of words is lenghth/2
	      //(+ may be 1 if the lenght is odd)
    int numWords=0;
    int j=0;
    int i=2; //data[0] and data[1] are not a compressed data but a size
    try {
    i=2;
    while(i < dataSize)
    {
      if( (data[i] < 0) && (data[i] > -127) )
      {
	int wordsCount = (((i+1 < dataSize)&&(data[i+1] < 0)&&(data[i+1] > -127))?(-data[i+1]):1);
	int symCount = words[sum(back(numWords,(-data[i])), wordsCount)] - words[back(numWords,(-data[i]))];
	int k;
	for(k=0; k < symCount; k++)
	{
	  output[j+k] = output[words[back(numWords,(-data[i]))] + k];
	}
	for(k=0; k < wordsCount; k++)
	  words[sum(numWords,k)] = words[sum(back(numWords, (-data[i])), k)] - words[back(numWords, (-data[i]))]+ j;
	j+=symCount;
	numWords=sum(numWords,wordsCount);
	i+=2;
      }
      else
      {
	if(!is_separator(data[i])) //not a separator
	{
	  words[numWords]=j;
	  numWords=next(numWords);
	  output[j]=data[i];
	  i++;
	  j++;
	}
	while( (i<dataSize)&&(!is_separator(data[i])) ) //limit ourself to the case where all the symbols have codes < 128
	{
	  output[j]=data[i];
	  i++;
	  j++;
	}
	while( (i< dataSize) && (is_separator(data[i])) && ((data[i]>=0)||(data[i] == -127)) ) //copying the separators
	{
	  if(data[i] == -127) i++;
	  words[numWords]=j;
	  numWords=next(numWords);
	  output[j]=data[i];
	  i++;
	  j++;
	}
      }
    }
    return output;
    }
	catch(Exception e)
    {
      System.out.println("Decompression exception:"+e.toString());
	  e.printStackTrace();
    }
    return null;
  }
}
