#include <stdio.h>
#include <string>
#include <iostream>
#include <arpa/inet.h>
#include "fmt/format.h"
#include "fmt/printf.h"
#include <fstream>
#include <map>
#include <bitset>
#include <curses.h>
#include <vector>
#include "galileo.hh"
#include "navmon.pb.h"
#include <unistd.h>
#include "navmon.hh" 

#include "CLI/CLI.hpp"
#include "version.hh"

static char program[]="navdisplay";

using namespace std;

extern const char* g_gitHash;

struct WinKeeper
{
  WinKeeper();
  struct Window
  {
    WINDOW *header, *text;
    int sv;
    void setHeader(std::string_view line)
    {
      wclear(header);
      wmove(header, 0, 0);
      wattron(header, A_BOLD | A_UNDERLINE);
      wprintw(header, "%s", &line[0]);
      wattroff(header, A_BOLD | A_UNDERLINE);
      wrefresh(header);
    }
    void setStatus(std::string_view line)
    {
      wmove(header, 1, 0);
      wattron(header, A_BOLD);
      wprintw(header, "%s", &line[0]);
      wattroff(header, A_BOLD);
      wrefresh(header);
    }

    void emitLine(std::string_view line)
    {
      wprintw(text, "%s\n", &line[0]);
      wrefresh(text);
    }

    void clear()
    {
      wclear(header);
      wrefresh(header);
      wclear(text);
      wrefresh(text);
    }
    time_t lastTime{0};
  };
  vector<Window> d_windows;
  static int d_h, d_w;
  void emitLine(int sv, std::string_view line);
  void setStatus(int sv, std::string_view line);

};

int WinKeeper::d_h;
int WinKeeper::d_w;

WinKeeper::WinKeeper()
{
  initscr();
  getmaxyx(stdscr, d_h, d_w);

  int winwidth = d_w / 7;
  for(int n=0; n < 8 ; ++n) {
    d_windows.push_back({
        newwin(3, winwidth, 0, n*(winwidth+2)),
        newwin(d_h-3, winwidth, 3, n*(winwidth+2)),
          0});
    scrollok(d_windows[n].text, 1);
  }
};

void WinKeeper::emitLine(int sv, std::string_view line)
{
  for(auto& w: d_windows) {
    if(w.sv == sv) {
      w.emitLine(line);
      return;
    }
  }
  // nothing matched, need to find a new window
  for(auto& w: d_windows) {
    if(!w.sv) {
      w.sv = sv;
      w.setHeader(std::to_string(sv));
      w.emitLine(line);
      return;
    }
  }
  // all windows are in use, take over oldest one
  int age=0;
  time_t now = time(0);
  auto *wptr = &*d_windows.begin();
  for(auto& w: d_windows) {
    if(now - w.lastTime > age) {
      age = now - w.lastTime;
      wptr = &w;
    }
  }

  wptr->sv = sv;
  wptr->setHeader(std::to_string(sv));
  wptr->emitLine(line);
  wptr->lastTime = now;
}

void WinKeeper::setStatus(int sv, std::string_view line)
{
  for(auto& w: d_windows) {
    if(w.sv == sv) {
      w.setStatus(line);
      return;
    }
  }
  // nothing matched
  for(auto& w: d_windows) {
    if(!w.sv) {
      w.sv = sv;
      w.setHeader(std::to_string(sv));
      w.setStatus(line);
      return;
    }
  }
  throw std::runtime_error("Ran out of windows searching for sv "+std::to_string(sv));
}


int main(int argc, char** argv)
{
  bool doVERSION{false};

  CLI::App app(program);

  app.add_flag("--version", doVERSION, "show program version and copyright");

  try {
    app.parse(argc, argv);
  } catch(const CLI::Error &e) {
    return app.exit(e);
  }

  if(doVERSION) {
    showVersion(program, g_gitHash);
    exit(0);
  }

  WinKeeper wk;
  for(;;) {
    char bert[4];
    if(readn2(0, bert, 4) != 4 || bert[0]!='b' || bert[1]!='e' || bert[2] !='r' || bert[3]!='t') {
      cerr<<"EOF or bad magic"<<endl;
      break;
    }
    
    uint16_t len;
    if(readn2(0, &len, 2) != 2)
      break;
    len = htons(len);
    char buffer[len];
    if(readn2(0, buffer, len) != len)
      break;
    
    NavMonMessage nmm;
    nmm.ParseFromString(string(buffer, len));
    int sv = nmm.gi().gnsssv();
    time_t now = time(0);
    if(nmm.localutcseconds() < now - 300)
      continue;
    if(nmm.type() == NavMonMessage::GalileoInavType) {
      static map<int, GalileoMessage> gms;
      GalileoMessage& gm = gms[nmm.gi().gnsssv()];
      
      basic_string<uint8_t> inav((uint8_t*)nmm.gi().contents().c_str(), nmm.gi().contents().size());
      int wtype =gm.parse(inav);
      wk.emitLine(sv, "src "+to_string(nmm.sourceid())+" wtype " + to_string(wtype));
      //        wk.setStatus(sv, "Hlth: "+std::to_string(getbitu(&inav[0], 67, 2)) +", el="+to_string(g_svstats[sv].el)+", db="+to_string(g_svstats[sv].db) );
      //          wk.emitLine(sv, "GST-UTC6, a0="+to_string(a0)+", a1="+to_string(a1)+", age="+to_string(tow/3600-t0t)+"h, dw="+to_string(dw)
      //                      +", wn0t="+to_string(wn0t)+", wn8="+to_string(wn&0xff));
      //          wk.emitLine(sv, "GST-UTC6, a0="+to_string(a0)+", a1="+to_string(a1));

      //        wk.emitLine(sv, "Alm"+to_string(wtype)+" IODa="+to_string(ioda));
      //          wk.emitLine(sv, "GST-GPS, a0g="+to_string(a0g)+", a1g="+to_string(a1g)+", t0g="+to_string(t0g)+", age="+to_string(tow/3600-t0g)+"h, dw="+to_string(dw)
      //                      +", wn0g="+to_string(wn0g)+", wn6="+to_string(wn&(1+2+4+8+16+32)));
    }
  }
 }
