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

  FILE:  laswaveform13writer.cpp

  CONTENTS:

    see corresponding header file

  PROGRAMMERS:

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

  COPYRIGHT:

    (c) 2007-2012, 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 LICENSE.txt 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 "laswaveform13writer.hpp"

#include "bytestreamout_file.hpp"
#include "arithmeticencoder.hpp"
#include "integercompressor.hpp"

class LASwaveformDescription
{
public:
  U8 compression;
  U8 nbits;
  U16 nsamples;
};

LASwaveform13writer::LASwaveform13writer()
{
  waveforms = 0;
  file = 0;
  stream = 0;
  enc = 0;
  ic8 = 0;
  ic16 = 0;
}

LASwaveform13writer::~LASwaveform13writer()
{
  if (waveforms)
  {
    I32 i;
    for (i = 1; i < 256; i++)
    {
      if (waveforms[i]) delete waveforms[i];
    }
    delete [] waveforms;
  }
  if (ic8) delete ic8;
  if (ic16) delete ic16;
  if (enc) delete enc;
}


BOOL LASwaveform13writer::open(const char* file_name, const LASvlr_wave_packet_descr * const * wave_packet_descr)
{
  if (file_name == 0)
  {
    REprintf("ERROR: file name pointer is zero\n");
    return FALSE;
  }

  // we need wave packet descriptors

  if (wave_packet_descr == 0)
  {
    REprintf("ERROR: wave packet descriptor pointer is zero\n");
    return FALSE;
  }

  // only array positions 1 through 255 should have a wave packet descriptor

  if (wave_packet_descr[0] != 0)
  {
    REprintf("ERROR: wave_packet_descr[0] with index 0 must be zero\n");
    return FALSE;
  }

  // copy relevant wave packet descriptors and check if compressed or not

  U16 i, number = 0;
  BOOL compressed = FALSE;

  if (waveforms == 0)
  {
    waveforms = new LASwaveformDescription*[256];
    for (i = 0; i < 256; i++) waveforms[i] = 0;
  }

  for (i = 1; i < 256; i++)
  {
    if (wave_packet_descr[i])
    {
      if (waveforms[i] == 0)
      {
        waveforms[i] = new LASwaveformDescription;
      }
      waveforms[i]->compression = wave_packet_descr[i]->getCompressionType();
      waveforms[i]->nbits = wave_packet_descr[i]->getBitsPerSample();
      waveforms[i]->nsamples = wave_packet_descr[i]->getNumberOfSamples();
      compressed = compressed || (waveforms[i]->compression > 0);
      number++;
    }
    else
    {
      if (waveforms[i])
      {
        delete waveforms[i];
        waveforms[i] = 0;
      }
    }
  }

  // create file name and open file

  char* file_name_temp = LASCopyString(file_name);

  I32 len = (I32)strlen(file_name_temp);
  if (file_name_temp[len-3] == 'L' || file_name_temp[len-3] == 'W')
  {
    file_name_temp[len-3] = 'W';
    file_name_temp[len-2] = 'D';
    file_name_temp[len-1] = (compressed ? 'Z' : 'P');
  }
  else
  {
    file_name_temp[len-3] = 'w';
    file_name_temp[len-2] = 'd';
    file_name_temp[len-1] = (compressed ? 'z' : 'p');
  }
  file = fopen(file_name_temp, "wb");

  if (file == 0)
  {
    REprintf( "ERROR: cannot open waveform file '%s'\n", file_name_temp);
    free(file_name_temp);
    return FALSE;
  }
  free(file_name_temp);

  // create stream

  if (IS_LITTLE_ENDIAN())
  {
    stream = new ByteStreamOutFileLE(file);
  }
  else
  {
    stream = new ByteStreamOutFileBE(file);
  }

  // write extended variable length header variable after variable (to avoid alignment issues)

  U16 reserved = 0xAABB;
  if (!stream->put16bitsLE((U8*)&reserved))
  {
    REprintf("ERROR: writing EVLR reserved\n");
    return FALSE;
  }
  CHAR user_id[16];
  memset(user_id, 0, 16);
  strcpy(user_id, "LASF_Spec");
  if (!stream->putBytes((U8*)user_id, 16))
  {
    REprintf("ERROR: writing EVLR user_id\n");
    return FALSE;
  }
  U16 record_id = 65535;
  if (!stream->put16bitsLE((U8*)&record_id))
  {
    REprintf("ERROR: writing EVLR record_id\n");
    return FALSE;
  }
  I64 record_length_after_header = 0;
  if (!stream->put64bitsLE((U8*)&record_length_after_header))
  {
    REprintf("ERROR: writing EVLR record_length_after_header\n");
    return FALSE;
  }
  CHAR description[32];
  memset(description, 0, 32);
  snprintf(description, 32, "%s by LAStools (%d)", (compressed ? "compressed" : "created"), LAS_TOOLS_VERSION);
  if (!stream->putBytes((U8*)description, 32))
  {
    REprintf("ERROR: writing EVLR description\n");
    return FALSE;
  }

  // write waveform descriptor cross-check

  char magic[25];
  snprintf(magic, 25, "LAStools waveform %d", LAS_TOOLS_VERSION);

  if (!stream->putBytes((U8*)magic, 24))
  {
    REprintf("ERROR: writing waveform descriptor cross-check\n");
    return FALSE;
  }

  if (!stream->put16bitsLE((U8*)&number))
  {
    REprintf("ERROR: writing number of waveform descriptors\n");
    return FALSE;
  }

  for (i = 1; i < 256; i++)
  {
    if (waveforms[i])
    {
      if (!stream->put16bitsLE((U8*)&i))
      {
        REprintf("ERROR: writing index of waveform descriptor %d\n", i);
        return FALSE;
      }
      if (!stream->putByte(waveforms[i]->compression))
      {
        REprintf("ERROR: writing compression of waveform descriptor %d\n", i);
        return FALSE;
      }
      if (!stream->putByte(waveforms[i]->nbits))
      {
        REprintf("ERROR: writing nbits of waveform descriptor %d\n", i);
        return FALSE;
      }
      if (!stream->put16bitsLE((U8*)&(waveforms[i]->nsamples)))
      {
        REprintf("ERROR: writing nsamples of waveform descriptor %d\n", i);
        return FALSE;
      }
    }
  }

  // create compressor

  if (compressed)
  {
    if (enc == 0) enc = new ArithmeticEncoder();
    if (ic8 == 0) ic8 = new IntegerCompressor(enc, 8);
    if (ic16 == 0) ic16 = new IntegerCompressor(enc, 16);
  }

  return TRUE;
}

BOOL LASwaveform13writer::write_waveform(LASpoint* point, U8* samples)
{
  U32 index = point->wavepacket.getIndex();
  if (index == 0)
  {
    return FALSE;
  }

  U32 nbits = waveforms[index]->nbits;
  if ((nbits != 8) && (nbits != 16))
  {
    REprintf( "ERROR: waveform with %d bits per samples not supported yet\n", nbits);
    return FALSE;
  }

  U32 nsamples = waveforms[index]->nsamples;
  if (nsamples == 0)
  {
    REprintf( "ERROR: waveform has no samples\n");
    return FALSE;
  }

  // set offset to waveform data

  I64 offset = stream->tell();
  point->wavepacket.setOffset(offset);

  // write waveform

  if (waveforms[index]->compression == 0)
  {
    U32 size = ((nbits/8) * nsamples);
    if (!stream->putBytes(samples, size))
    {
      REprintf( "ERROR: cannot write %u bytes for waveform with %u samples of %u bits\n", size, nsamples, nbits);
      return FALSE;
    }
    point->wavepacket.setSize(size);
  }
  else
  {
    U32 s_count;
    if (nbits == 8)
    {
      stream->putBytes(samples, 1);
      enc->init(stream);
      ic8->initCompressor();
      for (s_count = 1; s_count < nsamples; s_count++)
      {
        ic8->compress(samples[s_count-1], samples[s_count]);
      }
    }
    else
    {
      stream->putBytes(samples, 2);
      enc->init(stream);
      ic16->initCompressor();
      for (s_count = 1; s_count < nsamples; s_count++)
      {
        ic16->compress(((U16*)samples)[s_count-1], ((U16*)samples)[s_count]);
      }
    }
    enc->done();
    U32 size = (U32)(stream->tell() - offset);
    point->wavepacket.setSize(size);
  }

  return TRUE;
}

void LASwaveform13writer::close()
{
  if (stream->isSeekable())
  {
    I64 record_length_after_header = stream->tell();
    record_length_after_header -= 60;
    stream->seek(18);
    if (!stream->put64bitsLE((U8*)&record_length_after_header))
    {
      REprintf("ERROR: updating EVLR record_length_after_header\n");
    }
    stream->seekEnd();
  }
  if (stream)
  {
    delete stream;
    stream = 0;
  }
  if (file)
  {
    fclose(file);
    file = 0;
  }
}
