/* 
 * Copyright (c) 2007, 2014, Oracle and/or its affiliates. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2 of the
 * License.
 * 
 * This program 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 General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301  USA
 */

#ifndef _WIN32
#include <stdarg.h>
#endif

#include "grt_manager.h"
#include "grt_shell.h"
#include "base/file_functions.h"


using namespace grt;
using namespace bec;


#define SNIPPETS_FILENAME "shell_snippets.txt"
#define HISTORY_FILENAME "shell_history.txt"
#define BOOKMARKS_FILENAME "shell_bookmarks.txt"


ShellBE::ShellBE(GRTManager *grtm, const GRTDispatcher::Ref dispatcher)
  : _grtm(grtm), _dispatcher(dispatcher)
{
  _grt= grtm->get_grt();

  _shell= 0;

  _save_history_size= 0;
  _skip_history = 0;

  _history_ptr= _history.begin();
}


ShellBE::~ShellBE()
{
}


void ShellBE::set_save_directory(const std::string &path)
{
  _savedata_dir= path;
}


grt::ValueRef ShellBE::get_shell_variable(const std::string &varname)
{
  return _shell->get_global_var(varname);
}


void ShellBE::set_saves_history(int size)
{
  _save_history_size= size;
  if (size <= 0)
  {
    _history.clear();
    _history_ptr= _history.end();
  }
  else
  {
    while ((int)_history.size() > size)
      _history.pop_back();
    _history_ptr= _history.end();
  }
}


void ShellBE::run_script_file(const std::string &path)
{
  grt::ModuleLoader *loader= _grt->get_module_loader_for_file(path);
  if (!loader)
    throw std::runtime_error("Unsupported script file "+path);

  if (!loader->run_script_file(path))
    throw std::runtime_error("An error occurred while executing the script "+path);
}



bool ShellBE::run_script(const std::string &script, const std::string &language)
{
  grt::ModuleLoader *loader= _grt->get_module_loader(language);
  if (loader)
    return loader->run_script(script);
  throw std::runtime_error("Language "+language+" is not supported or enabled");
}


void ShellBE::process_line_async(const std::string &line)
{
  GRTShellTask::Ref task= GRTShellTask::create_task("User shell command", _dispatcher, line);

  task->signal_message().connect(boost::bind(&ShellBE::handle_msg, this, _1));
  task->set_handle_messages_from_thread();

  task->signal_finished().connect(boost::bind(&ShellBE::shell_finished_cb, this, _1, _2, line));
  
  _dispatcher->execute_now(task);
}


void ShellBE::shell_finished_cb(ShellCommand result, const std::string &prompt, const std::string &line)
{
  if (result == ShellCommandExit)
  {
    _grtm->terminate();
    _current_statement.clear();
  }
  else if (result == ShellCommandUnknown)
  { 
    if (_current_statement.empty())
      _current_statement= line;
    else
      _current_statement+= "\n"+line;
  }
  else if (result == ShellCommandStatement)
  {
    if (_current_statement.empty())
      _current_statement= line;
    else
      _current_statement+= "\n"+line;

    if (_save_history_size > 0 && _current_statement != "\n" && _current_statement != "")
      save_history_line(_current_statement);

    _current_statement.clear();    
  }
  else
  {
    if (_current_statement.empty())
      _current_statement= line;
    else
      _current_statement+= "\n"+line;

    if (_save_history_size > 0 && _current_statement != "\n" && _current_statement != "")
      save_history_line(_current_statement);

    _current_statement.clear();
  }

  if (_ready_slot)
    _ready_slot(prompt);
}



bool ShellBE::setup(const std::string &lang)
{
  if (!_grt->init_shell(lang))
    return false;

  _shell= _grt->get_shell();
  _grt->get_shell()->set_disable_quit(true);

  _shell->print_welcome();

  start();

  return true;
}


void ShellBE::start()
{
  _skip_history = 1;
  // trigger a command that will in turn make the GUI receive a ready
  // callback with the prompt that should be shown
  process_line_async("print(\"Ready.\\n\")");
}


void ShellBE::save_history_line(const std::string &line)
{
  if (line.empty()) return;
  if (_skip_history > 0)
  {
    --_skip_history;
    return;
  }
  
  //remove empty cmd if it is on top of the stack
  if (!_history.empty() && (*_history.begin()).empty())
    _history.pop_front();

  if (line[line.size()-1] == '\n')
    _history.push_front(line.substr(0, line.size()-1));
  else
    _history.push_front(line);
  if ((int)_history.size() > _save_history_size)
    _history.pop_back();
  _history_ptr= _history.begin();
}


void ShellBE::clear_history()
{
  _history.clear();
  _history_ptr= _history.end();
}



bool ShellBE::previous_history_line(const std::string &current_line,
                                    std::string &line)
{
  if (_history_ptr == _history.end())
    return false;

  bool saved_current = !(_history_ptr == _history.begin());
  
  // save the current line to the history
  if (!current_line.empty() && !saved_current)
  {
    saved_current= true;
    save_history_line(current_line);
  }
  std::list<std::string>::const_iterator tmp= _history_ptr;
  ++tmp;
  if (tmp == _history.end())
    return false;

  if (saved_current)
    line= *++_history_ptr;
  else
    line= *_history_ptr++;

  return true;
}


bool ShellBE::next_history_line(std::string &line)
{
  if (_history_ptr != _history.begin())
  {
    --_history_ptr;
    line= *_history_ptr;
    if (_history_ptr == _history.begin())
    {
      _history.pop_front();
      _history_ptr= _history.begin();
    }
    return true;
  }
  return false;
}

void ShellBE::reset_history_position()
{
  _history_ptr= _history.begin();
}


void ShellBE::set_ready_handler(const boost::function<void (const std::string&)> &slot)
{
  _ready_slot= slot;
}


void ShellBE::set_output_handler(const boost::function<void (const std::string&)> &slot)
{
  _output_slot= slot;
  if (_output_slot)
    flush_shell_output(); // Write out any pending text we might have.
}



void ShellBE::handle_msg(const Message &msg)
{
  // the default msg handler outputs everything to shell
  switch (msg.type)
  {
  case grt::ErrorMsg:
    write_line("ERROR: "+msg.text+"\n");
    break;
  case grt::WarningMsg:
    write_line("WARNING: "+msg.text+"\n");
    break;
  case grt::InfoMsg:
    write_line("INFO: "+msg.text+"\n");
    break;
  case grt::ProgressMsg:
    write_line("Progress: "+msg.text+"\n");
    break;
  case grt::OutputMsg:
    write(msg.text);
    break;
  default:
    write_line("Message: "+msg.text+"\n");
    break;
  }
}


void ShellBE::writef(const char *fmt, ...)
{
  va_list ap;
  char *tmp;
  std::string line;

  va_start(ap, fmt);
  tmp= g_strdup_vprintf(fmt, ap);
  line= tmp;
  g_free(tmp);
  va_end(ap);

  // Cache the text if there is no output slot set yet (usually at app start).
  // Flush this queue when we have an output slot and are running in the main thread currently.
  if (_grtm->is_threaded())
  {
    {
      base::MutexLock lock(_text_queue_mutex);
      _text_queue.push_back(line);
    }

    // if we're in the main thread, flush the message queue
    if (_grtm->in_main_thread() && _output_slot)
      flush_shell_output();
  }
  else 
  {
    // If not threaded print directly (given we have an output slot).
    // The text queue is flushed when an output slot is set.
    if(_output_slot)
      _output_slot(line);
    else
    {
      base::MutexLock lock(_text_queue_mutex);
      _text_queue.push_back(line);
    }
  }
}


void ShellBE::write_line(const std::string &line)
{
  writef("%s\n", line.c_str());
}


void ShellBE::write(const std::string &text)
{
  writef("%s", text.c_str());
}


void ShellBE::flush_shell_output()
{
  if (!_output_slot)
    return;

  std::string line;

  while(true)
  {
    {
      base::MutexLock lock(_text_queue_mutex);
      if (_text_queue.empty())
        break;
      line = _text_queue.front();
      _text_queue.pop_front();
    }
    _output_slot(line);
  }
}


std::vector<std::string> ShellBE::complete_line(const std::string &line, std::string &nprefix)
{
  return _shell->complete_line(line, nprefix);
}


std::string ShellBE::get_snippet_data()
{
  std::string path= make_path(_savedata_dir, SNIPPETS_FILENAME);
  gchar *contents;
  gsize length;
  
  if (g_file_get_contents(path.c_str(), &contents, &length, NULL))
  {
    std::string text= std::string(contents, contents+length);
    g_free(contents);
    return text;
  }
  return "";
}


void ShellBE::set_snippet_data(const std::string &data)
{
  std::string path= make_path(_savedata_dir, SNIPPETS_FILENAME);

  // Make sure path exists, if not create it with privileges 755
  g_mkdir_with_parents(_savedata_dir.c_str(), 0755);

  if (!g_file_set_contents(path.c_str(), data.c_str(), (gssize)data.size(), NULL))
  {
    throw std::runtime_error("Could not save file "+path);
  }
}


std::vector<std::string> ShellBE::get_grt_tree_bookmarks()
{
  return _grt_tree_bookmarks;
}


void ShellBE::add_grt_tree_bookmark(const std::string &path)
{
  if (std::find(_grt_tree_bookmarks.begin(), _grt_tree_bookmarks.end(), path) == _grt_tree_bookmarks.end())
    _grt_tree_bookmarks.push_back(path);
}


void ShellBE::delete_grt_tree_bookmark(const std::string &path)
{
  std::vector<std::string>::iterator iter= std::find(_grt_tree_bookmarks.begin(),
                                                     _grt_tree_bookmarks.end(),
                                                     path);
  if (iter != _grt_tree_bookmarks.end())
    _grt_tree_bookmarks.erase(iter);
}


void ShellBE::store_state()
{
  // Make sure path exists, if not create it with privileges 744
  g_mkdir_with_parents(_savedata_dir.c_str(), 0700);

  {
    std::string path= make_path(_savedata_dir, HISTORY_FILENAME);
    
    FILE *f= base_fopen(path.c_str(), "w+");
    if (!f)
      throw std::runtime_error("Could not save file "+path);
    
    for (std::list<std::string>::const_iterator i= _history.begin();
         i != _history.end(); ++i)
    {
      char **lines= g_strsplit(i->c_str(), "\n", 0);
      for (int j= 0; lines[j]; j++)
        fprintf(f, " %s\n", lines[j]);
      g_strfreev(lines);
      fprintf(f, "\n");
    }
    fclose(f);
  }
  
  {
    std::string path= make_path(_savedata_dir, BOOKMARKS_FILENAME);
    
    FILE *f= base_fopen(path.c_str(), "w+");
    if (!f)
      throw std::runtime_error("Could not save file "+path);
    
    for (std::vector<std::string>::const_iterator i= _grt_tree_bookmarks.begin();
         i != _grt_tree_bookmarks.end(); ++i)
    {
      fprintf(f, "%s\n", i->c_str());
    }
    fclose(f);
  }
}


void ShellBE::restore_state()
{
  {
    char line[1024];
    std::string path= make_path(_savedata_dir, HISTORY_FILENAME);
    std::string command;
    FILE *f= base_fopen(path.c_str(), "r");
    if (f)
    {
      _history.clear();
      
      while (!feof(f) && fgets(line, sizeof(line), f))
      {
        if (*line==' ')
          command+= line+1;
        else
        {
          while (!command.empty() && (command[command.size()-1] == ' ' || command[command.size()-1] == '\n'))
            command= command.substr(0, command.size()-1);

          if (!command.empty())
            _history.push_back(command);
          command= "";
        }
      }
      fclose(f);
    }
    _history_ptr= _history.begin();
  }
  
  {
    char line[1024];
    std::string path= make_path(_savedata_dir, BOOKMARKS_FILENAME);
    FILE *f= base_fopen(path.c_str(), "r");
    if (f)
    {
      bool cleared= false;
      
      while (!feof(f) && fgets(line, sizeof(line), f))
      {
        char *p= strchr(line, '\n');
        if (p) *p= 0;
        if (strlen(line) > 0 && line[0] == '/')
        {
          if (!cleared)
            _grt_tree_bookmarks.clear();
          cleared= true;
          _grt_tree_bookmarks.push_back(g_strstrip(line));
        }
      }
      fclose(f);
    }
    else
    {
      _grt_tree_bookmarks.push_back("/");
    }
  }
}

