/*
  *      Author: alexrayne <alexraynepe196@gmail.com>
  ------------------------------------------------------------------------
    Copyright (c) alexrayne

   All rights reserved.
   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
   - Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   - Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in the
     documentation and/or other materials provided with the distribution.
   - Neither the name of ARM nor the names of its contributors may be used
     to endorse or promote products derived from this software without
     specific prior written permission.
   *
   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
   AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS AND CONTRIBUTORS BE
   LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
   CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   POSSIBILITY OF SUCH DAMAGE. *
  ------------------------------------------------------------------------
 * */
#include <string.h>
#include <HAL/uart_hal.h>

#include "gpio.h"
#include "hw.h"



UART_BUFDevice::UART_BUFDevice()
{
  init();
}

void UART_BUFDevice::init(){
    tx_access.init();
    tx_empty.init();
    rx_avail.init();
    rx_sigcount = rx_sig_force_to;
    connected = false;
}


//virtual
UART_BUFDevice::WaitResult UART_BUFDevice::rx_wait(unsigned to){
    if (to > 0)
    //if (rx.isEmpty())
    {
        PTResult res = rx_avail.take(to);
        if (PT_SCHEDULE(res))
            return ptWAITING;
    }
    return (!rx.isEmpty())? 1 : DEV_TIME_OUT ;
};

//virtual
void UART_BUFDevice::on_rx_over(unsigned c){
    if (ev_rx_over != NULL){
        ev_rx_over(rx_over_arg, this, c);
        return;
    }
    // по дефолту отбрасываю голову буфера. Это идентично поведению при
    //  загрузке буфера по ДМА
    rx.pop_char();
    rx.push_char(c);
}



int UART_BUFDevice::readByte(u8 *data){
    int ch = rx.pop_char();
    if(ch >= 0) {
        *data = ch;
        return DEV_OK;
    }
    else
        return DEV_NOK;
}

int UART_BUFDevice::writeByte(u8 data){
    return send_char(data);
}

int UART_BUFDevice::readData(u8 *data, size_t size){
    EnterCS();
    bool ok = rx.ReadAndDeleteData(data, size);
    ExitCS();
    if(ok)
        return DEV_OK;
    else
        return DEV_NOK;
}

int UART_BUFDevice::writeData(const char *data){
    if (data == NULL)
        return 0;
    return this->writeData((const u8 *)data, strlen(data));
}

int UART_BUFDevice::writeData(const u8 *data, size_t size){
    return SendData(data, size);
}

int UART_BUFDevice::IOCtrl(ioctl_string cmd){
    //NOT_IMPLEMENTED
    return DEV_NOT_IMPLEMENTED;
}

int UART_BUFDevice::setPower(POWER_STATE state){
    //NOT_IMPLEMENTED
    return DEV_NOT_IMPLEMENTED;
}

//*******             stdout_device       ***************
//* блокирующая печать
//virtual
int UART_BUFDevice::putChar(int ch){
  if (!is_ready())
    return 0;
  tx_access.lock();
  int res = writeByte(ch);
  tx_access.unlock();
  return res;
}

//virtual
int UART_BUFDevice::puts( const char* str){
  if (!is_ready() || (str == NULL))
    return 0;
  size_t len = strlen(str);
  if (len <= 0)
      return 0;
  int res = 0;
  tx_access.lock();
  for(; len > 0; ){
    int sent = putData(str, len);
    if (sent > 0) {
      res += sent;
      str += sent;
      len -= sent;
      continue;
    }
    if (sent == 0){
      put_wait(TO_Poll);
      continue;
    }
    //look like put returns error, don't sen any
    break;
  }
  tx_access.unlock();
  return res;
}

//*  ожидание доступности печати
//*  \return - количество байт возможных для неблокирующей печати
//virtual
UART_BUFDevice::WaitResult UART_BUFDevice::put_wait(unsigned to){
  if (!is_ready())
    return -1;

  if (tx.isEmpty())
    return tx.StoreSize();

  int res = tx.FreeSpaceSize();
  if (to == 0)
      return res;

  //подожду пока хоть сколькото отправится в порт.
  //for ( ;res == 0; res = tx.FreeSpaceSize())
  do
  {
      // на полном порте надо убедиться что отправка идет
      SendData(NULL, 0);

      PTResult ok = tx_empty.take(to);
      if (PT_SCHEDULE(ok))
          return 0;
      if (ok == ptNOK)
          return -1;

      if (tx.isEmpty())
        return tx.StoreSize();
  }
  while ( res == tx.FreeSpaceSize() );

  return tx.FreeSpaceSize();
}

//*  почти тоже put_wait, ждет полного опустошения
//virtual
int UART_BUFDevice::put_flush(unsigned to){
  if (!is_ready()){
    return put_drop();
  }
  tx_access.lock();
  while (!tx.isEmpty())
      put_wait(TO_Poll);
  while ( tx_wait() == 0 )
      ;
  tx_access.unlock();
  return 0;
}
//*  очищает буфер, прерывая текущую отправку
//virtual
int UART_BUFDevice::put_drop(){
  tx_access.lock();
  tx.Clear();
  tx_empty.give();
  tx_access.unlock();
  return 0;
}

//virtual
int UART_BUFDevice::putData ( const void* src, unsigned len){
  if (!is_ready() || (len == 0))
    return 0;
  const char* str = (const char*)src;
  int res = 0;
  tx_access.lock();
  while (len > 0){
    int sent = postData(str, len);
    if (sent > 0){
      str += sent;
      res += sent;
      len -= sent;
    }
    else
      put_wait(TO_Poll);
  }
  tx_access.unlock();
  return res;
}

//*  неблокирующая печать
//*  \return - длинна отправленного участка
//virtual
int UART_BUFDevice::postData ( const void* src, unsigned len){
  const char* str = (const char*)src;
  if (!is_ready() || (len == 0))
    return 0;
  if (len == 0)
    return 0;
  int res = 0;
  tx_access.lock();
  do {
    size_t avail = tx.FreeSpaceSize();
    if (avail <= len)
      len = avail;
    if (len == 0)
      break;
    int sent = SendData((const u8*)str, len);
    if (sent > 0){
      str += sent;
      res += sent;
      len -= sent;
    }
    else
      break;
  } while(len > 0);
  tx_access.unlock();
  return res;
}

//* монополизация вывода
//* \arg onoff - захват/освобождение
//* \return    - состояние захвачн ли вывод
//virtual
bool UART_BUFDevice::put_access(bool onoff, unsigned to){
  //if (!tx.ready)
  //  return true;
  if (onoff)
    return tx_access.lock(to);
  else {
    tx_access.unlock();
    return true;
  }
}

//virtual
int UART_BUFDevice::get_char(){
  if (!is_ready())
    return -1;
  while (rx.isEmpty()){
    int res = get_wait(TO_Poll);
    if (res <= 0)
      return -1;
  }
  EnterCS();
  int res = rx.pop_char();
  ExitCS();
  return res;
}

//virtual
UART_BUFDevice::WaitResult UART_BUFDevice::get_wait(unsigned to){
  if (!is_ready())
    return DEV_NOT_IMPLEMENTED;

  bool infinit = (to == toInfinite);
  if (infinit)
    to = TO_PollRXIRQ ;

  if (rx.isEmpty()) {
  // just for underlying hardware update

  WaitResult ok = rx_wait(to);
  if ( ok <= 0 )
      return ok;

  for ( ; rx.isEmpty(); ) {
    if (!infinit)
        return DEV_TIME_OUT;

    ok = rx_wait(to);
    if (ok <= 0)
        return ok;
  }
  } //if (rx.isEmpty())

  int res = rx.DataSize();
  return res;
}

// блокирующий прием
//*  \return - длинна полученного участка
//virtual
int UART_BUFDevice::getData ( void* dst, unsigned len){
    if (!is_ready() || (dst == NULL) || (len == 0))
      return -1;
    int avail = get_waitfor(len);
    if (avail > len)
        avail = len;
    if (avail > 0){
        EnterCS();
        bool ok = rx.ReadAndDeleteData(dst, avail);
        ExitCS();
        if(ok)
            return avail;
    }
    return 0;
}

//*  ожидание amount доступных символов
//virtual
UART_BUFDevice::WaitResult UART_BUFDevice::get_waitfor(unsigned amount, unsigned to){
    if (!is_ready())
      return -1;

    unsigned have = rx.DataSize();
    if (have >= amount)
        return have;

    bool infinit = (to == toInfinite);
    if (infinit)
        to = TO_PollRXIRQ ;

    for ( ; have < amount; ) {
      rx_sigcount = amount - have;
      WaitResult ok = rx_wait(to);
      have = rx.DataSize();
      if ( ok <= 0 ) {
          if (!infinit)
            return have;
      }
    }
    return have;
}



