text: /*
   * (C) Copyright 2020 ECMWF.
   *
   * This software is licensed under the terms of the Apache Licence Version 2.0
   * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
   * In applying this licence, ECMWF does not waive the privileges and immunities
   * granted to it by virtue of its status as an intergovernmental organisation
   * nor does it submit to any jurisdiction.
   */
  
  #include "TableFormat.h"
  
  #include <iomanip>
  
  #include "atlas/io/Exceptions.h"
  #include "atlas/io/Metadata.h"
  #include "atlas/io/Record.h"
  #include "atlas/io/RecordItemReader.h"
  #include "atlas/io/Session.h"
  #include "atlas/io/print/Bytes.h"
  #include "atlas/io/types/array/ArrayReference.h"
  #include "atlas/io/types/scalar.h"
  #include "atlas/runtime/Exception.h"
  
  namespace atlas {
  namespace io {
  
  
  std::ostream& operator<<(std::ostream& out, const MetadataPrettyPrintBase& p) {
      p.print(out);
      return out;
  }
  
  std::string MetadataPrettyPrintBase::str() const {
      std::stringstream s;
      print(s);
      return s.str();
  }
  
  class DefaultMetadataPrettyPrint : public MetadataPrettyPrintBase {
  public:
      DefaultMetadataPrettyPrint(const Metadata&) {}
      void print(std::ostream&) const override {}
  };
  
  class ArrayMetadataPrettyPrint : public MetadataPrettyPrintBase {
      template <typename T>
      void print_value(std::ostream& out) const {
          std::vector<T> value;
          metadata_.get("value", value);
          out << "{";
          for (size_t i = 0; i < value.size(); ++i) {
              out << value[i];
              if (i < value.size() - 1) {
                  out << ",";
              }
          }
          out << "}";
      }
  
  public:
      ArrayMetadataPrettyPrint(const Metadata& m): metadata_(m) {}
      void print(std::ostream& out) const override {
          std::string type = metadata_.getString("type");
          ATLAS_ASSERT(type == "array");
          ArrayMetadata array(metadata_);
          out << std::setw(7) << std::left << array.datatype().str();
          if (metadata_.has("value")) {
              out << ": ";
              std::string datatype = metadata_.getString("datatype");
              if (datatype == array::DataType::str<double>()) {
                  print_value<double>(out);
              }
              else if (datatype == array::DataType::str<float>()) {
                  print_value<float>(out);
              }
              else if (datatype == array::DataType::str<size_t>()) {
                  print_value<size_t>(out);
              }
              else if (datatype == array::DataType::str<std::int32_t>()) {
                  print_value<std::int32_t>(out);
              }
              else if (datatype == array::DataType::str<std::int64_t>()) {
                  print_value<std::int64_t>(out);
              }
          }
          else {
              out << "[";
              for (int i = 0; i < array.rank(); ++i) {
                  out << array.shape(i);
                  if (i < array.rank() - 1) {
                      out << ",";
                  }
              }
              out << "]";
          }
      }
  
  private:
      Metadata metadata_;
  };
  
  class StringMetadataPrettyPrint : public MetadataPrettyPrintBase {
  public:
      StringMetadataPrettyPrint(const Metadata& m): metadata_(m) {}
      void print(std::ostream& out) const override {
          std::string type = metadata_.getString("type");
          ATLAS_ASSERT(type == "string");
          std::string value = metadata_.getString("value");
          if (value.size() <= 32) {
              out << value;
          }
          else {
              out << value.substr(0, 32) << "...";
          }
      }
  
  private:
      Metadata metadata_;
  };
  
  class ScalarMetadataPrettyPrint : public MetadataPrettyPrintBase {
  public:
      ScalarMetadataPrettyPrint(const Metadata& m): metadata_(m) {}
      template <typename T>
      T decode() const {
          T value;
          Data dummy;
          atlas::io::decode(metadata_, dummy, value);
          return value;
      }
      void print(std::ostream& out) const override {
          std::string type = metadata_.getString("type");
          ATLAS_ASSERT(type == "scalar");
          std::string datatype = metadata_.getString("datatype");
          std::string base64   = metadata_.getString("base64");
          out << std::setw(7) << std::left << datatype << ": ";
          if (datatype == array::DataType::str<double>()) {
              out << decode<double>();
          }
          else if (datatype == array::DataType::str<float>()) {
              out << decode<float>();
          }
          else if (datatype == array::DataType::str<size_t>()) {
              out << decode<size_t>();
          }
          else if (datatype == array::DataType::str<std::int32_t>()) {
              out << decode<std::int32_t>();
          }
          else if (datatype == array::DataType::str<std::int64_t>()) {
              out << decode<std::int64_t>();
          }
      }
  
  private:
      Metadata metadata_;
  };
  
  
  MetadataPrettyPrint::MetadataPrettyPrint(const atlas::io::Metadata& m) {
      std::string type = m.getString("type");
  
      // Poor man factory builder
      if (type == "array") {
          impl_.reset(new ArrayMetadataPrettyPrint(m));
      }
      else if (type == "scalar") {
          impl_.reset(new ScalarMetadataPrettyPrint(m));
      }
      else if (type == "string") {
          impl_.reset(new StringMetadataPrettyPrint(m));
      }
      else {
          impl_.reset(new DefaultMetadataPrettyPrint(m));
      }
  }
  
  std::ostream& operator<<(std::ostream& out, const MetadataPrettyPrint& p) {
      out << *p.impl_;
      return out;
  }
  std::string MetadataPrettyPrint::str() const {
      return impl_->str();
  }
  
  
  struct TablePrinter {
      std::vector<std::vector<std::string> > columns;
      std::vector<size_t> widths;
      std::string indent{"    "};
      std::string sep{" "};
      int col{0};
      int row{1};
      std::vector<bool> optional;
      std::vector<bool> underline;
  
  
      TablePrinter() = default;
  
      TablePrinter& column(const std::string& title, size_t width = 0) {
          columns.emplace_back(std::vector<std::string>{title});
          widths.emplace_back(std::max(title.size(), width));
          optional.push_back(false);
          underline.push_back(true);
          return *this;
      }
      TablePrinter& column() {
          columns.emplace_back(std::vector<std::string>{""});
          widths.emplace_back(0);
          optional.push_back(true);
          underline.push_back(false);
          return *this;
      }
  
      TablePrinter& operator<<(const MetadataPrettyPrint p) {
          *this << p.str();
          return *this;
      }
  
      TablePrinter& operator<<(const std::string& s) {
          columns[col].emplace_back(s);
          widths[col] = std::max(widths[col], s.size());
          if (optional[col] && widths[col] > 0) {
              optional[col] = false;
              widths[col]   = std::max(widths[col], columns[col][0].size());
          }
          ++col;
          if (col == columns.size()) {
              ++row;
              col = 0;
          }
          return *this;
      }
  
      void print(std::ostream& out) const {
          out << " ";
          for (size_t c = 0; c < columns.size(); ++c) {
              out << " " << sep << " " << std::setw(widths[c]) << std::left << columns[c][0];
          }
          out << " " << sep << std::endl;
  
          out << " ";
          for (size_t c = 0; c < columns.size(); ++c) {
              const char u = underline[c] ? '-' : ' ';
              out << " " << sep << " " << std::string(widths[c], u);  // underline
          }
          out << " " << sep << std::endl;
  
          for (size_t r = 1; r < row; ++r) {
              out << " ";
              for (size_t c = 0; c < columns.size(); ++c) {
                  out << " " << sep << " " << std::setw(widths[c]) << std::left << columns[c][r];
              }
              out << " " << sep << std::endl;
          }
      }
  };
  
  
  TableFormat::TableFormat(const Record::URI& record, const util::Config& config):
      record_(Session::record(record.path, record.offset)) {
      for (const auto& key : record_.keys()) {
          items_.emplace(key, Metadata());
          RecordItemReader{RecordItem::URI{record.path, record.offset, key}}.read(items_.at(key));
      }
  
      config.get("details", print_details_);
  }
  
  void TableFormat::print(std::ostream& out) const {
      ATLAS_ASSERT(not record_.empty());
  
      TablePrinter table;
      table.column("name");
      table.column("type");
      table.column("description");
      if (print_details_) {
          table.column("ver.");
          table.column("comp.");
          table.column("size");
          table.column("checksum[:8]");
          table.column("endian");
          table.column("created");
      }
      table.column();
  
      for (auto& key : record_.keys()) {
          auto& item          = items_.at(key);
          size_t cbytes       = item.data.compressed_size();
          size_t ubytes       = item.data.size();
          std::string endian  = (item.data.endian() == Endian::little) ? "little" : "big";
          std::string created = item.record.created().str().substr(0, 16);
          created[10]         = ' ';
          table << key;
          table << item.getString("type");
          table << MetadataPrettyPrint{item};
          if (print_details_) {
              table << item.record.version();
              if (item.data.section()) {
                  table << item.data.compression();
                  table << Bytes{cbytes}.str() + (item.data.compressed() ? " < " + Bytes{ubytes}.str() : "");
                  table << Checksum{item.data.checksum()}.str(8);
                  table << endian;
              }
              else {
                  table << ""
                        << ""
                        << ""
                        << "";
              }
              table << created;
          }
  
          if (record_.metadata(key).link()) {
              table << "-> " + record_.metadata(key).link().str();
          }
          else {
              table << "";
          }
      }
      table.print(out);
  }
  
  
  }  // namespace io
  }  // namespace atlas
  