/*
 * 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_BUCKETSERVERLOCATION_H_
#define GEODE_BUCKETSERVERLOCATION_H_

#include <string>

#include <geode/CacheableBuiltins.hpp>

#include "ServerLocation.hpp"

namespace apache {
namespace geode {
namespace client {

class BucketServerLocation : public ServerLocation {
 private:
  int m_bucketId;
  bool m_isPrimary;
  int8_t m_version;
  std::shared_ptr<CacheableStringArray> m_serverGroups;
  int8_t m_numServerGroups;

 public:
  BucketServerLocation()
      : ServerLocation(),
        m_bucketId(-1),
        m_isPrimary(false),
        m_version(0),
        m_serverGroups(nullptr),
        m_numServerGroups(static_cast<int8_t>(0)) {}

  explicit BucketServerLocation(std::string host)
      : ServerLocation(host),
        m_bucketId(-1),
        m_isPrimary(false),
        m_version(0),
        m_serverGroups(nullptr),
        m_numServerGroups(static_cast<int8_t>(0)) {}

  BucketServerLocation(int bucketId, int port, std::string host, bool isPrimary,
                       int8_t version)
      : ServerLocation(host, port),
        m_bucketId(bucketId),
        m_isPrimary(isPrimary),
        m_version(version),
        m_serverGroups(nullptr),
        m_numServerGroups(static_cast<int8_t>(0)) {}

  BucketServerLocation(int bucketId, int port, std::string host, bool isPrimary,
                       int8_t version, std::vector<std::string> serverGroups)
      : ServerLocation(host, port),
        m_bucketId(bucketId),
        m_isPrimary(isPrimary),
        m_version(version) {
    int32_t size = static_cast<int32_t>(serverGroups.size());
    if (size > 0) {
      std::vector<std::shared_ptr<CacheableString>> tmpServerGroups;
      tmpServerGroups.reserve(size);
      for (auto&& serverGroup : serverGroups) {
        tmpServerGroups.emplace_back(CacheableString::create(serverGroup));
      }
      if (size > 0x7f) {
        // TODO:  should fail here since m_numServerGroups is int8_t?
      }
      m_serverGroups = CacheableStringArray::create(std::move(tmpServerGroups));
      m_numServerGroups = static_cast<int8_t>(size);
    } else {
      m_serverGroups = nullptr;
      m_numServerGroups = static_cast<int8_t>(0);
    }
  }

  inline int getBucketId() const { return m_bucketId; }

  inline bool isPrimary() const { return m_isPrimary; }

  inline int8_t getVersion() const { return m_version; }

  void toData(DataOutput& output) const override {
    ServerLocation::toData(output);
    output.writeInt(m_bucketId);
    output.writeBoolean(m_isPrimary);
    output.write(m_version);
    output.write(static_cast<int8_t>(m_numServerGroups));
    if (m_numServerGroups > 0) {
      for (auto&& serverGroup : m_serverGroups->value()) {
        output.writeObject(serverGroup);
      }
    }
  }

  void fromData(DataInput& input) override {
    ServerLocation::fromData(input);
    m_bucketId = input.readInt32();
    m_isPrimary = input.readBoolean();
    m_version = input.read();
    m_numServerGroups = input.read();

    if (m_numServerGroups > 0) {
      std::vector<std::shared_ptr<CacheableString>> serverGroups;
      serverGroups.reserve(m_numServerGroups);
      for (int i = 0; i < m_numServerGroups; i++) {
        serverGroups.emplace_back(CacheableString::create(input.readString()));
      }
      m_serverGroups = CacheableStringArray::create(std::move(serverGroups));
    }
  }

  size_t objectSize() const override {
    return sizeof(int32_t) + sizeof(bool) + sizeof(int8_t);
  }

  BucketServerLocation& operator=(const BucketServerLocation& rhs) {
    if (this == &rhs) return *this;
    this->m_serverName = rhs.m_serverName;
    this->m_port = rhs.m_port;
    //(ServerLocation&)*this = rhs;
    this->m_bucketId = rhs.m_bucketId;
    this->m_isPrimary = rhs.m_isPrimary;
    this->m_version = rhs.m_version;
    this->m_numServerGroups = rhs.m_numServerGroups;
    this->m_serverGroups = rhs.m_serverGroups;
    return *this;
  }

  virtual std::string toString() const override {
    std::string out = "";
    out += "[" + m_serverName + ":" + std::to_string(m_port) + "]-" +
           std::to_string(m_bucketId) + "-" + std::to_string(m_isPrimary) +
           "-" + std::to_string(m_version);
    return out;
  }

  BucketServerLocation(
      const BucketServerLocation&
          rhs)  //: ServerLocation(rhs.getServerName(),rhs.getPort())
  {
    this->m_serverName = rhs.m_serverName;
    this->m_port = rhs.m_port;
    this->m_bucketId = rhs.m_bucketId;
    this->m_isPrimary = rhs.m_isPrimary;
    this->m_version = rhs.m_version;
    this->m_numServerGroups = rhs.m_numServerGroups;
    this->m_serverGroups = rhs.m_serverGroups;
  }

  inline std::shared_ptr<CacheableStringArray> getServerGroups() {
    return m_serverGroups;
  }
};

}  // namespace client
}  // namespace geode
}  // namespace apache

namespace std {

template <>
struct hash<apache::geode::client::BucketServerLocation> {
  typedef apache::geode::client::BucketServerLocation argument_type;
  typedef size_t result_type;
  size_t operator()(
      const apache::geode::client::BucketServerLocation& val) const {
    return val.hashcode();
  }
};

}  // namespace std

#endif  // GEODE_BUCKETSERVERLOCATION_H_
