/****************************************************************************
 * apps/graphics/tiff/tiff_finalize.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include "graphics/tiff.h"

#include "tiff_internal.h"

/****************************************************************************
 * Pre-Processor Definitions
 ****************************************************************************/

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

/****************************************************************************
 * Public Data
 ****************************************************************************/

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: tiff_readifdentry
 *
 * Description:
 *   Read the IFD entry at the specified offset.
 *
 * Input Parameters:
 *   fd       - File descriptor to rad from
 *   offset   - Offset to read from
 *   ifdentry - Location to read the data
 *
 * Returned Value:
 *   Zero (OK) on success.  A negated errno value on failure.
 *
 ****************************************************************************/

static int tiff_readifdentry(int fd, off_t offset,
                             FAR struct tiff_ifdentry_s *ifdentry)
{
  off_t newoffs;
  ssize_t nbytes;

  /* Seek to the read position */

  newoffs = lseek(fd, offset, SEEK_SET);
  if (newoffs == (off_t)-1)
    {
      return -errno;
    }

  /* Then read the IFD entry.  Anything returned by tiff_read other than the
   * size of the IFD entry would be an error.
   */

  nbytes = tiff_read(fd, ifdentry, SIZEOF_IFD_ENTRY);
  return nbytes == SIZEOF_IFD_ENTRY ? OK : -ENOSPC;
}

/****************************************************************************
 * Name: tiff_writeifdentry
 *
 * Description:
 *   Write the IFD entry at the specified offset.
 *
 * Input Parameters:
 *   fd       - File descriptor to rad from
 *   offset   - Offset to read from
 *   ifdentry - Location to read the data
 *
 * Returned Value:
 *   Zero (OK) on success.  A negated errno value on failure.
 *
 ****************************************************************************/

static int tiff_writeifdentry(int fd, off_t offset,
                              FAR struct tiff_ifdentry_s *ifdentry)
{
  off_t newoffs;

  /* Seek to the write position */

  newoffs = lseek(fd, offset, SEEK_SET);
  if (newoffs == (off_t)-1)
    {
      return -errno;
    }

  /* Then write the IFD entry */

  return tiff_write(fd, ifdentry, SIZEOF_IFD_ENTRY);
}

/****************************************************************************
 * Name: tiff_cleanup
 *
 * Description:
 *   Normal clean-up after completion of the TIFF file creation
 *
 * Input Parameters:
 *   info - A pointer to the caller allocated parameter passing/TIFF
 *          state instance.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

static void tiff_cleanup(FAR struct tiff_info_s *info)
{
  /* Close all opened files */

  if (info->outfd >= 0)
    {
      close(info->outfd);
    }

  info->outfd = -1;

  if (info->tmp1fd >= 0)
    {
      close(info->tmp1fd);
    }

  info->tmp1fd = -1;

  if (info->tmp2fd >= 0)
    {
      close(info->tmp2fd);
    }

  info->tmp2fd = -1;

  /* And remove the temporary files */

  unlink(info->tmpfile1);
  unlink(info->tmpfile2);
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: tiff_finalize
 *
 * Description:
 *   Finalize the TIFF output file, completing the TIFF file creation steps.
 *
 * Input Parameters:
 *   info - A pointer to the caller allocated parameter passing/TIFF state
 *          instance.
 *
 * Returned Value:
 *   Zero (OK) on success.  A negated errno value on failure.
 *
 ****************************************************************************/

int tiff_finalize(FAR struct tiff_info_s *info)
{
  struct tiff_ifdentry_s ifdentry;
  FAR uint8_t *ptr;
  size_t maxoffsets;
#ifdef CONFIG_DEBUG_GRAPHICS
  size_t total;
#endif
  off_t offset;
  int ret;
  int i;
  int j;

  /* Put all of the pieces together to create the final output file.
   * There are three pieces:
   *
   * 1) outfile: The partial output file containing the header, IFD and strip
   *    counts. This includes the StripOffsets and StripByteCounts that need
   *    to be updated.  Size=outsize;
   * 2) tmpfile1: This contains the offsets into tmpfile3 for each strip.
   *    The size of this file is tmp1size.  These offsets are relative to the
   *    beginning of tmpfile3 and need to be offset by outsize+tmp1size.
   * 3) tmpfile3: The strip data.  Size is tmp2size.  This is raw image data;
   *    no fixups are required.
   */

  DEBUGASSERT(info && info->outfd >= 0 &&
              info->tmp1fd >= 0 && info->tmp2fd >= 0);
  DEBUGASSERT((info->outsize & 3) == 0 && (info->tmp1size & 3) == 0);

  /* Fix-up the count value in the StripByteCounts IFD entry in the outfile.
   * The actual number of strips was unknown at the time that the IFD entry
   * was written.
   */

  ret = tiff_readifdentry(info->outfd, info->filefmt->sbcifdoffset,
                          &ifdentry);
  if (ret < 0)
    {
      goto errout;
    }

  tiff_put32(ifdentry.count, info->nstrips);

  ret = tiff_writeifdentry(info->outfd, info->filefmt->sbcifdoffset,
                           &ifdentry);
  if (ret < 0)
    {
      goto errout;
    }

  /* Fix-up the count and offset values in the StripOffsets IFD entry in the
   * outfile.  The StripOffsets data will be stored immediately after the
   * outfile, hence, the correct offset is outsize.
   */

  ret = tiff_readifdentry(info->outfd, info->filefmt->soifdoffset,
                          &ifdentry);
  if (ret < 0)
    {
      goto errout;
    }

  tiff_put32(ifdentry.count, info->nstrips);
  tiff_put32(ifdentry.offset, info->outsize);

  ret = tiff_writeifdentry(info->outfd, info->filefmt->soifdoffset,
                           &ifdentry);
  if (ret < 0)
    {
      goto errout;
    }

  /* Rewind to the beginning of tmpfile1 */

  offset = lseek(info->tmp1fd, 0, SEEK_SET);
  if (offset == (off_t)-1)
    {
      ret = -errno;
      goto errout;
    }

  /* Seek to the end of the outfile */

  ret = lseek(info->outfd, 0, SEEK_END);
  if (offset == (off_t)-1)
    {
      ret = -errno;
      goto errout;
    }

  /* Now read strip offset data from tmpfile1, update the offsets, and write
   * the updated offsets to the outfile.  The strip data will begin at offset
   * outsize + tmp1size;
   */

  maxoffsets = info->iosize >> 2;
#ifdef CONFIG_DEBUG_GRAPHICS
  total      = 0;
#endif

  for (i = 0; i < info->nstrips; )
    {
      size_t noffsets;
      ssize_t nbytes;

      /* Read a group of up to 32-bit values */

      noffsets = info->nstrips - i;
      if (noffsets > maxoffsets)
        {
          noffsets = maxoffsets;
        }

      nbytes = tiff_read(info->tmp1fd, info->iobuffer, noffsets << 2);

      /* If an error occurs or we fail to read exactly this number of
       * bytes, then something bad happened.
       */

      if (nbytes != noffsets << 2)
        {
          goto errout;
        }

      /* Fix up the offsets */

      for (j = 0, ptr = info->iobuffer;
           j < noffsets;
           j++, ptr += 4)
        {
          uint32_t stripoff = tiff_get32(ptr);
          stripoff += (info->outsize + info->tmp1size);
          tiff_put32(ptr, stripoff);
        }

      /* Then write the corrected offsets to the outfile */

      ret = tiff_write(info->outfd, info->iobuffer, nbytes);
      if (ret < 0)
        {
          goto errout;
        }

      /* Update the count of offsets written */

      i     += noffsets;
#ifdef CONFIG_DEBUG_GRAPHICS
      total += nbytes;
#endif
    }

#ifdef CONFIG_DEBUG_GRAPHICS
  DEBUGASSERT(total == info->tmp1size);
#endif

  /* Rewind to the beginning of tmpfile2 */

  offset = lseek(info->tmp2fd, 0, SEEK_SET);
  if (offset == (off_t)-1)
    {
      ret = -errno;
      goto errout;
    }

  /* Finally, copy the tmpfile2 to the end of the outfile */

#ifdef CONFIG_DEBUG_GRAPHICS
  total = 0;
#endif
  for (; ; )
    {
      ssize_t nbytes;

      /* Read a block of data from tmpfile2 */

      nbytes = tiff_read(info->tmp2fd, info->iobuffer, info->iosize);

      /* Check for tead errors and for end-of-file */

      if (nbytes < 0)
        {
          ret = (int)nbytes;
          goto errout;
        }
      else if (nbytes == 0)
        {
          break;
        }

      /* Then copy the data to the outfile */

      ret = tiff_write(info->outfd, info->iobuffer, nbytes);
      if (ret < 0)
        {
          goto errout;
        }

#ifdef CONFIG_DEBUG_GRAPHICS
      total += nbytes;
#endif
    }

#ifdef CONFIG_DEBUG_GRAPHICS
  DEBUGASSERT(total == info->tmp2size);
#endif

  /* Close all files and return success */

  tiff_cleanup(info);
  return OK;

errout:
  tiff_abort(info);
  return ret;
}

/****************************************************************************
 * Name: tiff_abort
 *
 * Description:
 *   Abort the TIFF file creation and create-up resources.
 *
 * Input Parameters:
 *   info - A pointer to the caller allocated parameter passing/TIFF state
 *          instance.
 *
 * Returned Value:
 *   None
 *
 ****************************************************************************/

void tiff_abort(FAR struct tiff_info_s *info)
{
  /* Perform normal cleanup */

  tiff_cleanup(info);

  /* But then delete the output file as well */

  unlink(info->outfile);
}