/*
 * 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.
 */


#include "pagespeed/kernel/image/read_image.h"

#include <setjmp.h>
#include <stdlib.h>
#include "pagespeed/kernel/base/message_handler.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/image/frame_interface_optimizer.h"
#include "pagespeed/kernel/image/gif_reader.h"
#include "pagespeed/kernel/image/image_frame_interface.h"
#include "pagespeed/kernel/image/jpeg_optimizer.h"
#include "pagespeed/kernel/image/jpeg_reader.h"
#include "pagespeed/kernel/image/png_optimizer.h"
#include "pagespeed/kernel/image/scanline_interface.h"
#include "pagespeed/kernel/image/scanline_interface_frame_adapter.h"
#include "pagespeed/kernel/image/scanline_utils.h"
#include "pagespeed/kernel/image/webp_optimizer.h"

namespace pagespeed {

namespace image_compression {

using net_instaweb::MessageHandler;

////////// Scanline API

// Forward declaration
MultipleFrameReader* InstantiateImageFrameReader(
    ImageFormat image_type,
    MessageHandler* handler,
    ScanlineStatus* status);

// Instantiates an uninitialized scanline image reader.
ScanlineReaderInterface* InstantiateScanlineReader(
    ImageFormat image_type,
    MessageHandler* handler,
    ScanlineStatus* status) {
  ScanlineReaderInterface* reader = NULL;
  const char* which = NULL;

  *status = ScanlineStatus(SCANLINE_STATUS_SUCCESS);
  switch (image_type) {
    case IMAGE_PNG:
      reader = new PngScanlineReaderRaw(handler);
      which = "PngScanlineReaderRaw";
      break;

    case IMAGE_JPEG:
      reader = new JpegScanlineReader(handler);
      which = "JpegScanlineReader";
      break;

    case IMAGE_WEBP:
      reader = new WebpScanlineReader(handler);
      which = "WebpScanlineReader";
      break;

    case IMAGE_GIF: {
      which = "FrameToScanlineReaderAdapter(GifFrameReader)";
      net_instaweb::scoped_ptr<MultipleFrameReader> mf_reader(
          InstantiateImageFrameReader(image_type, handler, status));
      if (!mf_reader->set_quirks_mode(QUIRKS_CHROME, status)) {
        return NULL;
      }
      reader =
          new FrameToScanlineReaderAdapter(
              new MultipleFramePaddingReader(mf_reader.release()));
      break;
    }

    case IMAGE_UNKNOWN:
      break;

    // No default so compiler will complain if any enum is not processed.
  }

  if (which == NULL) {
      *status = PS_LOGGED_STATUS(PS_LOG_DFATAL, handler,
                                 SCANLINE_STATUS_UNSUPPORTED_FORMAT,
                                 SCANLINE_UTIL,
                                 "invalid image type for reader: %d",
                                 image_type);
  } else if (reader == NULL) {
    *status = PS_LOGGED_STATUS(PS_LOG_ERROR, handler,
                               SCANLINE_STATUS_MEMORY_ERROR,
                               SCANLINE_UTIL,
                               "failed to allocate %s", which);
  }

  return reader;
}

// Returns an initialized scanline image reader.
ScanlineReaderInterface* CreateScanlineReader(
    ImageFormat image_type,
    const void* image_buffer,
    size_t buffer_length,
    MessageHandler* handler,
    ScanlineStatus* status) {
  net_instaweb::scoped_ptr<ScanlineReaderInterface> reader(
      InstantiateScanlineReader(image_type, handler, status));
  if (status->Success()) {
    *status = reader->InitializeWithStatus(image_buffer, buffer_length);
  }
  return status->Success() ? reader.release() : NULL;
}

// Forward declaration.
MultipleFrameWriter* InstantiateImageFrameWriter(
    ImageFormat image_type,
    MessageHandler* handler,
    ScanlineStatus* status);

// Instantiates an uninitialized scanline image writer.
ScanlineWriterInterface* InstantiateScanlineWriter(
    ImageFormat image_type,
    MessageHandler* handler,
    ScanlineStatus* status) {
  ScanlineWriterInterface* writer = NULL;
  const char* which = NULL;

  *status = ScanlineStatus(SCANLINE_STATUS_SUCCESS);
  switch (image_type) {
    case pagespeed::image_compression::IMAGE_JPEG:
      {
        net_instaweb::scoped_ptr<JpegScanlineWriter> jpeg_writer(
            new JpegScanlineWriter(handler));
        if (jpeg_writer != NULL) {
          // TODO(huibao): Set up error handling inside JpegScanlineWriter.
          // Remove 'setjmp' from the clients and remove the 'SetJmpBufEnv'
          // method.
          jmp_buf env;
          if (setjmp(env)) {
            // This code is run only when libjpeg hit an error, and
            // called longjmp(env). Note that this only works for as
            // long as this stack frame is valid.
            jpeg_writer->AbortWrite();
            return NULL;
          }

          jpeg_writer->SetJmpBufEnv(&env);
          writer = jpeg_writer.release();
        }
        which  = "JpegScanlineWriter";
      }
      break;

    case pagespeed::image_compression::IMAGE_PNG:
      writer = new PngScanlineWriter(handler);
      which = "PngScanlineWriter";
      break;

    case pagespeed::image_compression::IMAGE_WEBP:
      which = "FrameToScanlineWriterAdapter(WebpFrameWriter)";
      writer = new FrameToScanlineWriterAdapter(
          InstantiateImageFrameWriter(image_type, handler, status));
      break;

    case IMAGE_GIF:
      // This library does not implement a GIF writer; intentional
      // fall-through.
    case IMAGE_UNKNOWN:
      break;

    // No default so compiler will complain if any enum is not processed.
  }

  if (which == NULL) {
        *status = PS_LOGGED_STATUS(PS_LOG_DFATAL, handler,
                                   SCANLINE_STATUS_UNSUPPORTED_FORMAT,
                                   SCANLINE_UTIL,
                                   "invalid image type for writer: %d",
                                   image_type);
  } else if (writer == NULL) {
    *status = PS_LOGGED_STATUS(PS_LOG_ERROR, handler,
                               SCANLINE_STATUS_MEMORY_ERROR,
                               SCANLINE_UTIL,
                               "failed to allocate %s", which);
  }

  return writer;
}


// Returns an initialized scanline image writer.
ScanlineWriterInterface* CreateScanlineWriter(
    ImageFormat image_type,
    PixelFormat pixel_format,
    size_t width,
    size_t height,
    const void* config,
    GoogleString* image_data,
    MessageHandler* handler,
    ScanlineStatus* status) {
  net_instaweb::scoped_ptr<ScanlineWriterInterface> writer(
      InstantiateScanlineWriter(image_type, handler, status));
  if (status->Success()) {
    *status = writer->InitWithStatus(width, height, pixel_format);
  }
  if (status->Success()) {
    *status = writer->InitializeWriteWithStatus(config, image_data);
  }
  return status->Success() ? writer.release() : NULL;
}


////////// ImageFrame API

// Instantiates an uninitialized image frame reader.
MultipleFrameReader* InstantiateImageFrameReader(
    ImageFormat image_type,
    MessageHandler* handler,
    ScanlineStatus* status) {
  MultipleFrameReader* reader = NULL;

  *status = ScanlineStatus(SCANLINE_STATUS_SUCCESS);
  if (image_type == IMAGE_GIF) {
    // Native ImageFrame implementation
    reader = new GifFrameReader(handler);
    if (reader == NULL) {
      *status = PS_LOGGED_STATUS(
          PS_LOG_ERROR, handler,
          SCANLINE_STATUS_MEMORY_ERROR,
          SCANLINE_UTIL,
          "failed to allocate GifFrameReader");
    }
  } else {
    // Image formats for which we do not have an ImageFrame
    // implementation result in a wrapper around the corresponding
    // Scanline object.
    net_instaweb::scoped_ptr<ScanlineReaderInterface> scanline_reader(
        InstantiateScanlineReader(image_type, handler, status));
    if (status->Success()) {
      reader = new ScanlineToFrameReaderAdapter(
          scanline_reader.release(), handler);
      if (reader == NULL) {
        *status = PS_LOGGED_STATUS(
            PS_LOG_ERROR, handler,
            SCANLINE_STATUS_MEMORY_ERROR,
            SCANLINE_UTIL,
            "failed to allocate ScanlineToFrameReaderAdapter");
      }
    }
  }

  return reader;
}

// Returns an initialized image frame reader.
MultipleFrameReader* CreateImageFrameReader(
    ImageFormat image_type,
    const void* image_buffer,
    size_t buffer_length,
    QuirksMode quirks_mode,
    MessageHandler* handler,
    ScanlineStatus* status) {
  net_instaweb::scoped_ptr<MultipleFrameReader> reader(
      InstantiateImageFrameReader(image_type, handler, status));
  return (status->Success() &&
          reader->set_quirks_mode(quirks_mode, status) &&
          reader->Initialize(image_buffer, buffer_length, status)) ?
          reader.release() : NULL;
}

// Instantiates an uninitialized image frame writer.
MultipleFrameWriter* InstantiateImageFrameWriter(
    ImageFormat image_type,
    MessageHandler* handler,
    ScanlineStatus* status) {
  MultipleFrameWriter* allocated_writer = NULL;
  *status = ScanlineStatus(SCANLINE_STATUS_SUCCESS);

  if (image_type == IMAGE_WEBP) {
    // Native ImageFrame implementation
    allocated_writer = new WebpFrameWriter(handler);
    if (allocated_writer == NULL) {
      *status = PS_LOGGED_STATUS(
          PS_LOG_ERROR, handler,
          SCANLINE_STATUS_MEMORY_ERROR,
          SCANLINE_UTIL,
          "failed to allocate WebpFrameReader");
    }
  } else {
    // Image formats for which we do not have an ImageFrame
    // implementation result in a wrapper around the corresponding
    // Scanline object.
    net_instaweb::scoped_ptr<ScanlineWriterInterface> scanline_writer(
        InstantiateScanlineWriter(image_type, handler, status));
    if (status->Success()) {
      allocated_writer = new ScanlineToFrameWriterAdapter(
          scanline_writer.release(), handler);
      if (allocated_writer == NULL) {
        *status = PS_LOGGED_STATUS(
            PS_LOG_ERROR, handler,
            SCANLINE_STATUS_MEMORY_ERROR,
            SCANLINE_UTIL,
            "failed to allocate ScanlineToFrameWriterAdapter");
      }
    }
  }
  return allocated_writer;
}

// Returns an initialized image frame writer.
MultipleFrameWriter* CreateImageFrameWriter(
    ImageFormat image_type,
    const void* config,
    GoogleString* image_data,
    MessageHandler* handler,
    ScanlineStatus* status) {
  net_instaweb::scoped_ptr<MultipleFrameWriter> writer(
      InstantiateImageFrameWriter(image_type, handler, status));
  return (status->Success() &&
          writer->Initialize(config, image_data, status)) ?
      writer.release() : NULL;
}

////////// Utilities

bool ReadImage(ImageFormat image_type,
               const void* image_buffer,
               size_t buffer_length,
               void** pixels,
               PixelFormat* pixel_format,
               size_t* width,
               size_t* height,
               size_t* stride,
               MessageHandler* handler) {
  // Instantiate and initialize the reader based on image type.
  net_instaweb::scoped_ptr<ScanlineReaderInterface> reader;
  reader.reset(CreateScanlineReader(image_type, image_buffer, buffer_length,
                                    handler));
  if (reader.get() == NULL) {
    return false;
  }

  // The following information is available after the reader is initialized.
  // Copy them to the outputs if they are requested.
  if (pixel_format != NULL) {
    *pixel_format = reader->GetPixelFormat();
  }
  if (width != NULL) {
    *width = reader->GetImageWidth();
  }
  if (height != NULL) {
    *height = reader->GetImageHeight();
  }

  // Round up stride to a multiplier of 4.
  size_t bytes_per_row4 = (((reader->GetBytesPerScanline() + 3) >> 2) << 2);
  if (stride != NULL) {
    *stride = bytes_per_row4;
  }

  // Decode the image data (pixels) if it has been requested.
  if (pixels == NULL) {
    return true;
  }
  *pixels = NULL;
  const size_t data_length = reader->GetImageHeight() * bytes_per_row4;
  unsigned char* image_data = static_cast<unsigned char*>(malloc(data_length));
  if (image_data == NULL) {
    return false;
  }

  unsigned char* row_data = image_data;
  unsigned char* scanline = NULL;
  while (reader->HasMoreScanLines()) {
    if (!reader->ReadNextScanline(reinterpret_cast<void**>(&scanline))) {
      free(image_data);
      return false;
    }
    memcpy(row_data, scanline, reader->GetBytesPerScanline());
    row_data += bytes_per_row4;
  }

  *pixels = static_cast<void*>(image_data);
  return true;
}

}  // namespace image_compression

}  // namespace pagespeed
