/*
 * 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 "net/instaweb/spriter/image_library_interface.h"
#include "net/instaweb/spriter/mock_image_library_interface.h"
#include "net/instaweb/spriter/public/image_spriter.h"
#include "net/instaweb/spriter/public/image_spriter.pb.h"
#include "pagespeed/kernel/base/gmock.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/scoped_ptr.h"

namespace net_instaweb {
namespace spriter {

namespace {

// GMock names.
using ::testing::Return;
using ::testing::_;
using ::testing::StrictMock;
using ::testing::DoAll;
using ::testing::SetArgumentPointee;

// Constants used in several tests.
const int kSpriteId = 12345;
const ImageLibraryInterface::FilePath kInBasePath("/input/path");
const ImageLibraryInterface::FilePath kOutBasePath("/output/path");
const ImageLibraryInterface::FilePath kCombinedImagePath("subdir/out.png");
const ImageLibraryInterface::FilePath kPngA("path/to/a.png");
const ImageLibraryInterface::FilePath kPngB("b.png");

// Set up a protobuf of spriting settings.  Tests use this to get
// a reasonable default.
void SetupCommonOptions(SpriterInput* spriter_input,
                        ImageFormat format) {
  spriter_input->set_id(kSpriteId);

  SpriteOptions* options = spriter_input->mutable_options();
  options->set_placement_method(VERTICAL_STRIP);
  options->set_input_base_path(kInBasePath);
  options->set_output_base_path(kOutBasePath);
  options->set_output_image_path(kCombinedImagePath);
  options->set_output_format(format);
}

// This class acts as a delegate to ImageLibraryInterface,
// asserting if any failure occurs.
class FailOnImageLibError : public ImageLibraryInterface::Delegate {
 public:
  // Implement ImageLibInterface::Delegate:
  virtual void OnError(const GoogleString& error) const {
    ASSERT_TRUE(0) << "Unexpected error: " << error;
  }
};

// Test that ImageSpriter produces an empty image when asked to sprite
// zero images.
TEST(SpriterTest, ZeroImages) {
  SpriterInput spriter_input;
  SetupCommonOptions(&spriter_input, PNG);

  // Build mock ojects:
  FailOnImageLibError no_failures_allowed;

  scoped_ptr<StrictMock<MockImageLibraryInterface::MockCanvas> >
      mock_canvas(new StrictMock<MockImageLibraryInterface::MockCanvas>);

  testing::StrictMock<MockImageLibraryInterface>
      mock_image_lib(kInBasePath, kOutBasePath, &no_failures_allowed);

  // No images: Combined image will be 0x0.
  EXPECT_CALL(mock_image_lib, CreateCanvas(0, 0))
      .WillOnce(Return(mock_canvas.get()));

  EXPECT_CALL(*mock_canvas, WriteToFile(kCombinedImagePath, PNG))
      .WillOnce(Return(true));

  // spriter.Sprite() will free the canvas it gets.  In the test,
  // the canvas is mock object |mock_canvas|.  Disown the pointer.
  EXPECT_FALSE(NULL == mock_canvas.release());

  ImageSpriter spriter(&mock_image_lib);
  scoped_ptr<SpriterResult> sprite_result(spriter.Sprite(spriter_input));

  ASSERT_TRUE(sprite_result.get());
  EXPECT_EQ(kSpriteId, sprite_result->id());
  EXPECT_EQ(kOutBasePath, sprite_result->output_base_path());
  EXPECT_EQ(0, sprite_result->image_position_size());
}

// Sprite a single image.  Test that ImageSpriter produces an image
// that is the same size as the input.
TEST(SpriterTest, OneImage) {
  SpriterInput spriter_input;
  SetupCommonOptions(&spriter_input, JPEG);

  spriter_input.add_input_image_set()->set_path(kPngA);

  FailOnImageLibError no_failures_allowed;

  scoped_ptr<StrictMock<MockImageLibraryInterface::MockCanvas> >
      mock_canvas(new StrictMock<MockImageLibraryInterface::MockCanvas>);

  scoped_ptr<StrictMock<MockImageLibraryInterface::MockImage> >
      mock_image_a(new StrictMock<MockImageLibraryInterface::MockImage>);

  testing::StrictMock<MockImageLibraryInterface>
      mock_image_lib(kInBasePath, kOutBasePath, &no_failures_allowed);

  EXPECT_CALL(mock_image_lib, ReadFromFile(kPngA))
      .WillOnce(Return(mock_image_a.get()));

  // Image #1: 10x11.
  EXPECT_CALL(*mock_image_a, GetDimensions(_, _))
      .WillOnce(DoAll(SetArgumentPointee<0>(10),
                      SetArgumentPointee<1>(11),
                      Return(true)));

  EXPECT_CALL(*mock_canvas, DrawImage(mock_image_a.get(), 0, 0))
      .WillOnce(Return(true));

  // Canvas should be 10x11
  EXPECT_CALL(mock_image_lib, CreateCanvas(10, 11))
      .WillOnce(Return(mock_canvas.get()));

  EXPECT_CALL(*mock_canvas, WriteToFile(kCombinedImagePath, JPEG))
      .WillOnce(Return(true));

  // spriter.Sprite() will free these.
  EXPECT_FALSE(NULL == mock_canvas.release());
  EXPECT_FALSE(NULL == mock_image_a.release());

  ImageSpriter spriter(&mock_image_lib);
  scoped_ptr<SpriterResult> sprite_result(spriter.Sprite(spriter_input));

  ASSERT_TRUE(sprite_result.get());
  EXPECT_EQ(kSpriteId, sprite_result->id());
  EXPECT_EQ(kOutBasePath, sprite_result->output_base_path());
  EXPECT_EQ(1, sprite_result->image_position_size());
}

// Sprite two images.  Test that ImageSpriter produces an image
// that is the right overall size, and places the two images in
// the expected locations within the combined image.
TEST(SpriterTest, TwoImages) {
  SpriterInput spriter_input;
  SetupCommonOptions(&spriter_input, PNG);

  // Add two mock images.
  spriter_input.add_input_image_set()->set_path(kPngA);
  spriter_input.add_input_image_set()->set_path(kPngB);

  FailOnImageLibError no_failures_allowed;

  scoped_ptr<StrictMock<MockImageLibraryInterface::MockCanvas> >
      mock_canvas(new StrictMock<MockImageLibraryInterface::MockCanvas>);

  scoped_ptr<StrictMock<MockImageLibraryInterface::MockImage> >
      mock_image_a(new StrictMock<MockImageLibraryInterface::MockImage>);

  scoped_ptr<StrictMock<MockImageLibraryInterface::MockImage> >
      mock_image_b(new StrictMock<MockImageLibraryInterface::MockImage>);

  testing::StrictMock<MockImageLibraryInterface>
      mock_image_lib(kInBasePath, kOutBasePath, &no_failures_allowed);

  EXPECT_CALL(mock_image_lib, ReadFromFile(kPngA))
      .WillOnce(Return(mock_image_a.get()));

  EXPECT_CALL(*mock_image_a, GetDimensions(_, _))
      .WillOnce(DoAll(SetArgumentPointee<0>(10),
                      SetArgumentPointee<1>(11),
                      Return(true)));

  EXPECT_CALL(mock_image_lib, ReadFromFile(kPngB))
      .WillOnce(Return(mock_image_b.get()));

  EXPECT_CALL(*mock_image_b, GetDimensions(_, _))
      .WillOnce(DoAll(SetArgumentPointee<0>(20),
                      SetArgumentPointee<1>(21),
                      Return(true)));

  int expected_width = 20;  // max(10, 20)
  int expected_hieght = 32;  // 11 + 21
  EXPECT_CALL(mock_image_lib, CreateCanvas(expected_width, expected_hieght))
      .WillOnce(Return(mock_canvas.get()));

  // Draw the first image at the origin.
  EXPECT_CALL(*mock_canvas, DrawImage(mock_image_a.get(), 0, 0))
      .WillOnce(Return(true));

  // Draw the second image just below the first.
  EXPECT_CALL(*mock_canvas, DrawImage(mock_image_b.get(), 0, 11))
      .WillOnce(Return(true));

  EXPECT_CALL(*mock_canvas, WriteToFile(kCombinedImagePath, PNG))
      .WillOnce(Return(true));

  // spriter.Sprite() will free these.
  EXPECT_FALSE(NULL == mock_canvas.release());
  EXPECT_FALSE(NULL == mock_image_a.release());
  EXPECT_FALSE(NULL == mock_image_b.release());

  ImageSpriter spriter(&mock_image_lib);
  scoped_ptr<SpriterResult> sprite_result(spriter.Sprite(spriter_input));

  ASSERT_TRUE(sprite_result.get());
  EXPECT_EQ(kSpriteId, sprite_result->id());
  EXPECT_EQ(kOutBasePath, sprite_result->output_base_path());
  EXPECT_EQ(2, sprite_result->image_position_size());
  EXPECT_EQ(kCombinedImagePath, sprite_result->output_image_path());
}
}  // namesapce
}  // namespace spriter
}  // namespace net_instaweb
