#ifdef CONTROLLER_CPP

//The Super Scope is a light-gun: it detects the CRT beam cannon position,
//and latches the counters by toggling iobit. This only works on controller
//port 2, as iobit there is connected to the PPU H/V counter latch.
//(PIO $4201.d7)

//It is obviously not possible to perfectly simulate an IR light detecting
//a CRT beam cannon, hence this class will read the PPU raster counters.

//A Super Scope can still technically be used in port 1, however it would
//require manual polling of PIO ($4201.d6) to determine when iobit was written.
//Note that no commercial game ever utilizes a Super Scope in port 1.

void SuperScope::enter() {
  unsigned prev = 0;
  while(true) {
    unsigned next = cpu.vcounter() * 1364 + cpu.hcounter();

    if(offscreen == false) {
      unsigned target = y * 1364 + (x + 24) * 4;
      if(next >= target && prev < target) {
        //CRT raster detected, toggle iobit to latch counters
        iobit(0);
        iobit(1);
      }
    }

    if(next < prev) {
      //Vcounter wrapped back to zero; update cursor coordinates for start of new frame
      int nx = interface()->inputPoll(port, Input::Device::SuperScope, 0, (unsigned)Input::SuperScopeID::X);
      int ny = interface()->inputPoll(port, Input::Device::SuperScope, 0, (unsigned)Input::SuperScopeID::Y);
      nx += x;
      ny += y;
      x = max(-16, min(256 + 16, nx));
      y = max(-16, min(240 + 16, ny));
      offscreen = (x < 0 || y < 0 || x >= 256 || y >= (ppu.overscan() ? 240 : 225));
    }

    prev = next;
    step(2);
  }
}

uint2 SuperScope::data() {
  if(counter >= 8) return 1;

  if(counter == 0) {
    //turbo is a switch; toggle is edge sensitive
    bool newturbo = interface()->inputPoll(port, Input::Device::SuperScope, 0, (unsigned)Input::SuperScopeID::Turbo);
    if(newturbo && !turbo) {
      turbo = !turbo;  //toggle state
      turbolock = true;
    } else {
      turbolock = false;
    }

    //trigger is a button
    //if turbo is active, trigger is level sensitive; otherwise, it is edge sensitive
    trigger = false;
    bool newtrigger = interface()->inputPoll(port, Input::Device::SuperScope, 0, (unsigned)Input::SuperScopeID::Trigger);
    if(newtrigger && (turbo || !triggerlock)) {
      trigger = true;
      triggerlock = true;
    } else if(!newtrigger) {
      triggerlock = false;
    }

    //cursor is a button; it is always level sensitive
    cursor = interface()->inputPoll(port, Input::Device::SuperScope, 0, (unsigned)Input::SuperScopeID::Cursor);

    //pause is a button; it is always edge sensitive
    pause = false;
    bool newpause = interface()->inputPoll(port, Input::Device::SuperScope, 0, (unsigned)Input::SuperScopeID::Pause);
    if(newpause && !pauselock) {
      pause = true;
      pauselock = true;
    } else if(!newpause) {
      pauselock = false;
    }

    offscreen = (x < 0 || y < 0 || x >= 256 || y >= (ppu.overscan() ? 240 : 225));
  }

  switch(counter++) {
  case 0: return offscreen ? 0 : trigger;
  case 1: return cursor;
  case 2: return turbo;
  case 3: return pause;
  case 4: return 0;
  case 5: return 0;
  case 6: return offscreen;
  case 7: return 0;  //noise (1 = yes)
  }
}

void SuperScope::latch(bool data) {
  if(latched == data) return;
  latched = data;
  counter = 0;
}

void SuperScope::serialize(serializer& s) {
  Processor::serialize(s);
  //Save block.
  unsigned char block[Controller::SaveSize] = {0};
  block[0] = latched ? 1 : 0;
  block[1] = counter;
  block[2] = trigger ? 1 : 0;
  block[3] = cursor ? 1 : 0;
  block[4] = turbo ? 1 : 0;
  block[5] = pause ? 1 : 0;
  block[6] = offscreen ? 1 : 0;
  block[7] = (unsigned short)x >> 8;
  block[8] = (unsigned short)x;
  block[9] = (unsigned short)y >> 8;
  block[10] = (unsigned short)y;

  s.array(block, Controller::SaveSize);
  if(s.mode() == nall::serializer::Load) {
    latched = (block[0] != 0);
    counter = block[1];
    trigger = (block[2] != 0);
    cursor = (block[3] != 0);
    turbo = (block[4] != 0);
    pause = (block[5] != 0);
    offscreen = (block[6] != 0);
    x = (short)(((unsigned short)block[7] << 8) | (unsigned short)block[8]);
    y = (short)(((unsigned short)block[9] << 8) | (unsigned short)block[10]);
  }
}


SuperScope::SuperScope(bool port) : Controller(port) {
  create(Controller::Enter, 21477272);
  latched = 0;
  counter = 0;

  //center cursor onscreen
  x = 256 / 2;
  y = 240 / 2;

  trigger   = false;
  cursor    = false;
  turbo     = false;
  pause     = false;
  offscreen = false;

  turbolock   = false;
  triggerlock = false;
  pauselock   = false;
}

#endif
