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

#pragma once

#ifndef GEODE_INTEGRATION_TEST_THINCLIENTPDXSERIALIZER_H_
#define GEODE_INTEGRATION_TEST_THINCLIENTPDXSERIALIZER_H_

#include <string>


#include "fw_dunit.hpp"
#include "ThinClientHelper.hpp"
#include "Utils.hpp"
#include "testobject/PdxClassV1.hpp"
#include "testobject/PdxClassV2.hpp"
#include "testobject/NonPdxType.hpp"
#include "ThinClientPdxSerializers.hpp"
#include "CacheRegionHelper.hpp"

#define CLIENT1 s1p1
#define CLIENT2 s1p2
#define LOCATOR s2p2
#define SERVER1 s2p1

namespace {  // NOLINT(google-build-namespaces)

using apache::geode::client::CacheableBoolean;
using apache::geode::client::UserObjectSizer;

using PdxTests::PdxWrapper;
using PdxTests::TestPdxSerializerForV1;

bool isLocator = false;
bool isLocalServer = false;

const char* poolNames[] = {"Pool1", "Pool2", "Pool3"};
const std::string locHostPort =
    CacheHelper::getLocatorHostPort(isLocator, isLocalServer, 1);
bool isPoolConfig = false;  // To track if pool case is running

void initClient(const bool isthinClient, bool isPdxIgnoreUnreadFields) {
  LOGINFO("initClient: isPdxIgnoreUnreadFields = %d ", isPdxIgnoreUnreadFields);
  if (cacheHelper == nullptr) {
    cacheHelper = new CacheHelper(isthinClient, isPdxIgnoreUnreadFields, false,
                                  nullptr, false);
  }
  ASSERT(cacheHelper, "Failed to create a CacheHelper client instance.");
}

void stepOne(bool isPdxIgnoreUnreadFields = false) {
  // Create just one pool and attach all regions to that.
  initClient(true, isPdxIgnoreUnreadFields);
  isPoolConfig = true;
  createPool(poolNames[0], locHostPort, {}, 0, true);
  createRegionAndAttachPool("DistRegionAck", USE_ACK, poolNames[0],
                            false /*Caching disabled*/);
  LOG("StepOne complete.");
}

DUNIT_TASK_DEFINITION(LOCATOR, StartLocator)
  {
    // starting locator 1 2
    if (isLocator) {
      CacheHelper::initLocator(1);
    }
    LOG("Locator started");
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(CLIENT1, StepOnePoolLoc_PDX)
  {
    LOG("Starting Step One with Pool + Locator lists");
    stepOne(true);
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(CLIENT1, StepOnePoolLoc)
  {
    LOG("Starting Step One with Pool + Locator lists");
    stepOne();
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(SERVER1, CreateServerWithLocator)
  {
    LOG("Starting SERVER1...");
    if (isLocalServer) {
      CacheHelper::initServer(1, "cacheserverPdx.xml", locHostPort);
    }
    LOG("SERVER1 started");
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(SERVER1, CreateServerWithLocator1)
  {
    LOG("Starting SERVER1...");
    if (isLocalServer) {
      CacheHelper::initServer(1, "cacheserver.xml", locHostPort);
    }
    LOG("SERVER1 started");
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(SERVER1, CreateServerWithLocator2)
  {
    LOG("Starting SERVER1...");
    if (isLocalServer) {
      CacheHelper::initServer(1, "cacheserverForPdx.xml", locHostPort);
    }
    LOG("SERVER1 started");
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(SERVER1, CreateServerWithLocator3)
  {
    LOG("Starting SERVER1...");
    if (isLocalServer) {
      CacheHelper::initServer(1, "cacheserverPdxSerializer.xml", locHostPort);
    }
    LOG("SERVER1 started");
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(CLIENT2, StepTwoPoolLoc)
  {
    LOG("Starting Step Two with Pool + Locator");
    stepOne();
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(CLIENT2, StepTwoPoolLoc_PDX)
  {
    LOG("Starting Step Two with Pool + Locator");
    stepOne(true);
  }
END_TASK_DEFINITION

void checkPdxInstanceToStringAtServer(std::shared_ptr<Region> region) {
  auto keyPort = CacheableKey::create("success");
  auto checkPdxInstanceToStringAtServer =
      std::dynamic_pointer_cast<CacheableBoolean>(region->get(keyPort));
  bool actualValue = checkPdxInstanceToStringAtServer->value();
  ASSERT(actualValue == true,
         "checkPdxInstanceToStringAtServer: Val should be true");
}

DUNIT_TASK_DEFINITION(CLIENT1, JavaPutGet)
  {
    CacheRegionHelper::getCacheImpl(cacheHelper->getCache().get())
        ->getTypeRegistry()
        .registerPdxSerializer(std::make_shared<TestPdxSerializer>());

    auto region0 = getHelper()->getRegion("DistRegionAck");
    auto keyPort = CacheableKey::create(1);

    auto nonPdxType1 = std::make_shared<PdxTests::NonPdxType>();
    auto pdxWrapper1 = std::make_shared<PdxWrapper>(nonPdxType1, CLASSNAME1);
    region0->put(keyPort, pdxWrapper1);

    auto pdxWrapper2 =
        std::dynamic_pointer_cast<PdxWrapper>(region0->get(keyPort));

    auto regionGetSuccess =
        std::dynamic_pointer_cast<CacheableBoolean>(region0->get("success"));
    bool actualValue = regionGetSuccess->value();

    ASSERT(actualValue == true,
           "Task JavaPutGet:Objects of type NonPdxType should be equal");

    auto nonPdxType2 = std::static_pointer_cast<PdxTests::NonPdxType>(
        pdxWrapper2->getObject());

    ASSERT(nonPdxType1->equals(*nonPdxType2, false), "NonPdxType compare");
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(CLIENT2, JavaGet)
  {
    CacheRegionHelper::getCacheImpl(cacheHelper->getCache().get())
        ->getTypeRegistry()
        .registerPdxSerializer(std::make_shared<TestPdxSerializer>());

    LOGDEBUG("JavaGet-1 Line_309");
    auto region0 = getHelper()->getRegion("DistRegionAck");

    auto keyPort1 = CacheableKey::create(1);

    LOGDEBUG("JavaGet-2 Line_314");
    auto pdxWrapper1 =
        std::dynamic_pointer_cast<PdxWrapper>(region0->get(keyPort1));
    auto nonPdxType1 = std::static_pointer_cast<PdxTests::NonPdxType>(
        pdxWrapper1->getObject());

    LOGDEBUG("JavaGet-3 Line_316");
    auto keyPort2 = CacheableKey::create("putFromjava");

    LOGDEBUG("JavaGet-4 Line_316");
    auto pdxWrapper2 =
        std::dynamic_pointer_cast<PdxWrapper>(region0->get(keyPort2));
    auto nonPdxWrapper2 = std::static_pointer_cast<PdxTests::NonPdxType>(
        pdxWrapper2->getObject());

    LOGDEBUG("JavaGet-5 Line_320");
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(CLIENT1, putFromVersion1_PS)
  {
    auto region0 = getHelper()->getRegion("DistRegionAck");
    auto key = CacheableKey::create(1);

    CacheRegionHelper::getCacheImpl(cacheHelper->getCache().get())
        ->getTypeRegistry()
        .registerPdxSerializer(std::make_shared<TestPdxSerializerForV1>());

    // Create New object and wrap it in PdxWrapper (owner)
    auto nonPdxType1 = std::make_shared<PdxTests::TestDiffTypePdxSV1>(true);
    auto pdxWrapper = std::make_shared<PdxWrapper>(nonPdxType1, V1CLASSNAME2);

    // PUT
    region0->put(key, pdxWrapper);

    // GET
    auto pdxWrapper2 = std::dynamic_pointer_cast<PdxWrapper>(region0->get(key));
    auto nonPdxType2 = std::static_pointer_cast<PdxTests::TestDiffTypePdxSV1>(
        pdxWrapper2->getObject());

    // Equal check
    bool isEqual = nonPdxType1->equals(*nonPdxType2);
    LOGDEBUG("putFromVersion1_PS isEqual = %d", isEqual);
    ASSERT(isEqual == true,
           "Task putFromVersion1_PS:Objects of type TestPdxSerializerForV1 "
           "should be equal");
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(CLIENT2, putFromVersion2_PS)
  {
    auto region0 = getHelper()->getRegion("DistRegionAck");
    auto key = CacheableKey::create(1);

    CacheRegionHelper::getCacheImpl(cacheHelper->getCache().get())
        ->getTypeRegistry()
        .registerPdxSerializer(
            std::shared_ptr<PdxSerializer>(new TestPdxSerializerForV2));

    // Create New object and wrap it in PdxWrapper (owner)
    auto nonPdxType1 = std::make_shared<PdxTests::TestDiffTypePdxSV2>(true);
    auto pdxWrapper = std::make_shared<PdxWrapper>(nonPdxType1, V2CLASSNAME4);

    // PUT
    region0->put(key, pdxWrapper);

    // GET
    auto pdxWrapper2 = std::dynamic_pointer_cast<PdxWrapper>(region0->get(key));
    auto nonPdxType2 = std::static_pointer_cast<PdxTests::TestDiffTypePdxSV2>(
        pdxWrapper2->getObject());

    // Equal check
    bool isEqual = nonPdxType1->equals(*nonPdxType2);
    LOGDEBUG("putFromVersion2_PS isEqual = %d", isEqual);
    ASSERT(isEqual == true,
           "Task putFromVersion2_PS:Objects of type TestPdxSerializerForV2 "
           "should be equal");

    auto key2 = CacheableKey::create(2);
    region0->put(key2, pdxWrapper);
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(CLIENT1, getputFromVersion1_PS)
  {
    auto region0 = getHelper()->getRegion("DistRegionAck");
    auto key = CacheableKey::create(1);

    // GET
    auto pdxWrapper2 = std::dynamic_pointer_cast<PdxWrapper>(region0->get(key));
    auto nonPdxType2 = std::static_pointer_cast<PdxTests::TestDiffTypePdxSV1>(
        pdxWrapper2->getObject());

    // Create New object and Compare
    auto nonPdxType1 = std::make_shared<PdxTests::TestDiffTypePdxSV1>(true);
    bool isEqual = nonPdxType1->equals(*nonPdxType2);
    LOGDEBUG("getputFromVersion1_PS-1 isEqual = %d", isEqual);
    ASSERT(isEqual == true,
           "Task getputFromVersion1_PS:Objects of type TestPdxSerializerForV1 "
           "should be equal");

    // PUT
    region0->put(key, pdxWrapper2);

    auto key2 = CacheableKey::create(2);
    pdxWrapper2 = std::dynamic_pointer_cast<PdxWrapper>(region0->get(key2));
    auto pRet = std::static_pointer_cast<PdxTests::TestDiffTypePdxSV1>(
        pdxWrapper2->getObject());
    isEqual = nonPdxType1->equals(*pRet);
    LOGDEBUG("getputFromVersion1_PS-2 isEqual = %d", isEqual);
    ASSERT(isEqual == true,
           "Task getputFromVersion1_PS:Objects of type TestPdxSerializerForV1 "
           "should be equal");

    // Get then Put.. this should Not merge data back
    auto pdxWrapper = std::make_shared<PdxWrapper>(nonPdxType1, V1CLASSNAME2);
    region0->put(key2, pdxWrapper);
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(CLIENT2, getAtVersion2_PS)
  {
    auto region0 = getHelper()->getRegion("DistRegionAck");
    auto key = CacheableKey::create(1);

    // New object
    auto testDiffTpePdxSV2 = std::unique_ptr<PdxTests::TestDiffTypePdxSV2>(
        new PdxTests::TestDiffTypePdxSV2(true));

    // GET
    auto pdxWrapper2 = std::dynamic_pointer_cast<PdxWrapper>(region0->get(key));
    auto pRet = std::static_pointer_cast<PdxTests::TestDiffTypePdxSV2>(
        pdxWrapper2->getObject());

    bool isEqual = testDiffTpePdxSV2->equals(*pRet);
    LOGDEBUG("getAtVersion2_PS-1 isEqual = %d", isEqual);
    ASSERT(
        isEqual == true,
        "Task getAtVersion2_PS:Objects of type TestPdxSerializerForV2 should "
        "be equal");

    auto key2 = CacheableKey::create(2);
    testDiffTpePdxSV2.reset(new PdxTests::TestDiffTypePdxSV2(true));

    pdxWrapper2 = std::dynamic_pointer_cast<PdxWrapper>(region0->get(key2));
    pRet = std::static_pointer_cast<PdxTests::TestDiffTypePdxSV2>(
        pdxWrapper2->getObject());
    isEqual = testDiffTpePdxSV2->equals(*pRet);

    LOGDEBUG("getAtVersion2_PS-2 isEqual = %d", isEqual);
    ASSERT(
        isEqual == false,
        "Task getAtVersion2_PS:Objects of type TestPdxSerializerForV2 should "
        "be equal");
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(CLIENT1, CloseCache1)
  {
    LOG("cleanProc 1...");
    isPoolConfig = false;
    cleanProc();
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(CLIENT2, CloseCache2)
  {
    LOG("cleanProc 2...");
    isPoolConfig = false;
    cleanProc();
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(SERVER1, CloseServer)
  {
    LOG("closing Server1...");
    if (isLocalServer) {
      CacheHelper::closeServer(1);
      LOG("SERVER1 stopped");
    }
  }
END_TASK_DEFINITION

DUNIT_TASK_DEFINITION(LOCATOR, CloseLocator)
  {
    if (isLocator) {
      CacheHelper::closeLocator(1);
      LOG("Locator1 stopped");
    }
  }
END_TASK_DEFINITION

}  // namespace

#endif  // GEODE_INTEGRATION_TEST_THINCLIENTPDXSERIALIZER_H_
