// util/kaldi-io.cc

// Copyright 2009-2011  Microsoft Corporation;  Jan Silovsky
//                2016  Xiaohui Zhang

// 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 "util/kaldi-io.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#include <cstdlib>

#include "base/io-funcs.h"
#include "base/kaldi-math.h"
#include "util/kaldi-pipebuf.h"
#include "util/parse-options.h"
#include "util/text-utils.h"

#ifdef KALDI_CYGWIN_COMPAT
#include "util/kaldi-cygwin-io-inl.h"
#define MapOsPath(x) MapCygwinPath(x)
#else  // KALDI_CYGWIN_COMPAT
#define MapOsPath(x) x
#endif  // KALDI_CYGWIN_COMPAT

#if defined(_MSC_VER)
static FILE *popen(const char *command, const char *mode) {
#ifdef KALDI_CYGWIN_COMPAT
  return kaldi::CygwinCompatPopen(command, mode);
#else  // KALDI_CYGWIN_COMPAT
  return _popen(command, mode);
#endif  // KALDI_CYGWIN_COMPAT
}
#endif  // _MSC_VER

namespace kaldi {

#ifndef _MSC_VER  // on VS, we don't need this type.
// could replace basic_pipebuf<char> with stdio_filebuf<char> on some platforms.
// Would mean we could use less of our own code.
typedef basic_pipebuf<char> PipebufType;
#endif
}  // namespace kaldi

namespace kaldi {

std::string PrintableRxfilename(const std::string &rxfilename) {
  if (rxfilename == "" || rxfilename == "-") {
    return "standard input";
  } else {
    // If this call to Escape later causes compilation issues,
    // just replace it with "return rxfilename"; it's only a
    // pretty-printing issue.
    return ParseOptions::Escape(rxfilename);
  }
}

std::string PrintableWxfilename(const std::string &wxfilename) {
  if (wxfilename == "" || wxfilename == "-") {
    return "standard output";
  } else {
    // If this call to Escape later causes compilation issues,
    // just replace it with "return wxfilename"; it's only a
    // pretty-printing issue.
    return ParseOptions::Escape(wxfilename);
  }
}

OutputType ClassifyWxfilename(const std::string &filename) {
  const char *c = filename.c_str();
  size_t length = filename.length();
  char first_char = c[0],
       last_char = (length == 0 ? '\0' : c[filename.length() - 1]);

  // if 'filename' is "" or "-", return kStandardOutput.
  if (length == 0 || (length == 1 && first_char == '-')) {
    return kStandardOutput;
  } else if (first_char == '|') {
    return kPipeOutput;  // An output pipe like "|blah".
  } else if (isspace(first_char) || isspace(last_char) || last_char == '|') {
    return kNoOutput;  // Leading or trailing space: can't interpret this.
                       // Final '|' would represent an input pipe, not an
                       // output pipe.
    // } else if ((first_char == 'a' || first_char == 's') &&
    //            strchr(c, ':') != NULL &&
    //            (ClassifyWspecifier(filename, NULL, NULL, NULL) !=
    //            kNoWspecifier ||
    //             ClassifyRspecifier(filename, NULL, NULL) != kNoRspecifier)) {
    //   // e.g. ark:something or scp:something... this is almost certainly a
    //   // scripting error, so call it an error rather than treating it as a
    //   file.
    //   // In practice in modern kaldi scripts all (r,w)filenames begin with
    //   "ark"
    //   // or "scp", even though technically speaking options like "b", "t",
    //   "s" or
    //   // "cs" can appear before the ark or scp, like "b,ark".  For
    //   efficiency,
    //   // and because this code is really just a nicety to catch errors
    //   earlier
    //   // than they would otherwise be caught, we only call those extra
    //   functions
    //   // for filenames beginning with 'a' or 's'.
    //   return kNoOutput;
  } else if (isdigit(last_char)) {
    // This could be a file, but we have to see if it's an offset into a file
    // (like foo.ark:4314328), which is not allowed for writing (but is
    // allowed for reaching).  This eliminates some things which would be
    // valid UNIX filenames but are not allowed by Kaldi.  (Even if we allowed
    // such filenames for writing, we woudln't be able to correctly read them).
    const char *d = c + length - 1;
    while (isdigit(*d) && d > c) d--;
    if (*d == ':') return kNoOutput;
    // else it could still be a filename; continue to the next check.
  }

  // At this point it matched no other pattern so we assume a filename, but we
  // check for internal '|' as it's a common source of errors to have pipe
  // commands without the pipe in the right place.  Say that it can't be
  // classified.
  if (strchr(c, '|') != NULL) {
    KALDI_WARN << "Trying to classify wxfilename with pipe symbol in the"
                  " wrong place (pipe without | at the beginning?): "
               << filename;
    return kNoOutput;
  }
  return kFileOutput;  // It matched no other pattern: assume it's a filename.
}

InputType ClassifyRxfilename(const std::string &filename) {
  const char *c = filename.c_str();
  size_t length = filename.length();
  char first_char = c[0],
       last_char = (length == 0 ? '\0' : c[filename.length() - 1]);

  // if 'filename' is "" or "-", return kStandardInput.
  if (length == 0 || (length == 1 && first_char == '-')) {
    return kStandardInput;
  } else if (first_char == '|') {
    return kNoInput;  // An output pipe like "|blah": not
                      // valid for input.
  } else if (last_char == '|') {
    return kPipeInput;
  } else if (isspace(first_char) || isspace(last_char)) {
    return kNoInput;  // We don't allow leading or trailing space in a filename.
    // } else if ((first_char == 'a' || first_char == 's') &&
    //            strchr(c, ':') != NULL &&
    //           (ClassifyWspecifier(filename, NULL, NULL, NULL) !=
    //           kNoWspecifier ||
    //            ClassifyRspecifier(filename, NULL, NULL) != kNoRspecifier)) {
    //   // e.g. ark:something or scp:something... this is almost certainly a
    //   // scripting error, so call it an error rather than treating it as a
    //   file.
    //   // In practice in modern kaldi scripts all (r,w)filenames begin with
    //   "ark"
    //   // or "scp", even though technically speaking options like "b", "t",
    //   "s" or
    //   // "cs" can appear before the ark or scp, like "b,ark".  For
    //   efficiency,
    //   // and because this code is really just a nicety to catch errors
    //   earlier
    //   // than they would otherwise be caught, we only call those extra
    //   functions
    //   // for filenames beginning with 'a' or 's'.
    //   return kNoInput;
  } else if (isdigit(last_char)) {
    const char *d = c + length - 1;
    while (isdigit(*d) && d > c) d--;
    if (*d == ':')
      return kOffsetFileInput;  // Filename is like
                                // some_file:12345
    // otherwise it could still be a filename; continue to the next check.
  }

  // At this point it matched no other pattern so we assume a filename, but
  // we check for '|' as it's a common source of errors to have pipe
  // commands without the pipe in the right place.  Say that it can't be
  // classified in this case.
  if (strchr(c, '|') != NULL) {
    KALDI_WARN << "Trying to classify rxfilename with pipe symbol in the"
                  " wrong place (pipe without | at the end?): "
               << filename;
    return kNoInput;
  }
  return kFileInput;  // It matched no other pattern: assume it's a filename.
}

class OutputImplBase {
 public:
  // Open will open it as a file (no header), and return true
  // on success.  It cannot be called on an already open stream.
  virtual bool Open(const std::string &filename, bool binary) = 0;
  virtual std::ostream &Stream() = 0;
  virtual bool Close() = 0;
  virtual ~OutputImplBase() {}
};

class FileOutputImpl : public OutputImplBase {
 public:
  virtual bool Open(const std::string &filename, bool binary) {
    if (os_.is_open())
      KALDI_ERR << "FileOutputImpl::Open(), "
                << "open called on already open file.";
    filename_ = filename;
    os_.open(MapOsPath(filename_).c_str(),
             binary ? std::ios_base::out | std::ios_base::binary
                    : std::ios_base::out);
    return os_.is_open();
  }

  virtual std::ostream &Stream() {
    if (!os_.is_open())
      KALDI_ERR << "FileOutputImpl::Stream(), file is not open.";
    // I believe this error can only arise from coding error.
    return os_;
  }

  virtual bool Close() {
    if (!os_.is_open())
      KALDI_ERR << "FileOutputImpl::Close(), file is not open.";
    // I believe this error can only arise from coding error.
    os_.close();
    return !(os_.fail());
  }
  virtual ~FileOutputImpl() {
    if (os_.is_open()) {
      os_.close();
      if (os_.fail()) KALDI_ERR << "Error closing output file " << filename_;
    }
  }

 private:
  std::string filename_;
  std::ofstream os_;
};

class StandardOutputImpl : public OutputImplBase {
 public:
  StandardOutputImpl() : is_open_(false) {}

  virtual bool Open(const std::string &filename, bool binary) {
    if (is_open_)
      KALDI_ERR << "StandardOutputImpl::Open(), "
                   "open called on already open file.";
#ifdef _MSC_VER
    _setmode(_fileno(stdout), binary ? _O_BINARY : _O_TEXT);
#endif
    is_open_ = std::cout.good();
    return is_open_;
  }

  virtual std::ostream &Stream() {
    if (!is_open_)
      KALDI_ERR << "StandardOutputImpl::Stream(), object not initialized.";
    // I believe this error can only arise from coding error.
    return std::cout;
  }

  virtual bool Close() {
    if (!is_open_)
      KALDI_ERR << "StandardOutputImpl::Close(), file is not open.";
    is_open_ = false;
    std::cout << std::flush;
    return !(std::cout.fail());
  }
  virtual ~StandardOutputImpl() {
    if (is_open_) {
      std::cout << std::flush;
      if (std::cout.fail()) KALDI_ERR << "Error writing to standard output";
    }
  }

 private:
  bool is_open_;
};

class PipeOutputImpl : public OutputImplBase {
 public:
  PipeOutputImpl() : f_(NULL), os_(NULL) {}

  virtual bool Open(const std::string &wxfilename, bool binary) {
    filename_ = wxfilename;
    KALDI_ASSERT(f_ == NULL);  // Make sure closed.
    KALDI_ASSERT(wxfilename.length() != 0 && wxfilename[0] == '|');  // should
    // start with '|'
    std::string cmd_name(wxfilename, 1);
#if defined(_MSC_VER) || defined(__CYGWIN__)
    f_ = popen(cmd_name.c_str(), (binary ? "wb" : "w"));
#else
    f_ = popen(cmd_name.c_str(), "w");
#endif
    if (!f_) {  // Failure.
      KALDI_WARN << "Failed opening pipe for writing, command is: " << cmd_name
                 << ", errno is " << strerror(errno);
      return false;
    } else {
#ifndef _MSC_VER
      fb_ = new PipebufType(f_,  // Using this constructor won't make the
                                 // destructor try to close the stream when
                                 // we're done.
                            (binary ? std::ios_base::out | std::ios_base::binary
                                    : std::ios_base::out));
      KALDI_ASSERT(fb_ != NULL);  // or would be alloc error.
      os_ = new std::ostream(fb_);
#else
      os_ = new std::ofstream(f_);
#endif
      return os_->good();
    }
  }

  virtual std::ostream &Stream() {
    if (os_ == NULL)
      KALDI_ERR << "PipeOutputImpl::Stream(),"
                   " object not initialized.";
    // I believe this error can only arise from coding error.
    return *os_;
  }

  virtual bool Close() {
    if (os_ == NULL) KALDI_ERR << "PipeOutputImpl::Close(), file is not open.";
    bool ok = true;
    os_->flush();
    if (os_->fail()) ok = false;
    delete os_;
    os_ = NULL;
    int status;
#ifdef _MSC_VER
    status = _pclose(f_);
#else
    status = pclose(f_);
#endif
    if (status)
      KALDI_WARN << "Pipe " << filename_ << " had nonzero return status "
                 << status;
    f_ = NULL;
#ifndef _MSC_VER
    delete fb_;
    fb_ = NULL;
#endif
    return ok;
  }
  virtual ~PipeOutputImpl() {
    if (os_) {
      if (!Close())
        KALDI_ERR << "Error writing to pipe " << PrintableWxfilename(filename_);
    }
  }

 private:
  std::string filename_;
  FILE *f_;
#ifndef _MSC_VER
  PipebufType *fb_;
#endif
  std::ostream *os_;
};

class InputImplBase {
 public:
  // Open will open it as a file, and return true on success.
  // May be called twice only for kOffsetFileInput (otherwise,
  // if called twice, we just create a new Input object, to avoid
  // having to deal with the extra hassle of reopening with the
  // same object.
  // Note that we will to call Open with true (binary) for
  // for text-mode Kaldi files; the only actual text-mode input
  // is for non-Kaldi files.
  virtual bool Open(const std::string &filename, bool binary) = 0;
  virtual std::istream &Stream() = 0;
  virtual int32 Close() = 0;  // We only need to check failure in the case of
                              // kPipeInput.
  // on close for input streams.
  virtual InputType MyType() = 0;  // Because if it's kOffsetFileInput, we may
                                   // call Open twice
  // (has efficiency benefits).

  virtual ~InputImplBase() {}
};

class FileInputImpl : public InputImplBase {
 public:
  virtual bool Open(const std::string &filename, bool binary) {
    if (is_.is_open())
      KALDI_ERR << "FileInputImpl::Open(), "
                << "open called on already open file.";
    is_.open(
        MapOsPath(filename).c_str(),
        binary ? std::ios_base::in | std::ios_base::binary : std::ios_base::in);
    return is_.is_open();
  }

  virtual std::istream &Stream() {
    if (!is_.is_open())
      KALDI_ERR << "FileInputImpl::Stream(), file is not open.";
    // I believe this error can only arise from coding error.
    return is_;
  }

  virtual int32 Close() {
    if (!is_.is_open())
      KALDI_ERR << "FileInputImpl::Close(), file is not open.";
    // I believe this error can only arise from coding error.
    is_.close();
    // Don't check status.
    return 0;
  }

  virtual InputType MyType() { return kFileInput; }

  virtual ~FileInputImpl() {
    // Stream will automatically be closed, and we don't care about
    // whether it fails.
  }

 private:
  std::ifstream is_;
};

class StandardInputImpl : public InputImplBase {
 public:
  StandardInputImpl() : is_open_(false) {}

  virtual bool Open(const std::string &filename, bool binary) {
    if (is_open_)
      KALDI_ERR << "StandardInputImpl::Open(), "
                   "open called on already open file.";
    is_open_ = true;
#ifdef _MSC_VER
    _setmode(_fileno(stdin), binary ? _O_BINARY : _O_TEXT);
#endif
    return true;  // Don't check good() because would be false if
    // eof, which may be valid input.
  }

  virtual std::istream &Stream() {
    if (!is_open_)
      KALDI_ERR << "StandardInputImpl::Stream(), object not initialized.";
    // I believe this error can only arise from coding error.
    return std::cin;
  }

  virtual InputType MyType() { return kStandardInput; }

  virtual int32 Close() {
    if (!is_open_) KALDI_ERR << "StandardInputImpl::Close(), file is not open.";
    is_open_ = false;
    return 0;
  }
  virtual ~StandardInputImpl() {}

 private:
  bool is_open_;
};

class PipeInputImpl : public InputImplBase {
 public:
  PipeInputImpl() : f_(NULL), is_(NULL) {}

  virtual bool Open(const std::string &rxfilename, bool binary) {
    filename_ = rxfilename;
    KALDI_ASSERT(f_ == NULL);  // Make sure closed.
    KALDI_ASSERT(rxfilename.length() != 0 &&
                 rxfilename[rxfilename.length() - 1] ==
                     '|');  // should end with '|'
    std::string cmd_name(rxfilename, 0, rxfilename.length() - 1);
#if defined(_MSC_VER) || defined(__CYGWIN__)
    f_ = popen(cmd_name.c_str(), (binary ? "rb" : "r"));
#else
    f_ = popen(cmd_name.c_str(), "r");
#endif

    if (!f_) {  // Failure.
      KALDI_WARN << "Failed opening pipe for reading, command is: " << cmd_name
                 << ", errno is " << strerror(errno);
      return false;
    } else {
#ifndef _MSC_VER
      fb_ = new PipebufType(f_,  // Using this constructor won't lead the
                                 // destructor to close the stream.
                            (binary ? std::ios_base::in | std::ios_base::binary
                                    : std::ios_base::in));
      KALDI_ASSERT(fb_ != NULL);  // or would be alloc error.
      is_ = new std::istream(fb_);
#else
      is_ = new std::ifstream(f_);
#endif
      if (is_->fail() || is_->bad()) return false;
      if (is_->eof()) {
        KALDI_WARN << "Pipe opened with command "
                   << PrintableRxfilename(rxfilename) << " is empty.";
        // don't return false: empty may be valid.
      }
      return true;
    }
  }

  virtual std::istream &Stream() {
    if (is_ == NULL)
      KALDI_ERR << "PipeInputImpl::Stream(), object not initialized.";
    // I believe this error can only arise from coding error.
    return *is_;
  }

  virtual int32 Close() {
    if (is_ == NULL) KALDI_ERR << "PipeInputImpl::Close(), file is not open.";
    delete is_;
    is_ = NULL;
    int32 status;
#ifdef _MSC_VER
    status = _pclose(f_);
#else
    status = pclose(f_);
#endif
    if (status)
      KALDI_WARN << "Pipe " << filename_ << " had nonzero return status "
                 << status;
    f_ = NULL;
#ifndef _MSC_VER
    delete fb_;
    fb_ = NULL;
#endif
    return status;
  }
  virtual ~PipeInputImpl() {
    if (is_) Close();
  }
  virtual InputType MyType() { return kPipeInput; }

 private:
  std::string filename_;
  FILE *f_;
#ifndef _MSC_VER
  PipebufType *fb_;
#endif
  std::istream *is_;
};

/*
#else

// Just have an empty implementation of the pipe input that crashes if
// called.
class PipeInputImpl: public InputImplBase {
 public:
  PipeInputImpl() { KALDI_ASSERT(0 && "Pipe input not yet supported on this
  platform."); }
  virtual bool Open(const std::string, bool) { return 0; }
  virtual std::istream &Stream() const { return NULL; }
  virtual void Close() {}
  virtual InputType MyType() { return kPipeInput; }
};

#endif
*/

class OffsetFileInputImpl : public InputImplBase {
  // This class is a bit more complicated than the

 public:
  // splits a filename like /my/file:123 into /my/file and the
  // number 123.  Crashes if not this format.
  static void SplitFilename(const std::string &rxfilename,
                            std::string *filename, size_t *offset) {
    size_t pos = rxfilename.find_last_of(':');
    KALDI_ASSERT(pos != std::string::npos);  // would indicate error in calling
    // code, as the filename is supposed to be of the correct form at this
    // point.
    *filename = std::string(rxfilename, 0, pos);
    std::string number(rxfilename, pos + 1);
    bool ans = ConvertStringToInteger(number, offset);
    if (!ans)
      KALDI_ERR << "Cannot get offset from filename " << rxfilename
                << " (possibly you compiled in 32-bit and have a >32-bit"
                << " byte offset into a file; you'll have to compile 64-bit.";
  }

  bool Seek(size_t offset) {
    size_t cur_pos = is_.tellg();
    if (cur_pos == offset) {
      return true;
    } else if (cur_pos < offset && cur_pos + 100 > offset) {
      // We're close enough that it may be faster to just
      // read that data, rather than seek.
      for (size_t i = cur_pos; i < offset; i++) is_.get();
      return (is_.tellg() == std::streampos(offset));
    }
    // Try to actually seek.
    is_.seekg(offset, std::ios_base::beg);
    if (is_.fail()) {  // failbit or badbit is set [error happened]
      is_.close();
      return false;  // failure.
    } else {
      is_.clear();  // Clear any failure bits (e.g. eof).
      return true;  // success.
    }
  }

  // This Open routine is unusual in that it is designed to work even
  // if it was already open.  This for efficiency when seeking multiple
  // times.
  virtual bool Open(const std::string &rxfilename, bool binary) {
    if (is_.is_open()) {
      // We are opening when we have an already-open file.
      // We may have to seek within this file, or else close it and
      // open a different one.
      std::string tmp_filename;
      size_t offset;
      SplitFilename(rxfilename, &tmp_filename, &offset);
      if (tmp_filename == filename_ && binary == binary_) {  // Just seek
        is_.clear();  // clear fail bit, etc.
        return Seek(offset);
      } else {
        is_.close();  // don't bother checking error status of is_.
        filename_ = tmp_filename;
        is_.open(MapOsPath(filename_).c_str(),
                 binary ? std::ios_base::in | std::ios_base::binary
                        : std::ios_base::in);
        if (!is_.is_open())
          return false;
        else
          return Seek(offset);
      }
    } else {
      size_t offset;
      SplitFilename(rxfilename, &filename_, &offset);
      binary_ = binary;
      is_.open(MapOsPath(filename_).c_str(),
               binary ? std::ios_base::in | std::ios_base::binary
                      : std::ios_base::in);
      if (!is_.is_open())
        return false;
      else
        return Seek(offset);
    }
  }

  virtual std::istream &Stream() {
    if (!is_.is_open())
      KALDI_ERR << "FileInputImpl::Stream(), file is not open.";
    // I believe this error can only arise from coding error.
    return is_;
  }

  virtual int32 Close() {
    if (!is_.is_open())
      KALDI_ERR << "FileInputImpl::Close(), file is not open.";
    // I believe this error can only arise from coding error.
    is_.close();
    // Don't check status.
    return 0;
  }

  virtual InputType MyType() { return kOffsetFileInput; }

  virtual ~OffsetFileInputImpl() {
    // Stream will automatically be closed, and we don't care about
    // whether it fails.
  }

 private:
  std::string filename_;  // the actual filename
  bool binary_;           // true if was opened in binary mode.
  std::ifstream is_;
};

Output::Output(const std::string &wxfilename, bool binary, bool write_header)
    : impl_(NULL) {
  if (!Open(wxfilename, binary, write_header)) {
    if (impl_) {
      delete impl_;
      impl_ = NULL;
    }
    KALDI_ERR << "Error opening output stream "
              << PrintableWxfilename(wxfilename);
  }
}

bool Output::Close() {
  if (!impl_) {
    return false;  // error to call Close if not open.
  } else {
    bool ans = impl_->Close();
    delete impl_;
    impl_ = NULL;
    return ans;
  }
}

Output::~Output() {
  if (impl_) {
    bool ok = impl_->Close();
    delete impl_;
    impl_ = NULL;
    if (!ok)
      KALDI_ERR << "Error closing output file "
                << PrintableWxfilename(filename_)
                << (ClassifyWxfilename(filename_) == kFileOutput
                        ? " (disk full?)"
                        : "");
  }
}

std::ostream &Output::Stream() {  // will throw if not open; else returns
  // stream.
  if (!impl_) KALDI_ERR << "Output::Stream() called but not open.";
  return impl_->Stream();
}

bool Output::Open(const std::string &wxfn, bool binary, bool header) {
  if (IsOpen()) {
    if (!Close()) {  // Throw here rather than return status, as it's an error
      // about something else: if the user wanted to avoid the exception he/she
      // could have called Close().
      KALDI_ERR << "Output::Open(), failed to close output stream: "
                << PrintableWxfilename(filename_);
    }
  }

  filename_ = wxfn;

  OutputType type = ClassifyWxfilename(wxfn);
  KALDI_ASSERT(impl_ == NULL);

  if (type == kFileOutput) {
    impl_ = new FileOutputImpl();
  } else if (type == kStandardOutput) {
    impl_ = new StandardOutputImpl();
  } else if (type == kPipeOutput) {
    impl_ = new PipeOutputImpl();
  } else {  // type == kNoOutput
    KALDI_WARN << "Invalid output filename format "
               << PrintableWxfilename(wxfn);
    return false;
  }
  if (!impl_->Open(wxfn, binary)) {
    delete impl_;
    impl_ = NULL;
    return false;  // failed to open.
  } else {         // successfully opened it.
    if (header) {
      InitKaldiOutputStream(impl_->Stream(), binary);
      bool ok = impl_->Stream().good();  // still OK?
      if (!ok) {
        delete impl_;
        impl_ = NULL;
        return false;
      }
      return true;
    } else {
      return true;
    }
  }
}

Input::Input(const std::string &rxfilename, bool *binary) : impl_(NULL) {
  if (!Open(rxfilename, binary)) {
    KALDI_ERR << "Error opening input stream "
              << PrintableRxfilename(rxfilename);
  }
}

int32 Input::Close() {
  if (impl_) {
    int32 ans = impl_->Close();
    delete impl_;
    impl_ = NULL;
    return ans;
  } else {
    return 0;
  }
}

bool Input::OpenInternal(const std::string &rxfilename, bool file_binary,
                         bool *contents_binary) {
  InputType type = ClassifyRxfilename(rxfilename);
  if (IsOpen()) {
    // May have to close the stream first.
    if (type == kOffsetFileInput && impl_->MyType() == kOffsetFileInput) {
      // We want to use the same object to Open... this is in case
      // the files are the same, so we can just seek.
      if (!impl_->Open(rxfilename, file_binary)) {  // true is binary mode--
        // always open in binary.
        delete impl_;
        impl_ = NULL;
        return false;
      }
      // read the binary header, if requested.
      if (contents_binary != NULL)
        return InitKaldiInputStream(impl_->Stream(), contents_binary);
      else
        return true;
    } else {
      Close();
      // and fall through to code below which actually opens the file.
    }
  }
  if (type == kFileInput) {
    impl_ = new FileInputImpl();
  } else if (type == kStandardInput) {
    impl_ = new StandardInputImpl();
  } else if (type == kPipeInput) {
    impl_ = new PipeInputImpl();
  } else if (type == kOffsetFileInput) {
    impl_ = new OffsetFileInputImpl();
  } else {  // type == kNoInput
    KALDI_WARN << "Invalid input filename format "
               << PrintableRxfilename(rxfilename);
    return false;
  }
  if (!impl_->Open(rxfilename, file_binary)) {  // true is binary mode--
    // always read in binary.
    delete impl_;
    impl_ = NULL;
    return false;
  }
  if (contents_binary != NULL)
    return InitKaldiInputStream(impl_->Stream(), contents_binary);
  else
    return true;
}

Input::~Input() {
  if (impl_) Close();
}

std::istream &Input::Stream() {
  if (!IsOpen()) KALDI_ERR << "Input::Stream(), not open.";
  return impl_->Stream();
}

// template <> void ReadKaldiObject(const std::string &filename,
//                                  Matrix<float> *m) {
//   if (!filename.empty() && filename[filename.size() - 1] == ']') {
//     // This filename seems to have a 'range'... like foo.ark:4312423[20:30].
//     // (the bit in square brackets is the range).
//     std::string rxfilename, range;
//     if (!ExtractRangeSpecifier(filename, &rxfilename, &range)) {
//       KALDI_ERR << "Could not make sense of possible range specifier in
//       filename "
//                 << "while reading matrix: " << filename;
//     }
//     Matrix<float> temp;
//     bool binary_in;
//     Input ki(rxfilename, &binary_in);
//     temp.Read(ki.Stream(), binary_in);
//     if (!ExtractObjectRange(temp, range, m)) {
//       KALDI_ERR << "Error extracting range of object: " << filename;
//     }
//   } else {
//     // The normal case, there is no range.
//     bool binary_in;
//     Input ki(filename, &binary_in);
//     m->Read(ki.Stream(), binary_in);
//   }
// }
//
// template <> void ReadKaldiObject(const std::string &filename,
//                                  Matrix<double> *m) {
//   if (!filename.empty() && filename[filename.size() - 1] == ']') {
//     // This filename seems to have a 'range'... like foo.ark:4312423[20:30].
//     // (the bit in square brackets is the range).
//     std::string rxfilename, range;
//     if (!ExtractRangeSpecifier(filename, &rxfilename, &range)) {
//       KALDI_ERR << "Could not make sense of possible range specifier in
//       filename "
//                 << "while reading matrix: " << filename;
//     }
//     Matrix<double> temp;
//     bool binary_in;
//     Input ki(rxfilename, &binary_in);
//     temp.Read(ki.Stream(), binary_in);
//     if (!ExtractObjectRange(temp, range, m)) {
//       KALDI_ERR << "Error extracting range of object: " << filename;
//     }
//   } else {
//     // The normal case, there is no range.
//     bool binary_in;
//     Input ki(filename, &binary_in);
//     m->Read(ki.Stream(), binary_in);
//   }
// }

}  // end namespace kaldi
