// Copyright 2009-2020 Intel Corporation
// SPDX-License-Identifier: Apache-2.0

#pragma once

#include "../sys/platform.h"
#include "../sys/ref.h"
#include "../sys/filename.h"
#include "../sys/string.h"

#include <vector>
#include <iostream>
#include <cstdio>
#include <string.h>

namespace embree
{
  /*! stores the location of a stream element in the source */
  class ParseLocation
  {
  public:
    ParseLocation () : lineNumber(-1), colNumber(-1) {}
    ParseLocation (std::shared_ptr<std::string> fileName, ssize_t lineNumber, ssize_t colNumber, ssize_t /*charNumber*/)
      : fileName(fileName), lineNumber(lineNumber), colNumber(colNumber) {}

    std::string str() const
    {
      std::string str = "unknown";
      if (fileName) str = *fileName;
      if (lineNumber >= 0) str += " line " + toString(lineNumber);
      if (lineNumber >= 0 && colNumber >= 0) str += " character " + toString(colNumber);
      return str;
    }

  private:
    std::shared_ptr<std::string> fileName;         /// name of the file (or stream) the token is from
    ssize_t lineNumber;           /// the line number the token is from
    ssize_t colNumber;            /// the character number in the current line
  };

  /*! a stream class templated over the stream elements */
  template<typename T> class Stream : public RefCount
  {
    enum { BUF_SIZE = 1024 };
    
  private:
    virtual T next() = 0;
    virtual ParseLocation location() = 0;
    __forceinline std::pair<T,ParseLocation> nextHelper() {
      ParseLocation l = location();
      T v = next();
      return std::pair<T,ParseLocation>(v,l);
    }
    __forceinline void push_back(const std::pair<T,ParseLocation>& v) {
      if (past+future == BUF_SIZE) pop_front();
      size_t end = (start+past+future++)%BUF_SIZE;
      buffer[end] = v;
    }
    __forceinline void pop_front() {
      if (past == 0) THROW_RUNTIME_ERROR("stream buffer empty");
      start = (start+1)%BUF_SIZE; past--;
    }
  public:
    Stream () : start(0), past(0), future(0), buffer(BUF_SIZE) {}
    virtual ~Stream() {}
    
  public:
    
    const ParseLocation& loc() {
      if (future == 0) push_back(nextHelper());
      return buffer[(start+past)%BUF_SIZE].second;
    }
    T get() {
      if (future == 0) push_back(nextHelper());
      T t = buffer[(start+past)%BUF_SIZE].first;
      past++; future--;
      return t;
    }
    const T& peek() {
      if (future == 0) push_back(nextHelper());
      return buffer[(start+past)%BUF_SIZE].first;
    }
    const T& unget(size_t n = 1) {
      if (past < n) THROW_RUNTIME_ERROR ("cannot unget that many items");
      past -= n; future += n;
      return peek();
    }
    void drop() {
      if (future == 0) push_back(nextHelper());
      past++; future--;
    }
  private:
    size_t start,past,future;
    std::vector<std::pair<T,ParseLocation> > buffer;
  };
  
  /*! warps an iostream stream */
  class StdStream : public Stream<int>
  {
  public:
    StdStream (std::istream& cin, const std::string& name = "std::stream")
      : cin(cin), lineNumber(1), colNumber(0), charNumber(0), name(std::shared_ptr<std::string>(new std::string(name))) {}
    ~StdStream() {}
    ParseLocation location() {
      return ParseLocation(name,lineNumber,colNumber,charNumber);
    }
    int next() {
      int c = cin.get();
      if (c == '\n') { lineNumber++; colNumber = 0; } else if (c != '\r') colNumber++;
      charNumber++;
      return c;
    }
  private:
    std::istream& cin;
    ssize_t lineNumber;           /// the line number the token is from
    ssize_t colNumber;            /// the character number in the current line
    ssize_t charNumber;           /// the character in the file
    std::shared_ptr<std::string> name;             /// name of buffer
  };

  /*! creates a stream from a file */
  class FileStream : public Stream<int>
  {
  public:

    FileStream (FILE* file, const std::string& name = "file")
      : file(file), lineNumber(1), colNumber(0), charNumber(0), name(std::shared_ptr<std::string>(new std::string(name))) {}

    FileStream (const FileName& fileName)
      : lineNumber(1), colNumber(0), charNumber(0), name(std::shared_ptr<std::string>(new std::string(fileName.str())))
    {
      file = fopen(fileName.c_str(),"r");
      if (file == nullptr) THROW_RUNTIME_ERROR("cannot open file " + fileName.str());
    }
    ~FileStream() { if (file) fclose(file); }

  public:
    ParseLocation location() {
      return ParseLocation(name,lineNumber,colNumber,charNumber);
    }

    int next() {
      int c = fgetc(file);
      if (c == '\n') { lineNumber++; colNumber = 0; } else if (c != '\r') colNumber++;
      charNumber++;
      return c;
    }

  private:
    FILE* file;
    ssize_t lineNumber;           /// the line number the token is from
    ssize_t colNumber;            /// the character number in the current line
    ssize_t charNumber;           /// the character in the file
    std::shared_ptr<std::string> name;             /// name of buffer
  };

  /*! creates a stream from a string */
  class StrStream : public Stream<int>
  {
  public:

    StrStream (const char* str)
      : str(str), lineNumber(1), colNumber(0), charNumber(0) {}

  public:
    ParseLocation location() {
      return ParseLocation(std::shared_ptr<std::string>(),lineNumber,colNumber,charNumber);
    }

    int next() {
      int c = str[charNumber];
      if (c == 0) return EOF;
      if (c == '\n') { lineNumber++; colNumber = 0; } else if (c != '\r') colNumber++;
      charNumber++;
      return c;
    }

  private:
    const char* str;
    ssize_t lineNumber;           /// the line number the token is from
    ssize_t colNumber;            /// the character number in the current line
    ssize_t charNumber;           /// the character in the file
  };

  /*! creates a character stream from a command line */
  class CommandLineStream : public Stream<int>
  {
  public:
    CommandLineStream (int argc, char** argv, const std::string& name = "command line")
      : i(0), j(0), charNumber(0), name(std::shared_ptr<std::string>(new std::string(name)))
    {
      if (argc > 0) {
	for (size_t i=0; argv[0][i] && i<1024; i++) charNumber++;
	charNumber++;
      }
      for (ssize_t k=1; k<argc; k++) args.push_back(argv[k]);
    }
    ~CommandLineStream() {}
  public:
    ParseLocation location() {
      return ParseLocation(name,0,charNumber,charNumber);
    }
    int next() {
      if (i == args.size()) return EOF;
      if (j == args[i].size()) { i++; j=0; charNumber++; return ' '; }
      charNumber++;
      return args[i][j++];
    }
  private:
    size_t i,j;
    std::vector<std::string> args;
    ssize_t charNumber;           /// the character in the file
    std::shared_ptr<std::string> name;             /// name of buffer
  };
}
