/*
===============================================================================

  FILE:  lasreaditemcompressed_v1.cpp
  
  CONTENTS:
  
    see corresponding header file
  
  PROGRAMMERS:

    martin.isenburg@rapidlasso.com  -  http://rapidlasso.com

  COPYRIGHT:

    (c) 2007-2014, martin isenburg, rapidlasso - fast tools to catch reality

    This is free software; you can redistribute and/or modify it under the
    terms of the GNU Lesser General Licence as published by the Free Software
    Foundation. See the COPYING file for more information.

    This software is distributed WITHOUT ANY WARRANTY and without even the
    implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  
  CHANGE HISTORY:
  
    see corresponding header file
  
===============================================================================
*/

#include "lasreaditemcompressed_v1.hpp"
#include "laszip_common_v1.hpp"

#include <assert.h>
#include <string.h>

/*
===============================================================================
                       LASreadItemCompressed_POINT10_v1
===============================================================================
*/

struct LASpoint10
{
  I32 x;
  I32 y;
  I32 z;
  U16 intensity;
  U8 return_number : 3;
  U8 number_of_returns_of_given_pulse : 3;
  U8 scan_direction_flag : 1;
  U8 edge_of_flight_line : 1;
  U8 classification;
  I8 scan_angle_rank;
  U8 user_data;
  U16 point_source_ID;
};

LASreadItemCompressed_POINT10_v1::LASreadItemCompressed_POINT10_v1(ArithmeticDecoder* dec)
{
  U32 i;

  /* set decoder */
  assert(dec);
  this->dec = dec;

  /* create models and integer compressors */
  ic_dx = new IntegerCompressor(dec, 32);  // 32 bits, 1 context
  ic_dy = new IntegerCompressor(dec, 32, 20); // 32 bits, 20 contexts
  ic_z = new IntegerCompressor(dec, 32, 20);  // 32 bits, 20 contexts
  ic_intensity = new IntegerCompressor(dec, 16);
  ic_scan_angle_rank = new IntegerCompressor(dec, 8, 2);
  ic_point_source_ID = new IntegerCompressor(dec, 16);
  m_changed_values = dec->createSymbolModel(64);
  for (i = 0; i < 256; i++)
  {
    m_bit_byte[i] = 0;
    m_classification[i] = 0;
    m_user_data[i] = 0;
  }
}

LASreadItemCompressed_POINT10_v1::~LASreadItemCompressed_POINT10_v1()
{
  U32 i;

  delete ic_dx;
  delete ic_dy;
  delete ic_z;
  delete ic_intensity;
  delete ic_scan_angle_rank;
  delete ic_point_source_ID;
  dec->destroySymbolModel(m_changed_values);
  for (i = 0; i < 256; i++)
  {
    if (m_bit_byte[i]) dec->destroySymbolModel(m_bit_byte[i]);
    if (m_classification[i]) dec->destroySymbolModel(m_classification[i]);
    if (m_user_data[i]) dec->destroySymbolModel(m_user_data[i]);
  }
}

BOOL LASreadItemCompressed_POINT10_v1::init(const U8* item)
{
  U32 i;

  /* init state */
  last_x_diff[0] = last_x_diff[1] = last_x_diff[2] = 0;
  last_y_diff[0] = last_y_diff[1] = last_y_diff[2] = 0;
  last_incr = 0;

  /* init models and integer compressors */
  ic_dx->initDecompressor();
  ic_dy->initDecompressor();
  ic_z->initDecompressor();
  ic_intensity->initDecompressor();
  ic_scan_angle_rank->initDecompressor();
  ic_point_source_ID->initDecompressor();
  dec->initSymbolModel(m_changed_values);
  for (i = 0; i < 256; i++)
  {
    if (m_bit_byte[i]) dec->initSymbolModel(m_bit_byte[i]);
    if (m_classification[i]) dec->initSymbolModel(m_classification[i]);
    if (m_user_data[i]) dec->initSymbolModel(m_user_data[i]);
  }

  /* init last item */
  memcpy(last_item, item, 20);

  return TRUE;
}

inline void LASreadItemCompressed_POINT10_v1::read(U8* item)
{
  // find median difference for x and y from 3 preceding differences
  I32 median_x;
  if (last_x_diff[0] < last_x_diff[1])
  {
    if (last_x_diff[1] < last_x_diff[2])
      median_x = last_x_diff[1];
    else if (last_x_diff[0] < last_x_diff[2])
      median_x = last_x_diff[2];
    else
      median_x = last_x_diff[0];
  }
  else
  {
    if (last_x_diff[0] < last_x_diff[2])
      median_x = last_x_diff[0];
    else if (last_x_diff[1] < last_x_diff[2])
      median_x = last_x_diff[2];
    else
      median_x = last_x_diff[1];
  }

  I32 median_y;
  if (last_y_diff[0] < last_y_diff[1])
  {
    if (last_y_diff[1] < last_y_diff[2])
      median_y = last_y_diff[1];
    else if (last_y_diff[0] < last_y_diff[2])
      median_y = last_y_diff[2];
    else
      median_y = last_y_diff[0];
  }
  else
  {
    if (last_y_diff[0] < last_y_diff[2])
      median_y = last_y_diff[0];
    else if (last_y_diff[1] < last_y_diff[2])
      median_y = last_y_diff[2];
    else
      median_y = last_y_diff[1];
  }

  // decompress x y z coordinates
  I32 x_diff = ic_dx->decompress(median_x);
  ((LASpoint10*)last_item)->x += x_diff;
  // we use the number k of bits corrector bits to switch contexts
  U32 k_bits = ic_dx->getK();
  I32 y_diff = ic_dy->decompress(median_y, (k_bits < 19 ? k_bits : 19));
  ((LASpoint10*)last_item)->y += y_diff;
  k_bits = (k_bits + ic_dy->getK())/2;
  ((LASpoint10*)last_item)->z = ic_z->decompress(((LASpoint10*)last_item)->z, (k_bits < 19 ? k_bits : 19));

  // decompress which other values have changed
  I32 changed_values = dec->decodeSymbol(m_changed_values);

  if (changed_values)
  {
    // decompress the intensity if it has changed
    if (changed_values & 32)
    {
      ((LASpoint10*)last_item)->intensity = (U16)ic_intensity->decompress(((LASpoint10*)last_item)->intensity);
    }

    // decompress the edge_of_flight_line, scan_direction_flag, ... if it has changed
    if (changed_values & 16)
    {
      if (m_bit_byte[last_item[14]] == 0)
      {
        m_bit_byte[last_item[14]] = dec->createSymbolModel(256);
        dec->initSymbolModel(m_bit_byte[last_item[14]]);
      }
      last_item[14] = (U8)dec->decodeSymbol(m_bit_byte[last_item[14]]);
    }

    // decompress the classification ... if it has changed
    if (changed_values & 8)
    {
      if (m_classification[last_item[15]] == 0)
      {
        m_classification[last_item[15]] = dec->createSymbolModel(256);
        dec->initSymbolModel(m_classification[last_item[15]]);
      }
      last_item[15] = (U8)dec->decodeSymbol(m_classification[last_item[15]]);
    }
    
    // decompress the scan_angle_rank ... if it has changed
    if (changed_values & 4)
    {
      last_item[16] = (U8)ic_scan_angle_rank->decompress(last_item[16], k_bits < 3);
    }

    // decompress the user_data ... if it has changed
    if (changed_values & 2)
    {
      if (m_user_data[last_item[17]] == 0)
      {
        m_user_data[last_item[17]] = dec->createSymbolModel(256);
        dec->initSymbolModel(m_user_data[last_item[17]]);
      }
      last_item[17] = (U8)dec->decodeSymbol(m_user_data[last_item[17]]);
    }

    // decompress the point_source_ID ... if it has changed
    if (changed_values & 1)
    {
      ((LASpoint10*)last_item)->point_source_ID = (U16)ic_point_source_ID->decompress(((LASpoint10*)last_item)->point_source_ID);
    }
  }

  // record the difference
  last_x_diff[last_incr] = x_diff;
  last_y_diff[last_incr] = y_diff;
  last_incr++;
  if (last_incr > 2) last_incr = 0;

  // copy the last point
  memcpy(item, last_item, 20);
}

/*
===============================================================================
                       LASreadItemCompressed_GPSTIME11_v1
===============================================================================
*/

#define LASZIP_GPSTIME_MULTIMAX 512

LASreadItemCompressed_GPSTIME11_v1::LASreadItemCompressed_GPSTIME11_v1(ArithmeticDecoder* dec)
{
  /* set decoder */
  assert(dec);
  this->dec = dec;
  /* create entropy models and integer compressors */
  m_gpstime_multi = dec->createSymbolModel(LASZIP_GPSTIME_MULTIMAX);
  m_gpstime_0diff = dec->createSymbolModel(3);
  ic_gpstime = new IntegerCompressor(dec, 32, 6); // 32 bits, 6 contexts
}

LASreadItemCompressed_GPSTIME11_v1::~LASreadItemCompressed_GPSTIME11_v1()
{
  dec->destroySymbolModel(m_gpstime_multi);
  dec->destroySymbolModel(m_gpstime_0diff);
  delete ic_gpstime;
}

BOOL LASreadItemCompressed_GPSTIME11_v1::init(const U8* item)
{
  /* init state */
  last_gpstime_diff = 0;
  multi_extreme_counter = 0;

  /* init models and integer compressors */
  dec->initSymbolModel(m_gpstime_multi);
  dec->initSymbolModel(m_gpstime_0diff);
  ic_gpstime->initDecompressor();

  /* init last item */
  last_gpstime.u64 = *((U64*)item);
  return TRUE;
}

inline void LASreadItemCompressed_GPSTIME11_v1::read(U8* item)
{
  I32 multi;
  if (last_gpstime_diff == 0) // if the last integer difference was zero
  {
    multi = dec->decodeSymbol(m_gpstime_0diff);
    if (multi == 1) // the difference can be represented with 32 bits
    {
      last_gpstime_diff = ic_gpstime->decompress(0, 0);
      last_gpstime.i64 += last_gpstime_diff;      
    }
    else if (multi == 2) // the difference is huge
    {
      last_gpstime.u64 = dec->readInt64();
    }
  }
  else
  {
    multi = dec->decodeSymbol(m_gpstime_multi);

    if (multi <  LASZIP_GPSTIME_MULTIMAX-2)
    {
      I32 gpstime_diff;
      if (multi == 1)
      {
        gpstime_diff = ic_gpstime->decompress(last_gpstime_diff, 1);
        last_gpstime_diff = gpstime_diff;
        multi_extreme_counter = 0;
      }
      else if (multi == 0)
      {
        gpstime_diff = ic_gpstime->decompress(last_gpstime_diff/4, 2);
        multi_extreme_counter++;
        if (multi_extreme_counter > 3)
        {
          last_gpstime_diff = gpstime_diff;
          multi_extreme_counter = 0;
        }
      }
      else if (multi < 10)
      {
        gpstime_diff = ic_gpstime->decompress(multi*last_gpstime_diff, 3);
      }
      else if (multi < 50)
      {
        gpstime_diff = ic_gpstime->decompress(multi*last_gpstime_diff, 4);
      }
      else
      {
        gpstime_diff = ic_gpstime->decompress(multi*last_gpstime_diff, 5);
        if (multi == LASZIP_GPSTIME_MULTIMAX-3)
        {
          multi_extreme_counter++;
          if (multi_extreme_counter > 3)
          {
            last_gpstime_diff = gpstime_diff;
            multi_extreme_counter = 0;
          }
        }
      }
      last_gpstime.i64 += gpstime_diff;
    }
    else if (multi <  LASZIP_GPSTIME_MULTIMAX-1)
    {
      last_gpstime.u64 = dec->readInt64();
    }
  }
  *((I64*)item) = last_gpstime.i64;
}

/*
===============================================================================
                       LASreadItemCompressed_RGB12_v1
===============================================================================
*/

LASreadItemCompressed_RGB12_v1::LASreadItemCompressed_RGB12_v1(ArithmeticDecoder* dec)
{
  /* set decoder */
  assert(dec);
  this->dec = dec;

  /* create models and integer compressors */
  m_byte_used = dec->createSymbolModel(64);
  ic_rgb = new IntegerCompressor(dec, 8, 6);

  /* create last item */
  last_item = new U8[6];
}

LASreadItemCompressed_RGB12_v1::~LASreadItemCompressed_RGB12_v1()
{
  dec->destroySymbolModel(m_byte_used);
  delete ic_rgb;
  delete [] last_item;
}

BOOL LASreadItemCompressed_RGB12_v1::init(const U8* item)
{
  /* init state */

  /* init models and integer compressors */
  dec->initSymbolModel(m_byte_used);
  ic_rgb->initDecompressor();

  /* init last item */
  memcpy(last_item, item, 6);
  return TRUE;
}

inline void LASreadItemCompressed_RGB12_v1::read(U8* item)
{
  U32 sym = dec->decodeSymbol(m_byte_used);
  if (sym & (1 << 0)) ((U16*)item)[0] = (U16)ic_rgb->decompress(((U16*)last_item)[0]&255, 0);
  else ((U16*)item)[0] = (U16)(((U16*)last_item)[0]&0xFF);
  if (sym & (1 << 1)) ((U16*)item)[0] |= (((U16)ic_rgb->decompress(((U16*)last_item)[0]>>8, 1)) << 8);
  else ((U16*)item)[0] |= (((U16*)last_item)[0]&0xFF00);
  if (sym & (1 << 2)) ((U16*)item)[1] = (U16)ic_rgb->decompress(((U16*)last_item)[1]&255, 2);
  else ((U16*)item)[1] = (U16)(((U16*)last_item)[1]&0xFF);
  if (sym & (1 << 3)) ((U16*)item)[1] |= (((U16)ic_rgb->decompress(((U16*)last_item)[1]>>8, 3)) << 8);
  else ((U16*)item)[1] |= (((U16*)last_item)[1]&0xFF00);
  if (sym & (1 << 4)) ((U16*)item)[2] = (U16)ic_rgb->decompress(((U16*)last_item)[2]&255, 4);
  else ((U16*)item)[2] = (U16)(((U16*)last_item)[2]&0xFF);
  if (sym & (1 << 5)) ((U16*)item)[2] |= (((U16)ic_rgb->decompress(((U16*)last_item)[2]>>8, 5)) << 8);
  else ((U16*)item)[2] |= (((U16*)last_item)[2]&0xFF00);
  memcpy(last_item, item, 6);
}

/*
===============================================================================
                       LASreadItemCompressed_WAVEPACKET13_v1
===============================================================================
*/

LASreadItemCompressed_WAVEPACKET13_v1::LASreadItemCompressed_WAVEPACKET13_v1(ArithmeticDecoder* dec)
{
  /* set decoder */
  assert(dec);
  this->dec = dec;

  /* create models and integer compressors */
  m_packet_index = dec->createSymbolModel(256);
  m_offset_diff[0] = dec->createSymbolModel(4);
  m_offset_diff[1] = dec->createSymbolModel(4);
  m_offset_diff[2] = dec->createSymbolModel(4);
  m_offset_diff[3] = dec->createSymbolModel(4);
  ic_offset_diff = new IntegerCompressor(dec, 32);
  ic_packet_size = new IntegerCompressor(dec, 32);
  ic_return_point = new IntegerCompressor(dec, 32);
  ic_xyz = new IntegerCompressor(dec, 32, 3);

  /* create last item */
  last_item = new U8[28];
}

LASreadItemCompressed_WAVEPACKET13_v1::~LASreadItemCompressed_WAVEPACKET13_v1()
{
  dec->destroySymbolModel(m_packet_index);
  dec->destroySymbolModel(m_offset_diff[0]);
  dec->destroySymbolModel(m_offset_diff[1]);
  dec->destroySymbolModel(m_offset_diff[2]);
  dec->destroySymbolModel(m_offset_diff[3]);
  delete ic_offset_diff;
  delete ic_packet_size;
  delete ic_return_point;
  delete ic_xyz;
  delete [] last_item;
}

BOOL LASreadItemCompressed_WAVEPACKET13_v1::init(const U8* item)
{
  /* init state */
  last_diff_32 = 0;
  sym_last_offset_diff = 0;

  /* init models and integer compressors */
  dec->initSymbolModel(m_packet_index);
  dec->initSymbolModel(m_offset_diff[0]);
  dec->initSymbolModel(m_offset_diff[1]);
  dec->initSymbolModel(m_offset_diff[2]);
  dec->initSymbolModel(m_offset_diff[3]);
  ic_offset_diff->initDecompressor();
  ic_packet_size->initDecompressor();
  ic_return_point->initDecompressor();
  ic_xyz->initDecompressor();

  /* init last item */
  item++;
  memcpy(last_item, item, 28);
  return TRUE;
}

inline void LASreadItemCompressed_WAVEPACKET13_v1::read(U8* item)
{
  item[0] = (U8)(dec->decodeSymbol(m_packet_index));
  item++;

  LASwavepacket13 this_item_m;
  LASwavepacket13 last_item_m = LASwavepacket13::unpack(last_item);

  sym_last_offset_diff = dec->decodeSymbol(m_offset_diff[sym_last_offset_diff]);

  if (sym_last_offset_diff == 0)
  {
    this_item_m.offset = last_item_m.offset;
  }
  else if (sym_last_offset_diff == 1)
  {
    this_item_m.offset = last_item_m.offset + last_item_m.packet_size;
  }
  else if (sym_last_offset_diff == 2)
  {
    last_diff_32 = ic_offset_diff->decompress(last_diff_32);
    this_item_m.offset = last_item_m.offset + last_diff_32;
  }
  else
  {
    this_item_m.offset = dec->readInt64();
  }

  this_item_m.packet_size = ic_packet_size->decompress(last_item_m.packet_size);
  this_item_m.return_point.i32 = ic_return_point->decompress(last_item_m.return_point.i32);
  this_item_m.x.i32 = ic_xyz->decompress(last_item_m.x.i32, 0);
  this_item_m.y.i32 = ic_xyz->decompress(last_item_m.y.i32, 1);
  this_item_m.z.i32 = ic_xyz->decompress(last_item_m.z.i32, 2);

  this_item_m.pack(item);

  memcpy(last_item, item, 28);
}

/*
===============================================================================
                       LASreadItemCompressed_BYTE_v1
===============================================================================
*/

LASreadItemCompressed_BYTE_v1::LASreadItemCompressed_BYTE_v1(ArithmeticDecoder* dec, U32 number)
{
  /* set decoder */
  assert(dec);
  this->dec = dec;
  assert(number);
  this->number = number;

  /* create models and integer compressors */
  ic_byte = new IntegerCompressor(dec, 8, number);

  /* create last item */
  last_item = new U8[number];
}

LASreadItemCompressed_BYTE_v1::~LASreadItemCompressed_BYTE_v1()
{
  delete ic_byte;
  delete [] last_item;
}

BOOL LASreadItemCompressed_BYTE_v1::init(const U8* item)
{
  /* init state */

  /* init models and integer compressors */
  ic_byte->initDecompressor();

  /* init last item */
  memcpy(last_item, item, number);
  return TRUE;
}

inline void LASreadItemCompressed_BYTE_v1::read(U8* item)
{
  U32 i;
  for (i = 0; i < number; i++)
  {
    item[i] = (U8)(ic_byte->decompress(last_item[i], i));
  }
  memcpy(last_item, item, number);
}

// vim: set ts=2 sw=2 expandtabs
