/*
 * 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/opt/http/fallback_property_page.h"

#include <cstddef>

#include "testing/base/public/gunit.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/mock_timer.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/cache/lru_cache.h"
#include "pagespeed/kernel/http/google_url.h"
#include "pagespeed/kernel/util/platform.h"
#include "pagespeed/kernel/util/simple_stats.h"
#include "pagespeed/opt/http/cache_property_store.h"
#include "pagespeed/opt/http/mock_property_page.h"
#include "pagespeed/opt/http/property_cache.h"
#include "pagespeed/opt/http/property_store.h"

namespace net_instaweb {

const size_t kMaxCacheSize = 200;
const char kCohortName1[] = "cohort1";
const char kCacheKey1[] = "Key1";
const char kCacheKey2[] = "Key2";
const char kPropertyName1[] = "prop1";
const char kValue1[] = "value1";
const char kValue2[] = "value2";
const char kOptionsSignatureHash[] = "hash";
const char kCacheKeySuffix[] = "CacheKeySuffix";

class FallbackPropertyPageTest : public testing::Test {
 protected:
  FallbackPropertyPageTest()
      : lru_cache_(kMaxCacheSize),
        thread_system_(Platform::CreateThreadSystem()),
        timer_(thread_system_->NewMutex(), MockTimer::kApr_5_2010_ms),
        stats_(thread_system_.get()),
        cache_property_store_(
            "test/", &lru_cache_, &timer_, &stats_, thread_system_.get()),
        property_cache_(&cache_property_store_,
                        &timer_,
                        &stats_,
                        thread_system_.get()) {
    PropertyCache::InitCohortStats(kCohortName1, &stats_);
    PropertyStoreGetCallback::InitStats(&stats_);
    cohort_ = property_cache_.AddCohort(kCohortName1);
    cache_property_store_.AddCohort(cohort_->name());
  }

  // Sets both actual and fallback property page.
  void SetupFallbackPage() {
    PropertyPage* actual_property_page = new MockPropertyPage(
        thread_system_.get(),
        &property_cache_,
        kCacheKey1,
        kOptionsSignatureHash,
        kCacheKeySuffix);
    PropertyPage* fallback_property_page = new MockPropertyPage(
        thread_system_.get(),
        &property_cache_,
        kCacheKey2,
        kOptionsSignatureHash,
        kCacheKeySuffix);
    fallback_page_.reset(new FallbackPropertyPage(
        actual_property_page, fallback_property_page));
    property_cache_.Read(actual_property_page);
    property_cache_.Read(fallback_property_page);
  }

  void CheckNoValuePreset() {
    PropertyValue* property =
        fallback_page_->GetProperty(cohort_, kPropertyName1);
    EXPECT_FALSE(property->has_value());
    property = fallback_page_->actual_property_page()->GetProperty(
        cohort_, kPropertyName1);
    EXPECT_FALSE(property->has_value());
    property =
        fallback_page_->property_page_with_fallback_values()->GetProperty(
            cohort_, kPropertyName1);
    EXPECT_FALSE(property->has_value());
  }

  void CheckValueIsPresent(const GoogleString& value) {
    // Property present in page.
    PropertyValue* property =
        fallback_page_->GetProperty(cohort_, kPropertyName1);
    EXPECT_TRUE(property->has_value());
    EXPECT_EQ(value, property->value());

    // Property present in actual page.
    property = fallback_page_->actual_property_page()->GetProperty(
        cohort_, kPropertyName1);
    EXPECT_TRUE(property->has_value());
    EXPECT_EQ(value, property->value());

    // Property present in fallback page.
    property =
        fallback_page_->property_page_with_fallback_values()->GetProperty(
            cohort_, kPropertyName1);
    EXPECT_TRUE(property->has_value());
    EXPECT_EQ(value, property->value());
  }

  scoped_ptr<FallbackPropertyPage> fallback_page_;
  LRUCache lru_cache_;
  scoped_ptr<ThreadSystem> thread_system_;
  MockTimer timer_;
  SimpleStats stats_;
  CachePropertyStore cache_property_store_;
  PropertyCache property_cache_;
  const PropertyCache::Cohort* cohort_;

 private:
  DISALLOW_COPY_AND_ASSIGN(FallbackPropertyPageTest);
};

TEST_F(FallbackPropertyPageTest, TestIfNoFallbackPageSet) {
  PropertyPage* actual_property_page = new MockPropertyPage(
      thread_system_.get(),
      &property_cache_,
      kCacheKey1,
      kOptionsSignatureHash,
      kCacheKeySuffix);
  fallback_page_.reset(new FallbackPropertyPage(actual_property_page, NULL));
  property_cache_.Read(actual_property_page);
  fallback_page_->UpdateValue(cohort_, kPropertyName1, kValue1);
  fallback_page_->WriteCohort(cohort_);
  PropertyValue* property =
      fallback_page_->GetProperty(cohort_, kPropertyName1);
  EXPECT_TRUE(property->has_value());
  EXPECT_EQ(kValue1, property->value());
}

TEST_F(FallbackPropertyPageTest, TestUpdatePropertyIfFallbackPageIsSet) {
  SetupFallbackPage();
  // Intially, No value present.
  CheckNoValuePreset();
  // Update property in both pages.
  fallback_page_->UpdateValue(cohort_, kPropertyName1, kValue1);
  fallback_page_->WriteCohort(cohort_);

  CheckValueIsPresent(kValue1);
}

TEST_F(FallbackPropertyPageTest, TestDeletePropertyIfFallbackPageIsSet) {
  SetupFallbackPage();
  // Intially, no value present.
  CheckNoValuePreset();

  // Update property in both pages.
  fallback_page_->UpdateValue(cohort_, kPropertyName1, kValue1);
  fallback_page_->WriteCohort(cohort_);

  // Delete will delete from both the pages.
  fallback_page_->DeleteProperty(cohort_, kPropertyName1);

  // No value present.
  CheckNoValuePreset();
}

TEST_F(FallbackPropertyPageTest, TestGetPropertyIfFallbackPageIsSet) {
  SetupFallbackPage();
  // Intially, No value present.
  CheckNoValuePreset();

  // Update property in only fallback property page.
  fallback_page_->property_page_with_fallback_values()->UpdateValue(
      cohort_, kPropertyName1, kValue1);
  fallback_page_->WriteCohort(cohort_);

  // No property present in actual page.
  PropertyValue* property = fallback_page_->actual_property_page()->GetProperty(
      cohort_, kPropertyName1);
  EXPECT_FALSE(property->has_value());

  // GetProperty will return value from fallback page.
  property = fallback_page_->GetProperty(cohort_, kPropertyName1);
  EXPECT_TRUE(property->has_value());
  EXPECT_EQ(kValue1, property->value());

  // Update actual page with different value.
  fallback_page_->actual_property_page()->UpdateValue(
      cohort_, kPropertyName1, kValue2);
  // GetProperty will return value from actual page.
  property = fallback_page_->GetProperty(cohort_, kPropertyName1);
  EXPECT_TRUE(property->has_value());
  EXPECT_EQ(kValue2, property->value());
}

TEST_F(FallbackPropertyPageTest, TestWriteCohortIfFallbackPageIsSet) {
  SetupFallbackPage();
  // Intially, no value present.
  CheckNoValuePreset();

  // Update property in both pages.
  fallback_page_->UpdateValue(cohort_, kPropertyName1, kValue1);
  fallback_page_->WriteCohort(cohort_);

  // Re-read the values from cache.
  SetupFallbackPage();

  CheckValueIsPresent(kValue1);
}

TEST_F(FallbackPropertyPageTest, TestGetFallbackPageUrl) {
  GoogleString fallback_path("http://www.abc.com/b/");
  GoogleString device_type_suffix("0");
  GoogleUrl url_query(StrCat(fallback_path, "?c=d"));
  GoogleUrl url_base_path(StrCat(fallback_path, "c/"));

  EXPECT_EQ(StrCat(fallback_path, "@fallback"),
            FallbackPropertyPage::GetFallbackPageUrl(url_query));
  EXPECT_EQ(StrCat(fallback_path, "#fallback"),
            FallbackPropertyPage::GetFallbackPageUrl(url_base_path));
}

TEST_F(FallbackPropertyPageTest, TestIsFallbackUrl) {
  EXPECT_FALSE(FallbackPropertyPage::IsFallbackUrl("http://www.abc.com/b/"));
  EXPECT_TRUE(
      FallbackPropertyPage::IsFallbackUrl("http://www.abc.com/b/@fallback"));
  EXPECT_TRUE(
      FallbackPropertyPage::IsFallbackUrl("http://www.abc.com/b/#fallback"));
}

}  // namespace net_instaweb
