/* telnet.icc - inline functions for telnet.hh -*- C++ -*-
 * Copyright 2003 Bas Wijnen <wijnen@debian.org>
 *
 * 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 */

#include "telnet.hh"

namespace shevek
{
  telnet::option_t telnet::options[6] = {
    { BINARY,
      &telnet::will_check,
      &telnet::wont_check,
      &telnet::do_check,
      &telnet::dont_check,
      false,
      false,
      false },
    { ECHO,
      &telnet::will_check,
      &telnet::wont_check,
      &telnet::do_check,
      &telnet::dont_check,
      false,
      false,
      true },
    { SUPPRESS_GA,
      &telnet::will_check,
      &telnet::wont_check,
      &telnet::do_check,
      &telnet::dont_check,
      false,
      false,
      false },
    { STATUS,
      &telnet::l_dont,
      &telnet::nop,
      &telnet::l_wont,
      &telnet::nop,
      false,
      false,
      false },
    { TIMING_MARK,
      &telnet::l_dont,
      &telnet::nop,
      &telnet::l_wont,
      &telnet::nop,
      false,
      false,
      false },
    { EXOPL,
      &telnet::l_dont,
      &telnet::nop,
      &telnet::l_wont,
      &telnet::nop,
      false,
      false,
      false }
  };

  telnet::option_t *telnet::s_find (char opt)
  {
    startfunc;
    for (unsigned i = 0; i < sizeof (options) / sizeof (*options); ++i)
      {
	if (options[i].type == opt)
	  return &options[i];
      }
    // the type will be used in responses, so set it to the correct value
    nop_option.type = opt;
    return &nop_option;
  }

  void telnet::nop (option_t *)
  {
    startfunc;
  }

  void telnet::l_dont (option_t *opt)
  {
    startfunc;
    Glib::RefPtr <telnet> keep_object_alive = refptr_this <telnet> ();
    (void)&keep_object_alive;
    m_ignore = true;
    std::string cmd ("\377\376 ", 3);
    cmd[2] = opt->type;
    write (cmd);
    m_ignore = false;
  }

  void telnet::l_wont (option_t *opt)
  {
    startfunc;
    Glib::RefPtr <telnet> keep_object_alive = refptr_this <telnet> ();
    (void)&keep_object_alive;
    m_ignore = true;
    std::string cmd (std::string ("\377\374 ", 3) );
    cmd[2] = opt->type;
    write (cmd);
    m_ignore = false;
  }

  void telnet::l_do (option_t *opt)
  {
    startfunc;
    Glib::RefPtr <telnet> keep_object_alive = refptr_this <telnet> ();
    (void)&keep_object_alive;
    m_ignore = true;
    std::string cmd (std::string ("\377\375 ", 3) );
    cmd[2] = opt->type;
    write (cmd);
    m_ignore = false;
  }

  void telnet::l_will (option_t *opt)
  {
    startfunc;
    Glib::RefPtr <telnet> keep_object_alive = refptr_this <telnet> ();
    (void)&keep_object_alive;
    m_ignore = true;
    std::string cmd (std::string ("\377\373 ", 3) );
    cmd[2] = opt->type;
    write (cmd);
    m_ignore = false;
  }

  void telnet::do_check (option_t *opt)
  {
    startfunc;
    if (opt->here)
      return;
    Glib::RefPtr <telnet> keep_object_alive = refptr_this <telnet> ();
    (void)&keep_object_alive;
    if (opt->not_both && opt->there)
      l_wont (opt);
    else
      {
	opt->here = true;
	l_will (opt);
      }
  }

  void telnet::dont_check (option_t *opt)
  {
    startfunc;
    if (!opt->here)
      return;
    Glib::RefPtr <telnet> keep_object_alive = refptr_this <telnet> ();
    (void)&keep_object_alive;
    opt->here = false;
    l_wont (opt);
  }

  void telnet::will_check (option_t *opt)
  {
    startfunc;
    if (opt->there)
      return;
    Glib::RefPtr <telnet> keep_object_alive = refptr_this <telnet> ();
    (void)&keep_object_alive;
    if (opt->not_both && opt->here)
      l_dont (opt);
    else
      {
	opt->there = true;
	l_do (opt);
      }
  }

  void telnet::wont_check (option_t *opt)
  {
    startfunc;
    if (!opt->there)
      return;
    Glib::RefPtr <telnet> keep_object_alive = refptr_this <telnet> ();
    (void)&keep_object_alive;
    opt->there = false;
    l_dont (opt);
  }

  void telnet::l_do_sub (std::string const &data, std::string::size_type &pos)
  {
    startfunc;
  }
  
  void telnet::l_in_filter (std::string &data)
  {
    startfunc;
    Glib::RefPtr <telnet> keep_object_alive = refptr_this <telnet> ();
    (void)&keep_object_alive;
    std::string::size_type pos, oldpos = 0;
    std::string buffer = m_inbuffer + data;
    m_inbuffer.clear ();
    data.clear ();
    while (m_inbuffer.empty () )
      {
	pos = buffer.find_first_of ("\377\r", oldpos);
	if (pos == buffer.size () - 1)
	  {
	    m_inbuffer = buffer.substr (pos, 1);
	    buffer = buffer.substr (0, pos);
	    break;
	  }
	if (pos == std::string::npos)
	  break;
	data += buffer.substr (oldpos, pos - oldpos);
	option_t *opt;
	switch (buffer[pos++])
	  {
	  case '\r':
	    if (!options[BINARY_IDX].there)
	      {
		if (buffer[pos] == 0)
		  {
		    data += '\r';
		    ++pos;
		  }
		else if (buffer[pos] != '\n')
		  data += '\r';
	      }
	    break;
	  case '\xff':
	    switch (buffer[pos++])
	      {
	      case SE:
		break;
	      case NOP:
		break;
	      case MARK:
		break;
	      case BREAK:
		break;
	      case IP:
		break;
	      case AO:
		break;
	      case AYT:
		write (m_am_here);
		break;
	      case EC:
		if (!data.empty () )
		  data = data.substr (0, data.size () - 1);
		break;
	      case EL:
		oldpos = data.find_last_of ('\n');
		if (oldpos != std::string::npos)
		  data = data.substr (oldpos + 1);
		else
		  data.clear ();
		break;
	      case GA:
		break;
	      case SB:
		l_do_sub (data, pos);
		break;
	      case WILL:
		if (pos == buffer.size () )
		  {
		    m_inbuffer = buffer.substr (pos - 2, 2);
		    break;
		  }
		opt = s_find (buffer[pos]);
		(this->*opt->will) (opt);
		++pos;
		break;
	      case WONT:
		if (pos == buffer.size () )
		  {
		    m_inbuffer = buffer.substr (pos - 2, 2);
		    break;
		  }
		opt = s_find (buffer[pos]);
		(this->*opt->wont) (opt);
		++pos;
		break;
	      case DO:
		if (pos == buffer.size () )
		  {
		    m_inbuffer = buffer.substr (pos - 2, 2);
		    break;
		  }
		opt = s_find (buffer[pos]);
		(this->*opt->doo) (opt);
		++pos;
		break;
	      case DONT:
		if (pos == buffer.size () )
		  {
		    m_inbuffer = buffer.substr (pos - 2, 2);
		    break;
		  }
		opt = s_find (buffer[pos]);
		(this->*opt->dont) (opt);
		++pos;
		break;
	      case IAC:
		data += std::string ("\377\377", 2);
	      }
	  }
	oldpos = pos;
      }
    data += buffer.substr (oldpos);
    if (options[ECHO_IDX].here)
      write (data);
    return;
  }

  void telnet::l_out_filter (std::string &data)
  {
    startfunc;
    if (m_ignore)
      return;
    Glib::RefPtr <telnet> keep_object_alive = refptr_this <telnet> ();
    (void)&keep_object_alive;
    std::string::size_type pos, oldpos = 0;
    std::string result;
    while (1)
      {
	pos = data.find_first_of ("\xff\r\n", oldpos);
	if (pos == std::string::npos)
	  {
	    result += data.substr (oldpos);
	    data = result;
	    return;
	  }
	result += data.substr (oldpos, pos - oldpos);
	switch (data[pos++])
	  {
	  case '\r':
	    if (!options[BINARY_IDX].here)
	      result += std::string ("\r\x00", 2);
	    else
	      result += '\r';
	    break;
	  case '\n':
	    if (!options[BINARY_IDX].here)
	      result += std::string ("\r\n", 2);
	    else
	      result += '\n';
	    break;
	  case '\xff':
	    result += std::string ("\xff\xff", 2);
	    break;
	  default:
	    throw "bug: default reached in case";
	  }
	oldpos = pos;
      }
  }

  telnet::telnet (Glib::RefPtr <Glib::MainContext> main)
    : socket (main), m_ignore (false), m_am_here ("[Yes]\n")
  {
    startfunc;
    nop_option.will = &telnet::nop;
    nop_option.wont = &telnet::nop;
    nop_option.doo = &telnet::nop;
    nop_option.dont = &telnet::nop;
    in_filter (sigc::mem_fun (*this, &telnet::l_in_filter) );
    out_filter (sigc::mem_fun (*this, &telnet::l_out_filter) );
  }

  Glib::RefPtr <telnet> telnet::create (Glib::RefPtr <Glib::MainContext> main)
  {
    startfunc;
    return Glib::RefPtr <telnet> (new telnet (main) );
  }

  std::string &telnet::you_there ()
  {
    startfunc;
    return m_am_here;
  }
}
