// base/io-funcs.cc

// Copyright 2009-2011  Microsoft Corporation;  Saarland University

// See ../../COPYING for clarification regarding multiple authors
//
// Licensed 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

// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
// MERCHANTABLITY OR NON-INFRINGEMENT.
// See the Apache 2 License for the specific language governing permissions and
// limitations under the License.

#include "base/io-funcs.h"
#include "base/kaldi-math.h"

namespace kaldi {

template <>
void WriteBasicType<bool>(std::ostream& os, bool binary, bool b) {
  os << (b ? "T" : "F");
  if (!binary) os << " ";
  if (os.fail()) KALDI_ERR << "Write failure in WriteBasicType<bool>";
}

template <>
void ReadBasicType<bool>(std::istream& is, bool binary, bool* b) {
  KALDI_PARANOID_ASSERT(b != NULL);
  if (!binary) is >> std::ws;  // eat up whitespace.
  char c = is.peek();
  if (c == 'T') {
    *b = true;
    is.get();
  } else if (c == 'F') {
    *b = false;
    is.get();
  } else {
    KALDI_ERR << "Read failure in ReadBasicType<bool>, file position is "
              << is.tellg() << ", next char is " << CharToString(c);
  }
}

template <>
void WriteBasicType<float>(std::ostream& os, bool binary, float f) {
  if (binary) {
    char c = sizeof(f);
    os.put(c);
    os.write(reinterpret_cast<const char*>(&f), sizeof(f));
  } else {
    os << f << " ";
  }
}

template <>
void WriteBasicType<double>(std::ostream& os, bool binary, double f) {
  if (binary) {
    char c = sizeof(f);
    os.put(c);
    os.write(reinterpret_cast<const char*>(&f), sizeof(f));
  } else {
    os << f << " ";
  }
}

template <>
void ReadBasicType<float>(std::istream& is, bool binary, float* f) {
  KALDI_PARANOID_ASSERT(f != NULL);
  if (binary) {
    double d;
    int c = is.peek();
    if (c == sizeof(*f)) {
      is.get();
      is.read(reinterpret_cast<char*>(f), sizeof(*f));
    } else if (c == sizeof(d)) {
      ReadBasicType(is, binary, &d);
      *f = d;
    } else {
      KALDI_ERR << "ReadBasicType: expected float, saw " << is.peek()
                << ", at file position " << is.tellg();
    }
  } else {
    is >> *f;
  }
  if (is.fail()) {
    KALDI_ERR << "ReadBasicType: failed to read, at file position "
              << is.tellg();
  }
}

template <>
void ReadBasicType<double>(std::istream& is, bool binary, double* d) {
  KALDI_PARANOID_ASSERT(d != NULL);
  if (binary) {
    float f;
    int c = is.peek();
    if (c == sizeof(*d)) {
      is.get();
      is.read(reinterpret_cast<char*>(d), sizeof(*d));
    } else if (c == sizeof(f)) {
      ReadBasicType(is, binary, &f);
      *d = f;
    } else {
      KALDI_ERR << "ReadBasicType: expected float, saw " << is.peek()
                << ", at file position " << is.tellg();
    }
  } else {
    is >> *d;
  }
  if (is.fail()) {
    KALDI_ERR << "ReadBasicType: failed to read, at file position "
              << is.tellg();
  }
}

void CheckToken(const char* token) {
  if (*token == '\0') KALDI_ERR << "Token is empty (not a valid token)";
  const char* orig_token = token;
  while (*token != '\0') {
    if (::isspace(*token))
      KALDI_ERR << "Token is not a valid token (contains space): '"
                << orig_token << "'";
    token++;
  }
}

void WriteToken(std::ostream& os, bool binary, const char* token) {
  // binary mode is ignored;
  // we use space as termination character in either case.
  KALDI_ASSERT(token != NULL);
  CheckToken(token);  // make sure it's valid (can be read back)
  os << token << " ";
  if (os.fail()) {
    KALDI_ERR << "Write failure in WriteToken.";
  }
}

int Peek(std::istream& is, bool binary) {
  if (!binary) is >> std::ws;  // eat up whitespace.
  return is.peek();
}

void WriteToken(std::ostream& os, bool binary, const std::string& token) {
  WriteToken(os, binary, token.c_str());
}

void ReadToken(std::istream& is, bool binary, std::string* str) {
  KALDI_ASSERT(str != NULL);
  if (!binary) is >> std::ws;  // consume whitespace.
  is >> *str;
  if (is.fail()) {
    KALDI_ERR << "ReadToken, failed to read token at file position "
              << is.tellg();
  }
  if (!isspace(is.peek())) {
    KALDI_ERR << "ReadToken, expected space after token, saw instead "
              << CharToString(static_cast<char>(is.peek()))
              << ", at file position " << is.tellg();
  }
  is.get();  // consume the space.
}

int PeekToken(std::istream& is, bool binary) {
  if (!binary) is >> std::ws;  // consume whitespace.
  bool read_bracket;
  if (static_cast<char>(is.peek()) == '<') {
    read_bracket = true;
    is.get();
  } else {
    read_bracket = false;
  }
  int ans = is.peek();
  if (read_bracket) {
    if (!is.unget()) {
      // Clear the bad bit. This code can be (and is in fact) reached, since the
      // C++ standard does not guarantee that a call to unget() must succeed.
      is.clear();
    }
  }
  return ans;
}

void ExpectToken(std::istream& is, bool binary, const char* token) {
  int pos_at_start = is.tellg();
  KALDI_ASSERT(token != NULL);
  CheckToken(token);           // make sure it's valid (can be read back)
  if (!binary) is >> std::ws;  // consume whitespace.
  std::string str;
  is >> str;
  is.get();  // consume the space.
  if (is.fail()) {
    KALDI_ERR << "Failed to read token [started at file position "
              << pos_at_start << "], expected " << token;
  }
  // The second half of the '&&' expression below is so that if we're expecting
  // "<Foo>", we will accept "Foo>" instead.  This is so that the model-reading
  // code will tolerate errors in PeekToken where is.unget() failed; search for
  // is.clear() in PeekToken() for an explanation.
  if (strcmp(str.c_str(), token) != 0 &&
      !(token[0] == '<' && strcmp(str.c_str(), token + 1) == 0)) {
    KALDI_ERR << "Expected token \"" << token << "\", got instead \"" << str
              << "\".";
  }
}

void ExpectToken(std::istream& is, bool binary, const std::string& token) {
  ExpectToken(is, binary, token.c_str());
}

}  // end namespace kaldi
