/*
 * Copyright (c) 2012, Chris Johns <chrisj@rtems.org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */
/**
 * @file
 *
 * @ingroup rtems-ld
 *
 * @brief RTEMS Linker compression handles compressed images.
 *
 */

#if !defined (_RLD_COMPRESSION_H_)
#define _RLD_COMPRESSION_H_

#include <rld-files.h>

namespace rld
{
  namespace compress
  {
    /**
     * A compressor.
     */
    class compressor
    {
    public:
      /**
       * Construct the compressor for the given image.
       *
       * @param image The image to read or write to.
       * @param size The size of the input and output buffers.
       * @param out The compressor is compressing.
       * @param compress Set to false to disable compression.
       */
      compressor (files::image& image,
                  size_t        size,
                  bool          out = true,
                  bool          compress = true);

      /**
       * Destruct the compressor.
       */
      ~compressor ();

      /**
       * Write the data to the output buffer and once the image buffer is full
       * compress and write the compressed data to the image.
       *
       * @param data The data to write to the image compressed
       * @param length The mount of data in bytes to write.
       */
      void write (const void* data, size_t length);

      /**
       * Write the section of the input image file to the output buffer and
       * once the image buffer is full compress and write the compressed data
       * to the image.
       *
       * @param input The input image.
       * @param offset The input image offset to read from.
       * @param length The mount of data in bytes to write.
       */
      void write (files::image& input, off_t offset, size_t length);

      /**
       * Flush the output buffer is data is present.
       */
      void flush ();

      /**
       * Read the compressed data into the input buffer and return the section
       * requested.
       *
       * @param data Write the decompressed data here.
       * @param length The mount of data in bytes to read.
       * @return size_t The amount of data read.
       */
      size_t read (void* data, size_t length);

      /**
       * Read the decompressed data writing it to the image.
       *
       * @param output_ The output image.
       * @param offset The output image offset to write from.
       * @param length The mount of data in bytes to read.
       * @return size_t The amount of data read.
       */
      size_t read (files::image& output_, off_t offset, size_t length);

      /**
       * Read the decompressed data writing it to the image.
       *
       * @param output_ The output image.
       * @param length The mount of data in bytes to read.
       * @return size_t The amount of data read.
       */
      size_t read (files::image& output_, size_t length);

      /**
       * The amount of uncompressed data transferred.
       *
       * @return size_t The amount of data tranferred.
       */
      size_t transferred () const;

      /**
       * The amount of compressed data transferred.
       *
       * @return size_t The amount of compressed data tranferred.
       */
      size_t compressed () const;

      /**
       * The current offset in the stream.
       *
       * @return off_t The current uncompressed offset.
       */
      off_t offset () const;

    private:

      /**
       * Output the block of data to the output file with the block header.
       *
       * @param forced If true output the buffer.
       */
      void output (bool forced = false);

      /**
       * Input a block of compressed data and decompress it.
       */
      void input ();

      files::image& image;            //< The image to read or write to or from.
      size_t        size;             //< The size of the buffer.
      bool          out;              //< If true the it is compression.
      bool          compress;         //< If true compress the data.
      uint8_t*      buffer;           //< The decompressed buffer
      uint8_t*      io;               //< The I/O buffer.
      size_t        level;            //< The amount of data in the buffer.
      size_t        total;            //< The amount of uncompressed data
                                      //  transferred.
      size_t        total_compressed; //< The amount of compressed data
                                      //  transferred.
    };

    /**
     * Compressor template function for writing data to the compressor.
     */
    template < typename T >
    void write (compressor& comp, const T value)
    {
      uint8_t bytes[sizeof (T)];
      T       v = value;
      int     b = sizeof (T) - 1;
      while (b >= 0)
      {
        bytes[b--] = (uint8_t) v;
        v >>= 8;
      }
      comp.write (bytes, sizeof (T));
    }

    /**
     * Compressor template function for reading data from the compressor.
     */
    template < typename T >
    T read (compressor& comp)
    {
      uint8_t  bytes[sizeof (T)];
      T        v = 0;
      uint32_t b = 0;
      if (comp.read (bytes, sizeof (T)) != sizeof (T))
        throw rld::error ("Reading of value failed", "compression");
      while (b < sizeof (T))
      {
        v = (v << 8) | ((T) bytes[b++]);
      }
      return v;
    }

  }
}

static inline rld::compress::compressor& operator<< (rld::compress::compressor& comp,
                                                     const uint64_t             value) {
  rld::compress::write < uint64_t > (comp, value);
  return comp;
}

static inline rld::compress::compressor& operator<< (rld::compress::compressor& comp,
                                                     const uint32_t             value) {
  rld::compress::write < uint32_t > (comp, value);
  return comp;
}

static inline rld::compress::compressor& operator<< (rld::compress::compressor& comp,
                                                     const std::string&         str) {
  comp.write (str.c_str (), str.size ());
  return comp;
}

static inline rld::compress::compressor& operator>> (rld::compress::compressor& comp,
                                                     uint64_t&                  value) {
  value = rld::compress::read < uint64_t > (comp);
  return comp;
}

static inline rld::compress::compressor& operator>> (rld::compress::compressor& comp,
                                                     uint32_t&                  value) {
  value = rld::compress::read < uint32_t > (comp);
  return comp;
}

#endif
