#include <X11/Xlib.h>
#include <string>
#define STB_IMAGE_IMPLEMENTATION
#include "../dep/stb_image.h"

#include <stdexcept>

#include "image.hh"

namespace uw {
  Image::Image(Screen &screen, const std::string imgPath) : uwScreen{ screen } {
    if (imgPath.empty()) throw std::runtime_error("画像パスが空です。");

    data = stbi_load(imgPath.c_str(), &width, &height, &chan, 4);
    if (!data) throw std::runtime_error("画像の読み込みに失敗: " + imgPath);

    imageExtent.width = width;
    imageExtent.height = height;
    imagePath = imgPath;

    v = DefaultVisual(screen.getDisplay(), screen.getScreen());

    createDepth();
    createPixmap();
    createImage();
    createGC();
  }

  Image::~Image() {
    if (gc) XFreeGC(uwScreen.getDisplay(), gc);
    if (pm) XFreePixmap(uwScreen.getDisplay(), pm);
    if (image) XFree(image);
    if (data) stbi_image_free(data);
  }

  void Image::createDepth() {
    depth = DefaultDepth(uwScreen.getDisplay(), uwScreen.getScreen());

    if (depth != 24 && depth != 32) {
      throw std::runtime_error(
          "「" + std::to_string(depth) + "」の様な視覚の深さは未対応です。");
    }

    imageExtent.depth = depth;
  }

  void Image::createPixmap() {
    pm = XCreatePixmap(uwScreen.getDisplay(), uwScreen.getWindow(), width, height,
                       depth);
  }

  void Image::createImage() {
    if (depth == 24) {
      std::vector<uchar> cd(data, data + (width * height * 4));
      cdata = cd;

      for (int i = 0; i < width * height; i++) {
        cdata[i * 4 + 0] = data[i * 4 + 2];
        cdata[i * 4 + 1] = data[i * 4 + 1];
        cdata[i * 4 + 2] = data[i * 4 + 0];
        cdata[i * 4 + 3] = data[i * 4 + 3];
      }

      image = XCreateImage(uwScreen.getDisplay(), v, depth, ZPixmap, 0,
          (char *)cdata.data(), width, height, 32, 0);
    } else {
      image = XCreateImage(uwScreen.getDisplay(), v, depth, ZPixmap, 0,
          (char *)data, width, height, 32, 0);
    }

    if (!image) throw std::runtime_error("XImageの作成に失敗。");
  }

  void Image::createGC() {
    gc = XCreateGC(uwScreen.getDisplay(), pm, 0, nullptr);
    if (!gc) throw std::runtime_error("GCの作成に失敗。");
  }
}