/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2017-2020 Telegram Systems LLP
*/
#include "IntCtx.h"
namespace fift {
td::StringBuilder& operator<<(td::StringBuilder& os, const IntCtx& ctx) {
  if (ctx.include_depth) {
    return os << ctx.filename << ":" << ctx.line_no << ": ";
  } else {
    return os;
  }
}
std::ostream& operator<<(std::ostream& os, const IntCtx& ctx) {
  return os << (PSLICE() << ctx).c_str();
}
void CharClassifier::import_from_string(td::Slice str, int space_cls) {
  set_char_class(' ', space_cls);
  set_char_class('\t', space_cls);
  int cls = 3;
  for (char c : str) {
    if (c == ' ') {
      cls--;
    } else {
      set_char_class(c, cls);
    }
  }
}
void CharClassifier::import_from_string(std::string str, int space_cls) {
  import_from_string(td::Slice{str}, space_cls);
}
void CharClassifier::import_from_string(const char* str, int space_cls) {
  import_from_string(td::Slice{str}, space_cls);
}
CharClassifier CharClassifier::from_string(td::Slice str, int space_cls) {
  return CharClassifier{str, space_cls};
}
void CharClassifier::set_char_class(int c, int cl) {
  c &= 0xff;
  cl &= 3;
  int offs = (c & 3) * 2;
  int mask = (3 << offs);
  cl <<= offs;
  unsigned char* p = data_ + (c >> 2);
  *p = static_cast((*p & ~mask) | cl);
}
IntCtx::Savepoint::Savepoint(IntCtx& _ctx, std::string new_filename, std::string new_current_dir,
                             std::unique_ptr new_input_stream)
    : ctx(_ctx)
    , old_line_no(_ctx.line_no)
    , old_need_line(_ctx.need_line)
    , old_filename(_ctx.filename)
    , old_current_dir(_ctx.currentd_dir)
    , old_input_stream(_ctx.input_stream)
    , old_input_stream_holder(std::move(_ctx.input_stream_holder))
    , old_curline(_ctx.str)
    , old_curpos(_ctx.input_ptr - _ctx.str.c_str())
    , old_word(_ctx.word) {
  ctx.line_no = 0;
  ctx.filename = new_filename;
  ctx.currentd_dir = new_current_dir;
  ctx.input_stream = new_input_stream.get();
  ctx.input_stream_holder = std::move(new_input_stream);
  ctx.str = "";
  ctx.input_ptr = 0;
  ++(ctx.include_depth);
}
bool IntCtx::Savepoint::restore(IntCtx& _ctx) {
  if (restored || &ctx != &_ctx) {
    return false;
  }
  ctx.line_no = old_line_no;
  ctx.need_line = old_need_line;
  ctx.filename = old_filename;
  ctx.currentd_dir = old_current_dir;
  ctx.input_stream = old_input_stream;
  ctx.input_stream_holder = std::move(old_input_stream_holder);
  ctx.str = old_curline;
  ctx.input_ptr = ctx.str.c_str() + old_curpos;
  ctx.word = old_word;
  --(ctx.include_depth);
  return restored = true;
}
bool IntCtx::enter_ctx(std::string new_filename, std::string new_current_dir,
                       std::unique_ptr new_input_stream) {
  if (!new_input_stream) {
    return false;
  }
  ctx_save_stack.emplace_back(*this, std::move(new_filename), std::move(new_current_dir), std::move(new_input_stream));
  return true;
}
bool IntCtx::leave_ctx() {
  if (ctx_save_stack.empty()) {
    return false;
  }
  bool ok = ctx_save_stack.back().restore(*this);
  ctx_save_stack.pop_back();
  return ok;
}
bool IntCtx::top_ctx() {
  while (!ctx_save_stack.empty()) {
    if (!leave_ctx()) {
      return false;
    }
  }
  return true;
}
bool IntCtx::load_next_line() {
  if (!std::getline(*input_stream, str)) {
    return false;
  }
  need_line = false;
  if (!str.empty() && str.back() == '\r') {
    str.pop_back();
  }
  set_input(str);
  return true;
}
bool IntCtx::is_sb() const {
  return !eof() && line_no == 1 && *input_ptr == '#' && input_ptr[1] == '!';
}
td::Slice IntCtx::scan_word_to(char delim, bool err_endl) {
  load_next_line_ifreq();
  auto ptr = input_ptr;
  while (*ptr && *ptr != delim) {
    ptr++;
  }
  if (*ptr) {
    std::swap(ptr, input_ptr);
    return td::Slice{ptr, input_ptr++};
  } else if (err_endl && delim) {
    throw IntError{std::string{"end delimiter `"} + delim + "` not found"};
  } else {
    need_line = true;
    std::swap(ptr, input_ptr);
    return td::Slice{ptr, input_ptr};
  }
}
td::Slice IntCtx::scan_word() {
  skipspc(true);
  auto ptr = input_ptr;
  while (*ptr && *ptr != ' ' && *ptr != '\t' && *ptr != '\r') {
    ptr++;
  }
  auto ptr2 = ptr;
  std::swap(ptr, input_ptr);
  skipspc();
  return td::Slice{ptr, ptr2};
}
td::Slice IntCtx::scan_word_ext(const CharClassifier& classifier) {
  skipspc(true);
  auto ptr = input_ptr;
  while (*ptr && *ptr != '\r' && *ptr != '\n') {
    int c = classifier.classify(*ptr);
    if ((c & 1) && ptr != input_ptr) {
      break;
    }
    ptr++;
    if (c & 2) {
      break;
    }
  }
  std::swap(ptr, input_ptr);
  return td::Slice{ptr, input_ptr};
}
void IntCtx::skipspc(bool skip_eol) {
  do {
    while (*input_ptr == ' ' || *input_ptr == '\t' || *input_ptr == '\r') {
      ++input_ptr;
    }
    if (!skip_eol || *input_ptr) {
      break;
    }
  } while (load_next_line());
}
void IntCtx::check_compile() const {
  if (state <= 0) {
    throw IntError{"compilation mode only"};
  }
}
void IntCtx::check_execute() const {
  if (state != 0) {
    throw IntError{"interpret mode only"};
  }
}
void IntCtx::check_not_int_exec() const {
  if (state < 0) {
    throw IntError{"not allowed in internal interpret mode"};
  }
}
void IntCtx::check_int_exec() const {
  if (state >= 0) {
    throw IntError{"internal interpret mode only"};
  }
}
bool IntCtx::print_error_backtrace(std::ostream& os) const {
  if (exc_cont.is_null() && exc_next.is_null()) {
    os << "(no backtrace)\n";
    return false;
  }
  if (exc_cont.not_null()) {
    os << "top: ";
    exc_cont->dump(os, *this);
  }
  return print_backtrace(os, exc_next);
}
bool IntCtx::print_backtrace(std::ostream& os, Ref cont) const {
  for (int i = 1; cont.not_null() && i <= 16; i++) {
    os << "level " << i << ": ";
    cont->dump(os, *this);
    cont = cont->up();
  }
  if (cont.not_null()) {
    os << "... more levels ...\n";
  }
  return true;
}
Ref IntCtx::throw_exception(td::Status err, Ref cur) {
  exc_cont = std::move(cur);
  exc_next = std::move(next);
  error = std::move(err);
  next.clear();
  auto cont = std::move(exc_handler);
  if (cont.is_null()) {
    return {};  // no Fift exception handler set
  } else if (cont.is_unique()) {
    return cont.unique_write().handle_modify(*this);
  } else {
    return cont->handle_tail(*this);
  }
}
void IntCtx::clear_error() {
  error = td::Status::OK();
  exit_code = 0;
}
td::Result IntCtx::get_result() {
  if (error.is_error()) {
    return error.move_as_error();
  } else {
    return exit_code;
  }
}
td::Status IntCtx::add_error_loc(td::Status err) const {
  if (err.is_error()) {
    std::ostringstream os;
    if (include_depth && line_no) {
      os << filename << ":" << line_no << ":\t";
    }
    if (!word.empty()) {
      os << word << ":";
    }
    return err.move_as_error_prefix(os.str());
  } else {
    return err;
  }
}
td::Result IntCtx::run(Ref cont) {
  clear_error();
  while (cont.not_null()) {
    try {
      if (cont.is_unique()) {
        cont = cont.unique_write().run_modify(*this);
      } else {
        cont = cont->run_tail(*this);
      }
    } catch (IntError& err) {
      cont = throw_exception(td::Status::Error(err.msg), std::move(cont));
    } catch (vm::VmError& err) {
      cont = throw_exception(err.as_status(), std::move(cont));
    } catch (vm::VmVirtError& err) {
      cont = throw_exception(err.as_status(), std::move(cont));
    } catch (vm::CellBuilder::CellWriteError&) {
      cont = throw_exception(td::Status::Error("Cell builder write error"), std::move(cont));
    } catch (vm::VmFatal&) {
      cont = throw_exception(td::Status::Error("fatal vm error"), std::move(cont));
    }
    if (cont.is_null()) {
      cont = std::move(next);
    }
  }
  return get_result();
}
}  // namespace fift