//request from emulation core to load non-volatile media folder
auto Program::loadRequest(unsigned id, string name, string type, bool required) -> void {
  string location = BrowserDialog()
  .setTitle({"Load ", name})
  .setPath({config->library.location, name})
  .setFilters({string{name, "|*.", type}})
  .openFolder();
  if(!directory::exists(location)) return;

  mediaPaths(id) = location;
  folderPaths.append(location);
  emulator->load(id);
}

//request from emulation core to load non-volatile media file
auto Program::loadRequest(unsigned id, string filename, bool required) -> void {
  string pathname = mediaPaths(emulator->group(id));
  string location = {pathname, filename};
  if(file::exists(location)) {
    mmapstream stream{location};
    return emulator->load(id, stream);
  }
  if(filename == "manifest.bml") {
    string manifest;
    if(auto fp = popen(string{"icarus -m \"", pathname, "\""}, "r")) {
      while(true) {
        auto byte = fgetc(fp);
        if(byte == EOF) break;
        manifest.append((char)byte);
      }
      pclose(fp);
    }
    if(manifest) {
      memorystream stream{manifest.binary(), manifest.size()};
      return emulator->load(id, stream);
    }
  }
  if(required) MessageDialog().setTitle("higan").setText({
    "Missing required file: ", nall::filename(location), "\n\n",
    "From location:\n", nall::pathname(location)
  }).error();
}

//request from emulation core to save non-volatile media file
auto Program::saveRequest(unsigned id, string filename) -> void {
  string pathname = mediaPaths(emulator->group(id));
  string location = {pathname, filename};
  filestream stream{location, file::mode::write};
  return emulator->save(id, stream);
}

auto Program::videoColor(unsigned source, uint16 a, uint16 r, uint16 g, uint16 b) -> uint32 {
  if(config->video.saturation != 100) {
    uint16 grayscale = uclamp<16>((r + g + b) / 3);
    double saturation = config->video.saturation * 0.01;
    double inverse = max(0.0, 1.0 - saturation);
    r = uclamp<16>(r * saturation + grayscale * inverse);
    g = uclamp<16>(g * saturation + grayscale * inverse);
    b = uclamp<16>(b * saturation + grayscale * inverse);
  }

  if(config->video.gamma != 100) {
    double exponent = config->video.gamma * 0.01;
    double reciprocal = 1.0 / 32767.0;
    r = r > 32767 ? r : 32767 * pow(r * reciprocal, exponent);
    g = g > 32767 ? g : 32767 * pow(g * reciprocal, exponent);
    b = b > 32767 ? b : 32767 * pow(b * reciprocal, exponent);
  }

  if(config->video.luminance != 100) {
    double luminance = config->video.luminance * 0.01;
    r = r * luminance;
    g = g * luminance;
    b = b * luminance;
  }

  a >>= 8;
  r >>= 8;
  g >>= 8;
  b >>= 8;
  return a << 24 | r << 16 | g << 8 | b << 0;
}

auto Program::videoRefresh(const uint32* palette, const uint32* data, unsigned pitch, unsigned width, unsigned height) -> void {
  uint32* output;
  unsigned length;

  if(video->lock(output, length, width, height)) {
    pitch >>= 2, length >>= 2;

    for(auto y : range(height)) {
      const uint32* sp = data + y * pitch;
      uint32* dp = output + y * length;
      for(auto x : range(width)) {
        *dp++ = palette[*sp++];
      }
    }

    if(emulator->information.overscan && config->video.overscan.mask) {
      unsigned h = config->video.overscan.horizontal;
      unsigned v = config->video.overscan.vertical;

      if(h) for(auto y : range(height)) {
        memory::fill(output + y * length, 4 * h);
        memory::fill(output + y * length + (width - h), 4 * h);
      }

      if(v) for(auto y : range(v)) {
        memory::fill(output + y * length, 4 * width);
        memory::fill(output + (height - 1 - y) * length, 4 * width);
      }
    }

    video->unlock();
    video->refresh();
  }

  static unsigned frameCounter = 0;
  static time_t previous, current;
  frameCounter++;

  time(&current);
  if(current != previous) {
    previous = current;
    statusText = {"FPS: ", frameCounter};
    frameCounter = 0;
  }
}

auto Program::audioSample(int16 lsample, int16 rsample) -> void {
  signed samples[] = {lsample, rsample};
  dsp.sample(samples);
  while(dsp.pending()) {
    dsp.read(samples);
    audio->sample(samples[0], samples[1]);
  }
}

auto Program::inputPoll(unsigned port, unsigned device, unsigned input) -> int16 {
  if(presentation->focused()) {
    auto guid = emulator->port[port].device[device].input[input].guid;
    auto mapping = (InputMapping*)guid;
    if(mapping) return mapping->poll();
  }
  return 0;
}

auto Program::inputRumble(unsigned port, unsigned device, unsigned input, bool enable) -> void {
  if(presentation->focused() || !enable) {
    auto guid = emulator->port[port].device[device].input[input].guid;
    auto mapping = (InputMapping*)guid;
    if(mapping) return mapping->rumble(enable);
  }
}

auto Program::dipSettings(const Markup::Node& node) -> unsigned {
}

auto Program::path(unsigned group) -> string {
  return mediaPaths(group);
}

auto Program::notify(string text) -> void {
}
