/*!
  \file
  \brief GPS モジュール

  \author Satofumi KAMIMURA

  $Id: GpsCtrl.cpp 1000 2009-06-13 22:22:06Z satofumi $
*/

#include "GpsCtrl.h"
#include "NmeaData.h"
#include "SerialDevice.h"
#include "ConnectionUtils.h"
#include <cstring>

using namespace qrk;
using namespace std;


namespace
{
  enum {
    BufferSize = 512,
  };
}

struct GpsCtrl::pImpl
{
  string error_message_;
  Connection* con_;
  SerialDevice* serial_;

  char buffer_[BufferSize];
  int filled_;

  //string recv_buffer_;


  pImpl(void)
    : error_message_("no error."), con_(NULL), serial_(NULL), filled_(0)
  {
  }


  ~pImpl(void)
  {
    // con_ は外部からセットされる可能性があるため、このクラスでは解放しない
    // serial_ のみ解放する
    delete serial_;
  }


  bool connect(const char* device, long baudrate)
  {
    disconnect();
    if (! con_) {
      initializeSerial();
    }

    if (! con_->connect(device, baudrate)) {
      error_message_ = con_->what();
      return false;
    }

    filled_ = 0;
    return true;
  }


  void initializeSerial(void)
  {
    if (! serial_) {
      serial_ = new SerialDevice;
    }
    con_ = serial_;
  }


  void disconnect(void)
  {
    if (con_) {
      con_->disconnect();
    }
  }


  void update(NmeaData* nmea)
  {
    // データ読み出し
    size_t buffer_space_size = BufferSize - filled_ - 1;
    int n = con_->receive(&buffer_[filled_], buffer_space_size, 0);
    if (n <= 0) {
      return;
    }
    filled_ += n;
    buffer_[filled_] = '\n';

    while (true) {
      // パケットを取り出す
      int last_index = strcspn(buffer_, "\r\n");
      if (last_index >= filled_) {
        return;
      }

      int first_index = strcspn(buffer_, "$");

      if ((last_index < first_index) || (first_index >= filled_)) {
        // 無効なパケットより後ろのデータを、先頭に移動
        moveRemainData(first_index);
        continue;
      }

      string line = string(&buffer_[first_index], last_index - first_index);
      moveRemainData(last_index);

      // 受信情報を反映
      nmea->setLine(line);
    }
  }


  void moveRemainData(int next_first)
  {
    size_t filled_left = filled_ - next_first;
    memmove(&buffer_[0], &buffer_[next_first], filled_left);
    filled_ = filled_left;
    buffer_[filled_] = '\0';
  }
};


GpsCtrl::GpsCtrl(void) : pimpl(new pImpl)
{
}


GpsCtrl::~GpsCtrl(void)
{
}


const char* GpsCtrl::what(void) const
{
  return pimpl->error_message_.c_str();
}


bool GpsCtrl::connect(const char* device, long baudrate)
{
  return pimpl->connect(device, baudrate);
}


void GpsCtrl::setConnection(Connection* con)
{
  pimpl->con_ = con;
}


Connection* GpsCtrl::connection(void)
{
  return pimpl->con_;
}


void GpsCtrl::disconnect(void)
{
  pimpl->disconnect();
}


bool GpsCtrl::isConnected(void)
{
  return (pimpl->con_ == NULL) ? false : pimpl->con_->isConnected();
}


void GpsCtrl::update(NmeaData* nmea)
{
  return pimpl->update(nmea);
}
