#include "qgftfptargetsim.h"

#include <QUdpSocket>
#include <QNetworkDatagram>
#include <QTimerEvent>
#include <QTimer>
#include <QFile>

#include <QDebug>

#include "bsk_tftpd.h"
#include "bsk_tftp_pfs.h"

#pragma GCC diagnostic ignored "-Wmissing-field-initializers"

static bsk_download_site_t sites[]=
{
    {"ZynQ", "0x94", 0, 0, 6,
     {
        {"BL", bsk_download_partition_t::part_null, 64*1024},
        {"MM", bsk_download_partition_t::part_null, 128*1024},
        {"APP0", bsk_download_partition_t::part_null, 1*1024*1024},
        {"APP1", bsk_download_partition_t::part_null, 1*1024*1024},
        {"PL0", bsk_download_partition_t::part_null, 1*1024*1024},
        {"PL1", bsk_download_partition_t::part_null, 1*1024*1024}
     }
    },
    {"ZynQ", "0x95", 0, 0, 7,
     {
        {"BL", bsk_download_partition_t::part_null, 64*1024},
        {"MM", bsk_download_partition_t::part_null, 128*1024},
        {"OPER", bsk_download_partition_t::part_null, 1*1024*1024},
        {"PL0", bsk_download_partition_t::part_null, 1*1024*1024},
        {"PL1", bsk_download_partition_t::part_null, 1*1024*1024},
        {"FPGA0", bsk_download_partition_t::part_null, 0},
        {"FPGA1", bsk_download_partition_t::part_null, 0}
     }
    },

    {"DSP", "0x54", 0, 0, 4,
     {
        {"BL", bsk_download_partition_t::part_fsbl|bsk_download_partition_t::part_hidden, 64*1024},
        {"MM", bsk_download_partition_t::part_recovery|bsk_download_partition_t::part_hidden, 128*1024},
        {"OPER", bsk_download_partition_t::part_operative|bsk_download_partition_t::part_default, 1*1024*1024},
        {"APP2", 0, 1*1024*1024}
     }
    },
    {"DSP", "0x54", 0, 0, 4,
     {
        {"BL", bsk_download_partition_t::part_fsbl|bsk_download_partition_t::part_hidden, 64*1024},
        {"MM", bsk_download_partition_t::part_recovery|bsk_download_partition_t::part_hidden, 128*1024},
        {"OPER", bsk_download_partition_t::part_operative|bsk_download_partition_t::part_default, 1*1024*1024},
        {"APP2", 0, 1*1024*1024}
     }
    },
    {"DSP", "0x54", 0, 0, 4,
     {
        {"BL", bsk_download_partition_t::part_fsbl|bsk_download_partition_t::part_hidden, 64*1024},
        {"MM", bsk_download_partition_t::part_recovery|bsk_download_partition_t::part_hidden, 128*1024},
        {"OPER", bsk_download_partition_t::part_operative|bsk_download_partition_t::part_default, 1*1024*1024},
        {"APP2", 0, 1*1024*1024}
     }
    },
    {"DSP", "0x54", 0, 0, 4,
     {
        {"BL", bsk_download_partition_t::part_fsbl|bsk_download_partition_t::part_hidden, 64*1024},
        {"MM", bsk_download_partition_t::part_recovery|bsk_download_partition_t::part_hidden, 128*1024},
        {"OPER", bsk_download_partition_t::part_operative|bsk_download_partition_t::part_default, 1*1024*1024},
        {"APP2", 0, 1*1024*1024}
     }
    }
};

class QgTftpTargetSim::Implementation
{
public:
  QString name;
  int site_id;

  QObject* parent;
  QUdpSocket tftpServer;

  //static Implementation* gInstance;
  QHostAddress clientAddress;
  int clientPort;
  //int timerId;
  QTimer storeTimer;
  QFile storeFile;
  QByteArray storeBuffer;
  bsk_download_partition_t* currentParitition;

  int store_time;
  int progr_time;

  bsk_tftpd_context_id_t id;
  const bsk_tftpd_context_info_t* tftpd_cx;

  int state;

  Implementation(const QString& n, int sid, QObject* p):
      name(n),
      site_id(sid),
      parent(p),
      tftpServer(p)
  {
      //gInstance=this;
      store_time=2*1000;
      progr_time=2*1000;
      storeTimer.setSingleShot(true);
      storeBuffer.reserve(1024*1024);
      currentParitition=0;
      state=0;
  }

  void processPendingDatagram()
  {
      while(tftpServer.hasPendingDatagrams())
      {
          QNetworkDatagram d=tftpServer.receiveDatagram();
          //qDebug()<<"RCV:"<<d.data().size();
          auto cAddress=d.senderAddress();
          auto cPort=d.senderPort();
#if 0
          if (!tftpServer.isOpen())
          {
              tftpServer.connectToHost(d.senderAddress(), d.senderPort());
              qDebug()<<"Connect to"<<d.senderAddress()<<d.senderPort();
          }
          else if ((tftpServer.peerPort()!=d.senderPort()) || (tftpServer.peerAddress()!=d.senderAddress()))
          {
              tftpServer.connectToHost(d.senderAddress(), d.senderPort());
              qDebug()<<"Connect to"<<d.senderAddress()<<d.senderPort();
          }
#endif
          int r=bsk_tftpd_process_packet(id, cAddress.toIPv4Address(), cPort,
                                         d.data().constData(), d.data().size());
          if (r==0)
          {
              clientAddress=cAddress;
              clientPort=cPort;
          }
          else if (r!=-100)
          {
              qDebug()<<"Disconnect";
              //tftpServer.disconnectFromHost();
          }
      }
  }

  static int bsk_tftpd_log_delegate(bsk_tftpd_context_id_t id, const char* const msg, ...)
  {
      const bsk_tftpd_context_info_t* cx=bsk_tftpd_context_info(id);
      va_list args;
      va_start(args, msg);
      qDebug()<<id<<cx->name<<cx->port<<QString::vasprintf(msg, args);
      va_end(args);
      return 0;
  }

  char* progr_msg(const char* msg, int n=0)
  {
      static char buffer[512];
      if (n==0)
      {
          int i=storeTimer.interval();
          int r=storeTimer.remainingTime();
          n=(float(i-r)/float(i))*100;
          if (n>100)
              n=100;
          if (n<0)
              n=-1;
      }
      sprintf(buffer, "%d %s", n, msg);

      return buffer;
  }

  static bsk_tftpd_receive_status_t bsk_tftpd_receive_delegate(bsk_tftpd_context_id_t id, bsk_tftpd_receive_delegate_operation_t op, const void* const data, unsigned int block, unsigned int offset, unsigned int size_bytes)
  {
      const bsk_tftpd_context_info_t* cx=bsk_tftpd_context_info(id);
      Implementation* gInstance=reinterpret_cast<Implementation*>(cx->cookie);

      bsk_tftpd_receive_status_t sts={bsk_tftpd_ok, 0};
      if (op==bsk_tftpd_open_read_not_supported)
      {
          if (strcmp(bsk_tftpd_mfs_status_query, (char*)data)==0)
          {
             if (gInstance->state==0)
             {
                 sts.sts=bsk_tftpd_err_not_found;
                 sts.ack_msg="ready";
             }
             else if (gInstance->state==2)
             {
                 sts.sts=bsk_tftpd_err_bad_id;
                 sts.ack_msg=gInstance->progr_msg("Erasing");
             }
             else if (gInstance->state==3)
             {
                 sts.sts=bsk_tftpd_err_bad_id;
                 sts.ack_msg=gInstance->progr_msg("Storing");
             }
             else
             {
                 sts.sts=bsk_tftpd_err_bad_id;
                 sts.ack_msg="busy";
             }
          }
          else if (strcmp(bsk_tftpd_mfs_list_query, (char*)data)==0)
          {
              sts.sts=bsk_tftpd_err_file_exists;
              sts.ack_msg=bsk_tftpd_mfs_get_list(id);
          }
          else
              sts.sts=bsk_tftpd_err_not_found;
          return sts;
      }

      if ((op==bsk_tftpd_open_file) && (gInstance->state>0))
      {
          static QString reply_msg;
          static char cmsg[512];
          sts.sts=bsk_tftpd_err_not_found;
          int remain=gInstance->storeTimer.remainingTime();
          float store_completition=100*(5000.0f/(5000.0f-float(remain)));
          reply_msg=QString::asprintf("%u%% storing", int(store_completition));
          strncpy(cmsg, reply_msg.toLatin1().constData(), sizeof cmsg);
          sts.ack_msg=0; //cmsg;
          qDebug()<<sts.sts<<sts.ack_msg;
      }
      else if (op==bsk_tftpd_open_file)
      {
          const char* fname;
          gInstance->currentParitition=0;
          bsk_download_partition_t* p=bsk_tftp_mfs_decode_filename(gInstance->site_id, (char*)data, &fname);
          if (p==bsk_tftp_mfs_invalid_partition)
          {
              sts.sts=bsk_tftpd_err_not_found;
              sts.ack_msg=0;
              return sts;
          }
          gInstance->state=1;          
          if (p)
          {
              if (gInstance->storeFile.isOpen())
              {
                  qDebug()<<"WARNING:store file is open!!!";
                  gInstance->storeFile.close();
              }
              //gInstance->storeFile.setFileName(QString("_%1_%2.dat").arg(gInstance->site_id).arg(p->name));
              //gInstance->storeFile.open(QIODevice::WriteOnly);
              gInstance->storeBuffer.clear();
              gInstance->storeBuffer.reserve(1024*1024);
              gInstance->currentParitition=p;
          }

      }
      else if (op==bsk_tftpd_data)
      {
          //if (gInstance->storeFile.isOpen())
          //{
          //    gInstance->storeFile.write((char*)data, size_bytes);
          //}
          gInstance->storeBuffer.append((char*)data, size_bytes);
      }
      else if (op==bsk_tftpd_end)
      {
          //if (gInstance->storeFile.isOpen())
          //{
          //  gInstance->storeFile.write((char*)data, size_bytes);
          //  gInstance->storeFile.flush(); //close();
          //}
          if (gInstance->currentParitition)
          {
            if (gInstance->storeBuffer.size()>gInstance->currentParitition->max_size)
            {
                sts.sts=bsk_tftpd_err_no_space;
                sts.ack_msg="file too big";
            }
            else
            {
                if (size_bytes)
                    gInstance->storeBuffer.append((char*)data, size_bytes);

                sts.sts=bsk_tftpd_dont_ack;
                gInstance->storeTimer.start(gInstance->store_time);
                gInstance->storeFile.setFileName(QString("_%1_%2.dat").arg(gInstance->site_id).arg(gInstance->currentParitition->name));
                bool ok=gInstance->storeFile.open(QIODevice::WriteOnly);
                if (ok)
                {
                    gInstance->storeFile.write(gInstance->storeBuffer);
                    gInstance->storeFile.close();
                    gInstance->state=2;
                }
                else
                {
                    sts.sts=bsk_tftpd_err_access;
                    sts.ack_msg="error storing";
                }
            }
          }
          else
          {
              gInstance->state=0;
          }
      }

      return sts;
  }

  static int bsk_tftpd_send_delegate(bsk_tftpd_context_id_t id, unsigned int adr, unsigned int port, const void* const data, unsigned int size)
  {
      const bsk_tftpd_context_info_t* cx=bsk_tftpd_context_info(id);
      Implementation* gInstance=reinterpret_cast<Implementation*>(cx->cookie);

      QHostAddress ha(gInstance->clientAddress);
      int hp=gInstance->clientPort;
      if (adr)
          ha=QHostAddress(adr);
      if (port)
          hp=port;

      qDebug()<<"TFTP: send"<<QString("%1").arg(ha.toIPv4Address(), 0, 16)<<hp<<size;
      gInstance->tftpServer.writeDatagram((char*)data, size, ha, hp); //gInstance->clientAddress, gInstance->clientPort);
      return 0;
  }

};

//QgFtfpTargetSim::Implementation* QgFtfpTargetSim::Implementation::gInstance;

QgTftpTargetSim::QgTftpTargetSim(const QString &name, int sid, int port):
    p_(*new Implementation(name, sid, this))
{
    bsk_tftpd_mfs_set_site(&sites[0], 3);

    p_.tftpServer.bind(QHostAddress::LocalHost, port);

    connect(&p_.tftpServer, &QUdpSocket::readyRead, this, pendingDatagram);

    char* kn=strdup(name.toLatin1().constData());

    p_.id=bsk_tftpd_context_initialize(-1, kn, port, &p_);
    p_.tftpd_cx=bsk_tftpd_context_info(p_.id);

    bsk_tftpd_set_log_delegate(p_.id, Implementation::bsk_tftpd_log_delegate);
    bsk_tftpd_set_receive_delegate(p_.id, Implementation::bsk_tftpd_receive_delegate);
    bsk_tftpd_set_send_delegate(p_.id, Implementation::bsk_tftpd_send_delegate);

    connect(&p_.storeTimer, &QTimer::timeout, this, &timerExpired);
}

QgTftpTargetSim::~QgTftpTargetSim()
{
    delete &p_;
}

void QgTftpTargetSim::pendingDatagram()
{
    p_.processPendingDatagram();
}

void QgTftpTargetSim::setEraseTime(int t)
{
    p_.store_time=t*1000;
}

void QgTftpTargetSim::setProgTime(int t)
{
    p_.progr_time=t*1000;
}

void QgTftpTargetSim::timerExpired()
{
    if (p_.state==2)
    {
        p_.state=3;
        p_.storeTimer.start(p_.progr_time);
    }
    else if (p_.state==3)
    {
        p_.storeTimer.stop();
        bsk_tftpd_send_ack(p_.id, bsk_tftpd_ok, 0);
        if (p_.storeFile.isOpen())
        {
            qDebug()<<"File stored:"<<p_.storeFile.fileName();
            p_.storeFile.close();
        }
        else
        {
            qDebug()<<"File not stored:"<<p_.storeFile.fileName();
        }
        p_.state=0;
    }
}

#if 0
void QgFtfpTargetSim::timerEvent(QTimerEvent *event)
{
    if (event->timerId()==p_.timerId)
    {
        if (state==2)

        p_.timerId=0;
        bsk_tftpd_send_ack(p_.id, bsk_tftpd_ok, 0);
    }
}
#endif
