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

  FILE:  laswriter_bin.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 "laswriter_bin.hpp"

#include "bytestreamout_file.hpp"

#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
#endif

#include <stdlib.h>
#include <string.h>

struct TSrow
{
  U8 code;
  U8 line;
  U16 echo_intensity;
  I32 x;
  I32 y;
  I32 z;
};

struct TSpoint
{
  I32 x;
  I32 y;
  I32 z;
  U8 code;
  U8 echo;
  U8 flag;
  U8 mark;
  U16 line;
  U16 intensity;
};

struct TSheader
{
  I32 size;
  I32 version;
  I32 recog_val;
  I8 recog_str[4];
  I32 npoints;
  I32 units;
  F64 origin_x;
  F64 origin_y;
  F64 origin_z;
  I32 time;
  I32 rgb;
};

BOOL LASwriterBIN::refile(FILE* file)
{
  if (stream == 0) return FALSE;
  if (this->file) this->file = file;
  return ((ByteStreamOutFile*)stream)->refile(file);
}

BOOL LASwriterBIN::open(const char* file_name, const LASheader* header, const char* version, U32 io_buffer_size)
{
  if (file_name == 0)
  {
    REprintf("ERROR: file name pointer is zero\n");
    return FALSE;
  }

  file = fopen(file_name, "wb");

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

  if (setvbuf(file, NULL, _IOFBF, io_buffer_size) != 0)
  {
    REprintf( "WARNING: setvbuf() failed with buffer size %u\n", io_buffer_size);
  }

  ByteStreamOut* out;
  if (IS_LITTLE_ENDIAN())
    out = new ByteStreamOutFileLE(file);
  else
    out = new ByteStreamOutFileBE(file);

  return open(out, header, version);
}

BOOL LASwriterBIN::open(FILE* file, const LASheader* header, const char* version)
{
  if (file == 0)
  {
    REprintf("ERROR: file pointer is zero\n");
    return FALSE;
  }

#ifdef _WIN32
  if (file == stdout)
  {
    if(_setmode( _fileno( stdout ), _O_BINARY ) == -1 )
    {
      REprintf( "ERROR: cannot set stdout to binary (untranslated) mode\n");
    }
  }
#endif

  ByteStreamOut* out;
  if (IS_LITTLE_ENDIAN())
    out = new ByteStreamOutFileLE(file);
  else
    out = new ByteStreamOutFileBE(file);

  return open(out, header, version);
}

BOOL LASwriterBIN::open(ByteStreamOut* stream, const LASheader* header, const char* version)
{
  if (stream == 0)
  {
    REprintf("ERROR: ByteStreamOut pointer is zero\n");
    return FALSE;
  }
  this->stream = stream;

  if (header == 0)
  {
    REprintf("ERROR: LASheader pointer is zero\n");
    return FALSE;
  }

  if (strstr(version, "ts16"))
  {
    this->version = 20020715;
  }
  else
  {
    this->version = 20010712;
  }

  TSheader tsheader;
  tsheader.size = sizeof(TSheader);
  tsheader.version = this->version;
  tsheader.recog_val = 970401;
  memcpy(tsheader.recog_str, "CXYZ", 4);
  tsheader.npoints = (header->number_of_point_records ? header->number_of_point_records : (U32)header->extended_number_of_point_records);
  double scale = header->x_scale_factor;
  if (header->y_scale_factor < scale) scale = header->y_scale_factor;
  if (header->z_scale_factor < scale) scale = header->z_scale_factor;
  units = tsheader.units = (I32)(1.0 / scale);
  origin_x = tsheader.origin_x = -header->x_offset/scale;
  origin_y = tsheader.origin_y = -header->y_offset/scale;
  origin_z = tsheader.origin_z = -header->z_offset/scale;
  tsheader.time = (header->point_data_format == 1) || (header->point_data_format == 3) || (header->point_data_format == 4) || (header->point_data_format == 5);
  tsheader.rgb = (header->point_data_format == 2) || (header->point_data_format == 3) || (header->point_data_format == 5);

  return stream->putBytes((U8*)&tsheader, sizeof(TSheader));
}

BOOL LASwriterBIN::write_point(const LASpoint* point)
{
  U16 echo;

  if (point->number_of_returns <= 1)
    echo = 0;
  else if (point->return_number == 1)
    echo = 1;
  else if (point->return_number >= point->number_of_returns)
    echo = 3;
  else
    echo = 2;

  if (version == 20020715)
  {
    TSpoint tspoint;
    tspoint.x = I32_QUANTIZE(point->get_x()*units+origin_x);
    tspoint.y = I32_QUANTIZE(point->get_y()*units+origin_y);
    tspoint.z = I32_QUANTIZE(point->get_z()*units+origin_z);
    tspoint.code = point->classification;
    tspoint.echo = (U8)echo;
    tspoint.flag  = 0;
    tspoint.mark  = 0;
    tspoint.line = point->point_source_ID;
    tspoint.intensity = point->intensity;
    if (!stream->putBytes((U8*)&tspoint, sizeof(TSpoint))) return FALSE;
  }
  else
  {
    TSrow tsrow;
    tsrow.code = point->classification;
    tsrow.line = (U8)(point->point_source_ID);
    tsrow.echo_intensity = (echo << 14) | (point->intensity & 0x3FFF);
    tsrow.x = I32_QUANTIZE(point->get_x()*units+origin_x);
    tsrow.y = I32_QUANTIZE(point->get_y()*units+origin_y);
    tsrow.z = I32_QUANTIZE(point->get_z()*units+origin_z);
    if (!stream->putBytes((U8*)&tsrow, sizeof(TSrow))) return FALSE;
  }

  if (point->have_gps_time)
  {
    U32 time = (U32)(point->gps_time/0.0002+0.5);
    if (!stream->putBytes((U8*)&time, sizeof(U32))) return FALSE;
  }
  if (point->have_rgb)
  {
    U8 rgba[4];
    rgba[0] = point->rgb[0]/256;
    rgba[1] = point->rgb[1]/256;
    rgba[2] = point->rgb[2]/256;
    rgba[3] = 0;
    if (!stream->putBytes((U8*)&rgba, sizeof(U32))) return FALSE;
  }
  p_count++;
  return TRUE;
}

BOOL LASwriterBIN::update_header(const LASheader* header, BOOL use_inventory, BOOL update_extra_bytes)
{
  return TRUE;
}

I64 LASwriterBIN::close(BOOL update_npoints)
{
  I64 bytes = 0;

  if (stream)
  {
    if (update_npoints && p_count != npoints)
    {
      if (!stream->isSeekable())
      {
#ifdef _WIN32
        REprintf( "ERROR: stream not seekable. cannot update header from %" PRId64 " to %" PRId64 " points.\n", npoints, p_count);
#else
        REprintf( "ERROR: stream not seekable. cannot update header from %" PRId64 " to %" PRId64 " points.\n", npoints, p_count);
#endif
      }
      else
      {
	      stream->seek(16);
	      stream->put32bitsLE((U8*)&p_count);
        stream->seekEnd();
      }
    }
    bytes = stream->tell();
    delete stream;
    stream = 0;
  }

  if (file)
  {
    fclose(file);
    file = 0;
  }

  npoints = p_count;
  p_count = 0;

  return bytes;
}

LASwriterBIN::LASwriterBIN()
{
  stream = 0;
  file = 0;
}

LASwriterBIN::~LASwriterBIN()
{
  if (file) close();
}
