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

  Program:   Visualization Toolkit
  Module:    vtkBase64InputStream.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkBase64InputStream.h"
#include "vtkBase64Utilities.h"
#include "vtkObjectFactory.h"

//------------------------------------------------------------------------------
vtkStandardNewMacro(vtkBase64InputStream);

//------------------------------------------------------------------------------
vtkBase64InputStream::vtkBase64InputStream()
{
  this->BufferLength = 0;
}

//------------------------------------------------------------------------------
vtkBase64InputStream::~vtkBase64InputStream() = default;

//------------------------------------------------------------------------------
void vtkBase64InputStream::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}

//------------------------------------------------------------------------------
inline int vtkBase64InputStream::DecodeTriplet(
  unsigned char& c0, unsigned char& c1, unsigned char& c2)
{
  // Read the 4 bytes encoding this triplet from the stream.

  unsigned char in[4];
  this->Stream->read(reinterpret_cast<char*>(in), 4);
  if (this->Stream->gcount() < 4)
  {
    return 0;
  }

  return vtkBase64Utilities::DecodeTriplet(in[0], in[1], in[2], in[3], &c0, &c1, &c2);
}

//------------------------------------------------------------------------------
void vtkBase64InputStream::StartReading()
{
  this->Superclass::StartReading();
  this->BufferLength = 0;
}

//------------------------------------------------------------------------------
void vtkBase64InputStream::EndReading() {}

//------------------------------------------------------------------------------
int vtkBase64InputStream::Seek(vtkTypeInt64 offset)
{
  vtkTypeInt64 triplet = offset / 3;
  int skipLength = offset % 3;

  // Seek to the beginning of the encoded triplet containing the
  // offset.
  std::streamoff off = static_cast<std::streamoff>(this->StreamStartPosition + (triplet * 4));
  if (!this->Stream->seekg(off, std::ios::beg))
  {
    return 0;
  }

  // Decode the first triplet if it is paritally skipped.
  if (skipLength == 0)
  {
    this->BufferLength = 0;
  }
  else if (skipLength == 1)
  {
    unsigned char c;
    this->BufferLength = this->DecodeTriplet(c, this->Buffer[0], this->Buffer[1]) - 1;
  }
  else
  {
    unsigned char c[2];
    this->BufferLength = this->DecodeTriplet(c[0], c[1], this->Buffer[0]) - 2;
  }

  // A DecodeTriplet call may have failed to read the stream.
  // If so, the current buffer length will be negative.
  return ((this->BufferLength >= 0) ? 1 : 0);
}

//------------------------------------------------------------------------------
size_t vtkBase64InputStream::Read(void* data_in, size_t length)
{
  unsigned char* data = static_cast<unsigned char*>(data_in);
  unsigned char* out = data;
  unsigned char* end = out + length;

  // If the previous read ended before filling buffer, don't read
  // more.
  if (this->BufferLength < 0)
  {
    return 0;
  }

  // Use leftover characters from a previous decode.
  if ((out != end) && (this->BufferLength == 2))
  {
    *out++ = this->Buffer[0];
    this->Buffer[0] = this->Buffer[1];
    this->BufferLength = 1;
  }
  if ((out != end) && (this->BufferLength == 1))
  {
    *out++ = this->Buffer[0];
    this->BufferLength = 0;
  }

  // Decode all complete triplets.
  while ((end - out) >= 3)
  {
    int len = this->DecodeTriplet(out[0], out[1], out[2]);
    out += len;
    if (len < 3)
    {
      this->BufferLength = len - 3;
      return (out - data);
    }
  }

  // Decode the last triplet and save leftover characters in the buffer.
  if ((end - out) == 2)
  {
    int len = this->DecodeTriplet(out[0], out[1], this->Buffer[0]);
    this->BufferLength = len - 2;
    if (len > 2)
    {
      out += 2;
    }
    else
    {
      out += len;
    }
  }
  else if ((end - out) == 1)
  {
    int len = this->DecodeTriplet(out[0], this->Buffer[0], this->Buffer[1]);
    this->BufferLength = len - 1;
    if (len > 1)
    {
      out += 1;
    }
    else
    {
      out += len;
    }
  }

  return (out - data);
}
