/**
* 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.
 */
#ifndef IOTDB_DEVICEID_H
#define IOTDB_DEVICEID_H

#include <algorithm>
#include <memory>
#include <numeric>
#include <string>
#include <vector>

namespace storage {

static const int DEFAULT_SEGMENT_NUM_FOR_TABLE_NAME = 3;
static const std::string PATH_SEPARATOR = ".";

class IDeviceID {
public:
  virtual ~IDeviceID() = default;
  virtual std::string get_table_name() {
    return "";
  }
  virtual int segment_num() {
    return 0;
  }
  virtual const std::vector<std::string>& get_segments() const {
    return empty_segments_;
  }
  virtual std::string get_device_name() const {
    return "";
  };
  virtual bool operator<(const IDeviceID& other) {
    return 0;
  }
  virtual bool operator==(const IDeviceID& other) {
    return false;
  }
  virtual bool operator!=(const IDeviceID& other) {
    return false;
  }

protected:
  IDeviceID() : empty_segments_() {}

private:
  const std::vector<std::string> empty_segments_;
};

struct IDeviceIDComparator {
  bool operator()(const std::shared_ptr<IDeviceID>& lhs,
                  const std::shared_ptr<IDeviceID>& rhs) const {
    return *lhs < *rhs;
  }
};

class StringArrayDeviceID : public IDeviceID {
public:
  explicit StringArrayDeviceID(const std::vector<std::string>& segments)
      : segments_(formalize(segments)) {}

  explicit StringArrayDeviceID() : segments_() {}

  ~StringArrayDeviceID() override = default;

  std::string get_device_name() const override {
    return segments_.empty()
               ? ""
               : std::accumulate(
                     std::next(segments_.begin()), segments_.end(), segments_.front(),
                     [](std::string a, const std::string& b) { return std::move(a) + "." + b; });
  };

  std::string get_table_name() override {
    return segments_.empty() ? "" : segments_[0];
  }

  int segment_num() override {
    return static_cast<int>(segments_.size());
  }

  const std::vector<std::string>& get_segments() const override {
    return segments_;
  }

  bool operator<(const IDeviceID& other) override {
    auto other_segments = other.get_segments();
    return std::lexicographical_compare(segments_.begin(), segments_.end(), other_segments.begin(),
                                        other_segments.end());
  }

  bool operator==(const IDeviceID& other) override {
    auto other_segments = other.get_segments();
    return (segments_.size() == other_segments.size()) &&
           std::equal(segments_.begin(), segments_.end(), other_segments.begin());
  }

  bool operator!=(const IDeviceID& other) override {
    return !(*this == other);
  }

private:
  std::vector<std::string> segments_;

  std::vector<std::string> formalize(const std::vector<std::string>& segments) {
    auto it = std::find_if(segments.rbegin(), segments.rend(),
                           [](const std::string& seg) { return !seg.empty(); });
    return std::vector<std::string>(segments.begin(), it.base());
  }

  std::vector<std::string> split_device_id_string(const std::vector<std::string>& splits) {
    size_t segment_cnt = splits.size();
    std::vector<std::string> final_segments;

    if (segment_cnt == 0) {
      return final_segments;
    }

    if (segment_cnt == 1) {
      // "root" -> {"root"}
      final_segments.push_back(splits[0]);
    } else if (segment_cnt < static_cast<size_t>(DEFAULT_SEGMENT_NUM_FOR_TABLE_NAME + 1)) {
      // "root.a" -> {"root", "a"}
      // "root.a.b" -> {"root.a", "b"}
      std::string table_name = std::accumulate(splits.begin(), splits.end() - 1, std::string(),
                                               [](const std::string& a, const std::string& b) {
                                                 return a.empty() ? b : a + PATH_SEPARATOR + b;
                                               });
      final_segments.push_back(table_name);
      final_segments.push_back(splits.back());
    } else {
      // "root.a.b.c" -> {"root.a.b", "c"}
      // "root.a.b.c.d" -> {"root.a.b", "c", "d"}
      std::string table_name =
          std::accumulate(splits.begin(), splits.begin() + DEFAULT_SEGMENT_NUM_FOR_TABLE_NAME,
                          std::string(), [](const std::string& a, const std::string& b) {
                            return a.empty() ? b : a + PATH_SEPARATOR + b;
                          });

      final_segments.emplace_back(std::move(table_name));
      final_segments.insert(final_segments.end(),
                            splits.begin() + DEFAULT_SEGMENT_NUM_FOR_TABLE_NAME, splits.end());
    }

    return final_segments;
  }
};
} // namespace storage

#endif