#include "philipsslide.hpp"
#include "philips-isyntax-rs/src/bindings.rs.h"

const std::string PhilipsEngine::_version = PixelEngine::version();

std::unique_ptr<PhilipsEngine> new_() { return std::make_unique<PhilipsEngine>(); }

PhilipsEngine::PhilipsEngine()
    : _render_context(std::make_unique<SoftwareRenderContext>()),
      _render_backend(std::make_unique<SoftwareRenderBackend>(RenderBackend::ImageFormatType::RGB)),
      _pixel_engine(std::make_unique<PixelEngine>(*_render_backend, *_render_context)) {}

std::string const& PhilipsEngine::sdkVersion() const { return _version; }

std::vector<std::string> const& PhilipsEngine::containers() const { return _pixel_engine->containers(); }

std::string const& PhilipsEngine::containerVersion(std::string const& container) const {
    return _pixel_engine->containerVersion(container);
}

std::vector<std::string> const& PhilipsEngine::compressors() const { return _pixel_engine->compressors(); }

std::vector<std::string> const& PhilipsEngine::pixelTransforms() const { return _pixel_engine->pixelTransforms(); }

std::vector<std::string> const& PhilipsEngine::colorspaceTransforms() const {
    return _pixel_engine->colorspaceTransforms();
}

std::vector<std::string> const& PhilipsEngine::qualityPresets() const { return _pixel_engine->qualityPresets(); }

std::vector<std::string> const& PhilipsEngine::supportedFilters() const { return _pixel_engine->supportedFilters(); }

void PhilipsEngine::clientCertificates(std::string const& cert, std::string const& key, std::string const& password) {
    _pixel_engine->clientCertificates(cert, key, password);
}

void PhilipsEngine::certificates(std::string const& path) { _pixel_engine->certificates(path); }

std::unique_ptr<PixelEngine>& PhilipsEngine::inner() { return _pixel_engine; }

std::unique_ptr<Facade> PhilipsEngine::facade(std::string const& input) const {
    return std::make_unique<Facade>(_pixel_engine->operator[](input));
}
// ------------------------------------

// File properties
Facade::Facade(ISyntaxFacade& facade) : _facade(facade) {}

void Facade::open(rust::Str url, rust::Str container, rust::Str cache_filename) const {
    std::string _url(url);
    std::string _container(container);
    std::string _cache_filename(cache_filename);
    _facade.open(_url, _container, std::ios::in | std::ios::binary, _cache_filename);
}

void Facade::close() const { _facade.close(); }

size_t Facade::numImages() const { return _facade.numImages(); }

std::string const& Facade::iSyntaxFileVersion() const { return _facade.iSyntaxFileVersion(); }

std::string const& Facade::id() const { return _facade.id(); }

std::string const& Facade::barcode() const { return _facade.barcode(); }

std::string const& Facade::scannerCalibrationStatus() const { return _facade.scannerCalibrationStatus(); }

std::vector<std::string> const& Facade::softwareVersions() const { return _facade.softwareVersions(); }

std::string const& Facade::derivationDescription() const { return _facade.derivationDescription(); }

std::string const& Facade::acquisitionDateTime() const { return _facade.acquisitionDateTime(); }

std::string const& Facade::manufacturer() const { return _facade.manufacturer(); }

std::string const& Facade::modelName() const { return _facade.modelName(); }

std::string const& Facade::deviceSerialNumber() const { return _facade.deviceSerialNumber(); }

uint16_t Facade::scannerRackNumber() const { return _facade.scannerRackNumber(); }

uint16_t Facade::scannerSlotNumber() const { return _facade.scannerSlotNumber(); }

std::string const& Facade::scannerOperatorId() const { return _facade.scannerOperatorId(); }

uint16_t Facade::scannerRackPriority() const { return _facade.scannerRackPriority(); }

std::vector<std::string> const& Facade::dateOfLastCalibration() const { return _facade.dateOfLastCalibration(); }

std::vector<std::string> const& Facade::timeOfLastCalibration() const { return _facade.timeOfLastCalibration(); }

bool Facade::isPhilips() const { return _facade.isPhilips(); }

bool Facade::isHamamatsu() const { return _facade.isHamamatsu(); }

bool Facade::isUFS() const { return _facade.isUFS(); }

bool Facade::isUFSb() const { return _facade.isUFSb(); }

bool Facade::isUVS() const { return _facade.isUVS(); }

std::unique_ptr<Image> Facade::image(std::string const& image_type) const {
    return std::make_unique<Image>(_facade[image_type]);
}

// ------------------------------------

// Image properties
Image::Image(SubImage& image) : _image(image) {}

std::string const& Image::pixelTransform() const { return _image.pixelTransform(); }

std::string const& Image::qualityPreset() const { return _image.qualityPreset(); }

size_t Image::quality() const { return _image.quality(); }

std::string const& Image::compressor() const { return _image.compressor(); }

std::string const& Image::colorspaceTransform() const { return _image.colorspaceTransform(); }

size_t Image::numTiles() const { return _image.numTiles(); }

std::string const& Image::iccProfile() const { return _image.iccProfile(); }

std::array<double, 9> Image::iccMatrix() const { return _image.iccMatrix(); }

std::vector<uint8_t> const& Image::imageData() const { return _image.imageData(); }

std::string const& Image::lossyImageCompression() const { return _image.lossyImageCompression(); }

double Image::lossyImageCompressionRatio() const { return _image.lossyImageCompressionRatio(); }

std::string const& Image::lossyImageCompressionMethod() const { return _image.lossyImageCompressionMethod(); }

std::string const& Image::colorLinearity() const { return _image.colorLinearity(); }

std::unique_ptr<ImageView> Image::view() const {
    const auto type = _image.imageType();
    SourceView* source_view = &_image.sourceView();
    View* view = source_view;

    if (type == "WSI") {
        const auto bitsStored = view->bitsStored();
        // Enable best quality
        const std::map<std::size_t, std::vector<std::size_t>> truncationLevel{{0, {0, 0, 0}}};
        source_view->truncation(false, false, truncationLevel);

        if (bitsStored > 8) {
            PixelEngine::UserView& user_view = source_view->addChainedView();
            auto matrix = user_view.addFilter("3x3Matrix16"); // Apply ICC profile
            auto icc_matrix = iccMatrix();
            user_view.filterParameterMatrix3x3(matrix, "matrix3x3", icc_matrix);
            user_view.addFilter("Linear16ToSRGB8"); // This Filter converts 9-bit image to 8-bit image.
            view = static_cast<View*>(&user_view);  // Safe because View is the base class of UserView
        }
    }
    return std::make_unique<ImageView>(*view);
}

// ------------------------------------

// View properties
ImageView::ImageView(View& view) : _view(view) {}

DimensionsRange ImageView::dimensionRanges(uint32_t level) const {
    const auto ranges = _view.dimensionRanges(level);
    return DimensionsRange{ranges.at(0).at(0), ranges.at(0).at(1), ranges.at(0).at(2),
                           ranges.at(1).at(0), ranges.at(1).at(1), ranges.at(1).at(2)};
}

std::vector<std::string> const& ImageView::dimensionNames() const { return _view.dimensionNames(); }

std::vector<std::string> const& ImageView::dimensionUnits() const { return _view.dimensionUnits(); }

std::vector<std::string> const& ImageView::dimensionTypes() const { return _view.dimensionTypes(); }

std::vector<double> const& ImageView::scale() const { return _view.scale(); }

std::vector<double> const& ImageView::origin() const { return _view.origin(); }

rust::Vec<Rectangle> ImageView::envelopesAsRects(uint32_t level) const {
    auto envelopes_range = _view.dataEnvelopes(level).asRectangles();

    auto res = rust::Vec<Rectangle>();
    res.reserve(envelopes_range.size());

    for (auto& range : envelopes_range) {
        res.push_back(Rectangle{range.at(0), range.at(1), range.at(2), range.at(3)});
    }
    return res;
}

uint16_t ImageView::bitsAllocated() const { return _view.bitsAllocated(); }

uint16_t ImageView::bitsStored() const { return _view.bitsStored(); }

uint16_t ImageView::highBit() const { return _view.highBit(); }

uint16_t ImageView::pixelRepresentation() const { return _view.pixelRepresentation(); }

uint16_t ImageView::planarConfiguration() const { return _view.planarConfiguration(); }

uint16_t ImageView::samplesPerPixel() const { return _view.samplesPerPixel(); }

uint32_t ImageView::numDerivedLevels() const { return _view.numDerivedLevels(); }

std::vector<size_t> ImageView::pixelSize() const { return _view.pixelSize(); }

void ImageView::read_region(const std::unique_ptr<PhilipsEngine>& engine, const RegionRequest& request,
                            rust::Vec<uint8_t>& buffer, Size& image_size) const {
    const std::vector<std::vector<std::size_t>> view_range{
        {request.roi.start_x, request.roi.end_x, request.roi.start_y, request.roi.end_y, request.level}};
    auto const& envelopes = _view.dataEnvelopes(request.level);
    auto regions = _view.requestRegions(view_range, envelopes, false, {254, 254, 254}, BufferType::RGB);

    auto regions_ready = engine.get()->inner()->waitAny(regions);
    auto region = regions_ready.front();

    // compute image size
    const auto dimension_range = dimensionRanges(request.level);
    const auto& range = region->range();
    image_size.w = 1 + ((range[1] - range[0]) / dimension_range.step_x);
    image_size.h = 1 + ((range[3] - range[2]) / dimension_range.step_y);
    const size_t nb_sub_pixels = image_size.w * image_size.h * 3;

    buffer.reserve(nb_sub_pixels); // RGB pixel
    region->get(buffer.data(), nb_sub_pixels);
}
// ------------------------------------