text: #include "irc/protocol.hxx"
  
  
  using namespace tikirc;
  
  
  std::string message::prefix::nickname() const
  {
      std::size_t endpos = _value.find('!');
      if (endpos == std::string::npos) {
          endpos = _value.find('@');
          if (endpos == std::string::npos) {
              return _value;
          }
      }
  
      return _value.substr(0, endpos);
  }
  
  std::string message::prefix::user() const
  {
      std::size_t startpos = _value.find('!');
      if (startpos == std::string::npos) {
          return std::string();
      }
  
      std::size_t endpos = _value.find('@');
      if (endpos == std::string::npos) {
          throw format_error("! without @ in prefix");
      }
  
      return _value.substr(startpos, endpos - startpos);
  }
  
  std::string message::prefix::host() const
  {
      std::size_t startpos = _value.find('@');
      if (startpos == std::string::npos) {
          return std::string();
      }
  
      return _value.substr(startpos);
  }
  
  std::string message::prefix::servername() const
  {
      if (_value.find('@') != std::string::npos) {
          throw format_error("@ in server name");
      }
  
      return _value;
  }
  
  
  static std::unordered_map<std::string, std::string>
          tags_map(std::vector<std::string> list)
  {
      std::unordered_map<std::string, std::string> map;
  
      for (auto& tag : list) {
          std::size_t nend = tag.find('=');
          if (nend == std::string::npos) {
              throw format_error("no value for tag");
          }
  
          map.emplace(
              tag.substr(0, nend),
              tag.substr(nend + 1, tag.length() - (nend + 1))
          );
      }
  
      return map;
  }
  
  
  static message::reply get_reply(const std::string& command);
  static std::string to_string(message::reply rpl);
  static message parse(const std::string& line, std::size_t cr = std::string::npos);
  
  message::message() :
      _prefix(), _command(), _params(),
      _has_trailer(), _trailer(),
      _tags(), rpl()
  {}
  
  message::message(const prefix& prefix, const std::string& command,
                   const std::vector<std::string>& params,
                   const std::string& trailer,
                   const std::unordered_map<std::string, std::string>& tags) :
      _prefix(prefix), _command(command), _params(params),
      _has_trailer(true), _trailer(trailer),
      _tags(tags), rpl(get_reply(command))
  {}
  
  message::message(const prefix& prefix, const std::string& command,
                   const std::vector<std::string>& params,
                   const std::unordered_map<std::string, std::string>& tags) :
      _prefix(prefix), _command(command), _params(params),
      _has_trailer(false), _trailer(),
      _tags(tags), rpl(get_reply(command))
  {}
  
  message::message(const prefix& prefix, reply reply,
                   const std::vector<std::string>& params,
                   const std::string& trailer,
                   const std::unordered_map<std::string, std::string>& tags) :
      _prefix(prefix), _command(to_string(reply)), _params(params),
      _has_trailer(true), _trailer(trailer),
      _tags(tags), rpl(reply)
  {}
  
  message::message(const prefix& prefix, reply reply,
                   const std::vector<std::string>& params,
                   const std::unordered_map<std::string, std::string>& tags) :
      _prefix(prefix), _command(to_string(reply)), _params(params),
      _has_trailer(false), _trailer(),
      _tags(tags), rpl(reply)
  {}
  
  message::message(const prefix& prefix, const std::string& command,
                   const std::vector<std::string>& params,
                   const std::string& trailer,
                   const std::vector<std::string>& tags) :
      message(prefix, command, params, trailer, tags_map(tags))
  {}
  
  message::message(const prefix& prefix, const std::string& command,
                   const std::vector<std::string>& params,
                   const std::vector<std::string>& tags) :
      message(prefix, command, params, tags_map(tags))
  {}
  
  message::message(const prefix& prefix, reply reply,
                   const std::vector<std::string>& params,
                   const std::string& trailer,
                   const std::vector<std::string>& tags) :
      message(prefix, reply, params, trailer, tags_map(tags))
  {}
  
  message::message(const prefix& prefix, reply reply,
                   const std::vector<std::string>& params,
                   const std::vector<std::string>& tags) :
      message(prefix, reply, params, tags_map(tags))
  {}
  
  message::message(const prefix& prefix, const std::string& command,
                   const std::vector<std::string>& params,
                   const std::string& trailer) :
      _prefix(prefix), _command(command), _params(params),
      _has_trailer(true), _trailer(trailer),
      _tags(), rpl(get_reply(command))
  {}
  
  message::message(const prefix& prefix, const std::string& command,
                   const std::vector<std::string>& params) :
      _prefix(prefix), _command(command), _params(params),
      _has_trailer(false), _trailer(),
      _tags(), rpl(get_reply(command))
  {}
  
  message::message(const prefix& prefix, reply reply,
                   const std::vector<std::string>& params,
                   const std::string& trailer) :
      _prefix(prefix), _command(to_string(rpl)), _params(params),
      _has_trailer(true), _trailer(trailer),
      _tags(), rpl(reply)
  {}
  
  message::message(const prefix& prefix, reply reply,
                   const std::vector<std::string>& params) :
      _prefix(prefix), _command(to_string(rpl)), _params(params),
      _has_trailer(false), _trailer(),
      _tags(), rpl(reply)
  {}
  
  message::message(const std::string& command,
                   const std::vector<std::string>& params,
                   const std::string& trailer) :
      _prefix(), _command(command), _params(params),
      _has_trailer(true), _trailer(trailer),
      _tags(), rpl(get_reply(command))
  {}
  
  message::message(const std::string& command,
                   const std::vector<std::string>& params) :
      _prefix(), _command(command), _params(params),
      _has_trailer(false), _trailer(),
      _tags(), rpl(get_reply(command))
  {}
  
  message::message(reply reply,
                   const std::vector<std::string>& params,
                   const std::string& trailer) :
      _prefix(), _command(to_string(rpl)), _params(params),
      _has_trailer(true), _trailer(trailer),
      _tags(), rpl(reply)
  {}
  
  message::message(reply reply,
                   const std::vector<std::string>& params) :
      _prefix(), _command(to_string(rpl)), _params(params),
      _has_trailer(false), _trailer(),
      _tags(), rpl(reply)
  {}
  
  message::message(const std::string& line) :
      message(parse(line))
  {}
  
  
  bool message::has_ctcp() const
  { return _trailer[0] == '\1'; }
  
  std::pair<std::string, std::string> message::ctcp() const
  {
      const std::string& msg = _trailer;
      if (msg[0] != '\1') {
          return std::make_pair(std::string(), msg);
      }
  
      bool termd = msg[msg.size()-1] == '\1';
      std::size_t cmd_end = msg.find(' '),
                  value_end = msg.size() - (termd ? 2 : 1);
      std::pair<std::string, std::string> r;
      if (cmd_end == std::string::npos) {
          r.first = msg.substr(1, value_end);
      } else {
          r.first = msg.substr(1, cmd_end - 1);
          r.second = msg.substr(cmd_end + 1, value_end);
      }
  
      return r;
  }
  
  
  std::string message::line(bool terminate) const
  {
      std::string l;
  
      if (has_tags()) {
          l += '@';
          for (auto& tag : _tags) {
              l += tag.first + '=' + tag.second + ';';
          }
          l[l.size() - 1] = ' ';
      }
  
      if (_prefix.exists()) {
          l += ':' + std::string(_prefix) + ' ';
      }
  
      l += _command;
      for (auto& parm : _params) {
          l += ' ' + parm;
      }
  
      if (_trailer.length() > 0) {
          l += " :" + _trailer;
      }
  
      if (terminate) {
          l += "\r\n";
      }
  
      return l;
  }
  
  
  void parser::receive(const std::string& data)
  {
      std::size_t oldbufend = recv_buffer.length();
      recv_buffer += data;
  
      std::size_t pos = 0, end = pos;
      std::size_t cr = recv_buffer.find('\r', oldbufend);
      while (
          cr != std::string::npos &&
          recv_buffer.length() > (cr + 1)
      ) {
          if (recv_buffer[cr + 1] != '\n')
              throw format_error("CR without LF");
  
          end = cr + 2;
          receive(message(recv_buffer.substr(pos, end)));
          pos = end;
          cr = recv_buffer.find('\r', pos);
      }
  
      recv_buffer = recv_buffer.substr(
          end, recv_buffer.size() - end
      );
  }
  
  
  static std::string token(
      const std::string& line, std::size_t& pos, std::size_t& end,
      std::size_t cr, bool cr_npos = false
  )
  {
      end = line.find(' ', pos);
      if (end == std::string::npos || end > cr) {
          if (cr_npos) {
              end = std::string::npos;
              std::string r = line.substr(pos, cr - pos);
              pos = cr + 1;
              return r;
          } else {
              throw format_error("unexpected CR");
          }
      } else {
          std::string r = line.substr(pos, end - pos);
          pos = end + 1;
          return r;
      }
  }
  
  
  message::reply get_reply(const std::string& command)
  {
      try {
          return static_cast<message::reply>(std::stoi(command));
      } catch (const std::invalid_argument& err) {
      } catch (const std::out_of_range& err) {
      }
  
      return message::_RPL_NOTAREPLY;
  }
  
  std::string to_string(message::reply rpl)
  {
      if (rpl == message::_RPL_NOTAREPLY) {
          return "";
      } else {
          std::string r = std::to_string(static_cast<std::uint32_t>(rpl));
          if (r.length() == 1) {
              return std::string("00") + r;
          } else if (r.length() == 2) {
              return std::string("0") + r;
          } else {
              return r;
          }
      }
  }
  
  
  message parse(const std::string& line, std::size_t cr)
  {
      std::size_t pos = 0, end = pos;
  
      if (cr == std::string::npos) {
          cr = line.find('\r', pos);
          if (cr == std::string::npos || line[cr+1] != '\n') {
              throw format_error("no CRLF");
          }
      }
  
      std::unordered_map<std::string, std::string> _tags;
      bool have_tags = false;
      if (line[pos] == '@') {
          have_tags = true;
          pos += 1;
          const std::string tags = token(line, pos, end, cr);
          std::string name, value;
          std::size_t tpos = 0, tend = 0;
  
          while (tend != std::string::npos) {
              tend = tags.find('=', tpos);
              name = tags.substr(tpos, tend - tpos);
              tpos = tend + 1;
              if (tend == std::string::npos)
                  throw format_error("no value for tag");
  
              tend = tags.find(';', tpos);
              value = tags.substr(tpos, tend - tpos);
              tpos = tend + 1;
  
              _tags.emplace(name, value);
          }
      }
  
      message::prefix _prefix;
      if (line[pos] == ':') {
          pos += 1;
          _prefix = token(line, pos, end, cr);
      }
  
      std::string _command = token(line, pos, end, cr, true);
  
      std::vector<std::string> _params;
      while (end != std::string::npos) {
          if (line[pos] == ':') {
              pos += 1;
              std::string _trailer = line.substr(pos, cr - pos);
  
              if (have_tags) {
                  return message(_prefix, _command, _params, _trailer, _tags);
              } else {
                  return message(_prefix, _command, _params, _trailer);
              }
          } else {
              _params.push_back(token(line, pos, end, cr, true));
          }
      }
  
      if (have_tags) {
          return message(_prefix, _command, _params, _tags);
      } else {
          return message(_prefix, _command, _params);
      }
  }
  