//  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).

/*
 * This header file defines miscellaneous utility classes.
 *
 * @author Tian Xia <tianx@fb.com>
 */

#pragma once

#include <sstream>
#include "FbsonDocument.h"

namespace fbson {

#define OUT_BUF_SIZE 1024

/*
 * FbsonToJson converts an FbsonValue object to a JSON string.
 */
class FbsonToJson {
 public:
  FbsonToJson() : os_(buffer_, OUT_BUF_SIZE) {}

  // get json string
  const char* json(const FbsonValue* pval) {
    os_.clear();
    os_.seekp(0);

    if (pval) {
      intern_json(pval);
    }

    os_.put(0);
    return os_.getBuffer();
  }

 private:
  // recursively convert FbsonValue
  void intern_json(const FbsonValue* val) {
    switch (val->type()) {
    case FbsonType::T_Null: {
      os_.write("null", 4);
      break;
    }
    case FbsonType::T_True: {
      os_.write("true", 4);
      break;
    }
    case FbsonType::T_False: {
      os_.write("false", 5);
      break;
    }
    case FbsonType::T_Int8: {
      os_.write(((Int8Val*)val)->val());
      break;
    }
    case FbsonType::T_Int16: {
      os_.write(((Int16Val*)val)->val());
      break;
    }
    case FbsonType::T_Int32: {
      os_.write(((Int32Val*)val)->val());
      break;
    }
    case FbsonType::T_Int64: {
      os_.write(((Int64Val*)val)->val());
      break;
    }
    case FbsonType::T_Double: {
      os_.write(((DoubleVal*)val)->val());
      break;
    }
    case FbsonType::T_String: {
      os_.put('"');
      os_.write(((StringVal*)val)->getBlob(), ((StringVal*)val)->getBlobLen());
      os_.put('"');
      break;
    }
    case FbsonType::T_Binary: {
      os_.write("\"<BINARY>", 9);
      os_.write(((BinaryVal*)val)->getBlob(), ((BinaryVal*)val)->getBlobLen());
      os_.write("<BINARY>\"", 9);
      break;
    }
    case FbsonType::T_Object: {
      object_to_json((ObjectVal*)val);
      break;
    }
    case FbsonType::T_Array: {
      array_to_json((ArrayVal*)val);
      break;
    }
    default:
      break;
    }
  }

  // convert object
  void object_to_json(const ObjectVal* val) {
    os_.put('{');

    auto iter = val->begin();
    auto iter_fence = val->end();

    while (iter < iter_fence) {
      // write key
      if (iter->klen()) {
        os_.put('"');
        os_.write(iter->getKeyStr(), iter->klen());
        os_.put('"');
      } else {
        os_.write(iter->getKeyId());
      }
      os_.put(':');

      // convert value
      intern_json(iter->value());

      ++iter;
      if (iter != iter_fence) {
        os_.put(',');
      }
    }

    assert(iter == iter_fence);

    os_.put('}');
  }

  // convert array to json
  void array_to_json(const ArrayVal* val) {
    os_.put('[');

    auto iter = val->begin();
    auto iter_fence = val->end();

    while (iter != iter_fence) {
      // convert value
      intern_json((const FbsonValue*)iter);
      ++iter;
      if (iter != iter_fence) {
        os_.put(',');
      }
    }

    assert(iter == iter_fence);

    os_.put(']');
  }

 private:
  FbsonOutStream os_;
  char buffer_[OUT_BUF_SIZE];
};

} // namespace fbson
