#include "main.h"
#include "visual-details.h"
#include <vector>
#include <iostream>

// convert the sprixel at s having pixel dimensions dimyXdimx to an rgb(a)
// matrix for easier analysis. breaks on malformed sixels.
std::vector<uint32_t> sixel_to_rgb(const char* s, int dimy, int dimx) {
  std::vector<uint32_t> bmap(dimy * dimx, 0x00000000ull);
  std::vector<uint32_t> colors;
  // first we skip the header
  while(*s != '#'){
    ++s;
  }
  // now we build the color table (form: #Pc;Pu;Px;Py;Pz). data starts with
  // a color spec lacking a semicolon.
  enum {
    STATE_WANT_HASH,
    STATE_WANT_COLOR,
    STATE_WANT_COLORSEMI,
    STATE_WANT_COLORSPACE,
    STATE_WANT_DATA,
  } state = STATE_WANT_HASH;
  unsigned color = 0;
  unsigned x = 0;
  unsigned y = 0;
  unsigned rle = 1;
  while(*s){
    if(*s == '\e'){
      break;
    }
    if(state == STATE_WANT_HASH){
      REQUIRE('#' == *s);
      state = STATE_WANT_COLOR;
    }else if(state == STATE_WANT_COLOR){
      CHECK(isdigit(*s));
      color = 0;
      do{
        color *= 10;
        color += *s - '0';
        ++s;
      }while(isdigit(*s));
//std::cerr << "Got color " << color << std::endl;
      --s;
      state = STATE_WANT_COLORSEMI;
    }else if(state == STATE_WANT_COLORSEMI){
      // if we get a semicolon, we're a colorspec, otherwise data
      if(*s == ';'){
        state = STATE_WANT_COLORSPACE;
      }else{
        state = STATE_WANT_DATA;
        rle = 1;
      }
    }else if(state == STATE_WANT_COLORSPACE){
      CHECK('2' == *(s++));
      CHECK(';' == *(s++));
      int r = 0;
      do{
        r *= 10;
        r += *s - '0';
        ++s;
      }while(isdigit(*s));
      CHECK(';' == *(s++));
      int g = 0;
      do{
        g *= 10;
        g += *s - '0';
        ++s;
      }while(isdigit(*s));
      CHECK(';' == *(s++));
      int b = 0;
      do{
        b *= 10;
        b += *s - '0';
        ++s;
      }while(isdigit(*s));
      uint32_t rgb = htole(0xff000000 + (r << 16u) * 255 / 100 + (g << 8u) * 255 / 100 + b * 255 / 100);
//std::cerr << "Got color " << color << ": " << r << "/" << g << "/" << b << std::endl;
      if(color >= colors.capacity()){
        colors.resize(color + 1);
      }
      colors[color] = rgb;
      state = STATE_WANT_HASH;
      --s;
    }
    // read until we hit next colorspec
    if(state == STATE_WANT_DATA){
//std::cerr << "Character " << *s << std::endl;
      if(*s == '#'){
        state = STATE_WANT_HASH;
        --s;
      }else if(*s == '!'){ // RLE
        ++s;
        rle = 0;
        do{
          rle *= 10;
          rle += *s - '0';
          ++s;
        }while(isdigit(*s));
        CHECK(2 < rle);
        --s;
      }else if(*s == '$'){
        x = 0;
        state = STATE_WANT_HASH;
      }else if(*s == '-'){
        x = 0;
        y += 6;
        state = STATE_WANT_HASH;
      }else{
//std::cerr << "RLE: " << rle << " pos: " << y << "*" << x << std::endl;
        for(unsigned xpos = x ; xpos < x + rle ; ++xpos){
          for(unsigned ypos = y ; ypos < y + 6 ; ++ypos){
            if((*s - 63) & (1u << (ypos - y))){
              // ought be an empty pixel
              CHECK(0x00000000ull == bmap[ypos * dimx + xpos]);
//std::cerr << *s << " BMAP[" << ypos << "][" << xpos << "] = " << std::hex << colors[color] << std::dec << std::endl;
              bmap[ypos * dimx + xpos] = colors[color];
            }
          }
        }
        x += rle;
        rle = 1;
      }
    }
    ++s;
  }
  return bmap;
}

/*
void print_bmap(const std::vector<uint32_t> rgba, int pixy, int pixx){
  for(int y = 0 ; y < pixy ; ++y){
    for(int x = 0 ; x < pixx ; ++x){
      std::cerr << "rgba[" << y << "][" << x << "] (" << y * x << "): " << std::hex << rgba[y * pixx + x] << std::dec << std::endl;
    }
  }
}
*/

TEST_CASE("Sixels") {
  auto nc_ = testing_notcurses();
  REQUIRE(nullptr != nc_);
  ncplane* ncp_ = notcurses_stdplane(nc_);
  REQUIRE(ncp_);
  auto n_ = notcurses_stdplane(nc_);
  REQUIRE(n_);

  // this can only run with a Sixel backend
  if(notcurses_check_pixel_support(nc_) <= 0){
    CHECK(0 == notcurses_stop(nc_));
    return;
  }
  if(nc_->tcache.color_registers <= 0){
    CHECK(0 == notcurses_stop(nc_));
    return;
  }

#ifdef NOTCURSES_USE_MULTIMEDIA
  SUBCASE("SixelRoundtrip") {
    CHECK(1 == ncplane_set_base(n_, "&", 0, 0));
    auto ncv = ncvisual_from_file(find_data("worldmap.png").get());
    REQUIRE(ncv);
    struct ncvisual_options vopts{};
    vopts.blitter = NCBLIT_PIXEL;
    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
    auto newn = ncvisual_render(nc_, ncv, &vopts);
    CHECK(newn);
    CHECK(0 == notcurses_render(nc_));
    auto rgb = sixel_to_rgb(newn->sprite->glyph, newn->sprite->pixy, newn->sprite->pixx);
    for(int y = 0 ; y < newn->sprite->pixy ; ++y){
      for(int x = 0 ; x < newn->sprite->pixx ; ++x){
//fprintf(stderr, "%03d/%03d NCV: %08x RGB: %08x\n", y, x, ncv->data[y * newn->sprite->pixx + x], rgb[y * newn->sprite->pixx + x]);
        // FIXME
        //CHECK(ncv->data[y * newn->sprite->pixx + x] == rgb[y * newn->sprite->pixx + x]);
      }
    }
    ncvisual_destroy(ncv);
  }

  SUBCASE("SixelBlit") {
    CHECK(1 == ncplane_set_base(n_, "&", 0, 0));
    auto ncv = ncvisual_from_file(find_data("natasha-blur.png").get());
    REQUIRE(ncv);
    struct ncvisual_options vopts{};
    vopts.blitter = NCBLIT_PIXEL;
    vopts.flags = NCVISUAL_OPTION_NODEGRADE;
    auto newn = ncvisual_render(nc_, ncv, &vopts);
    CHECK(newn);
    auto rgbold = sixel_to_rgb(newn->sprite->glyph, newn->sprite->pixy, newn->sprite->pixx);
//print_bmap(rgbold, newn->sprite->pixy, newn->sprite->pixx);
    CHECK(0 == notcurses_render(nc_));
    struct ncplane_options nopts = {
      .y = ncplane_dim_y(newn) * 3 / 4,
      .x = 0,
      .rows = ncplane_dim_y(newn) / 4,
      .cols = ncplane_dim_x(newn) / 2,
      .userptr = nullptr, .name = "blck",
      .resizecb = nullptr,
      .flags = 0,
      .margin_b = 0, .margin_r = 0,
    };
    auto blockerplane = ncplane_create(newn, &nopts);
    REQUIRE(nullptr != blockerplane);
    uint64_t chan = CHANNELS_RGB_INITIALIZER(0, 0, 0, 0, 0, 0);
    CHECK(1 == ncplane_set_base(blockerplane, " ", 0, chan));
    CHECK(0 == notcurses_render(nc_));
    CHECK(1 == ncplane_set_base(n_, "%", 0, 0));
    CHECK(0 == notcurses_render(nc_));
    // FIXME at this point currently, we get a degraded back of the orca
    // test via conversion back to image? unsure
    auto rgbnew = sixel_to_rgb(newn->sprite->glyph, newn->sprite->pixy, newn->sprite->pixx);
//print_bmap(rgbnew, newn->sprite->pixy, newn->sprite->pixx);
    CHECK(0 == ncplane_destroy(newn));
    CHECK(0 == ncplane_destroy(blockerplane));
    ncvisual_destroy(ncv);
  }
#endif

  CHECK(!notcurses_stop(nc_));
}
