module fortplot_test_raster_probe
    !! Pixel probes for rendering tests.
    !!
    !! Images are packed RGB bytes, row-major from the top-left corner, the
    !! layout of fortplot's raster buffer and of a binary PPM (P6) body. PDF
    !! output is rasterised by poppler's pdftoppm, an implementation that
    !! shares no code with fortplot, so PDF probes are independent oracles.
    implicit none

    private
    public :: color_bbox, read_ppm, rasterize_pdf, have_command

contains

    subroutine color_bbox(img, w, h, rgb, tol, x0, x1, y0, y1, count, region)
        !! Bounding box (0-based columns x0..x1, rows y0..y1) and pixel count
        !! of pixels within tol of rgb in every channel. count = 0 when none.
        !! region = [col_lo, col_hi, row_lo, row_hi] limits the search.
        integer(1), intent(in) :: img(:)
        integer, intent(in) :: w, h, rgb(3), tol
        integer, intent(out) :: x0, x1, y0, y1, count
        integer, intent(in), optional :: region(4)
        integer :: row, col, k, c(3), lim(4)
        logical :: hit

        lim = [0, w - 1, 0, h - 1]
        if (present(region)) lim = region
        x0 = huge(1); y0 = huge(1); x1 = -1; y1 = -1; count = 0
        do row = max(0, lim(3)), min(h - 1, lim(4))
            do col = max(0, lim(1)), min(w - 1, lim(2))
                do k = 1, 3
                    c(k) = iand(int(img(3*(row*w + col) + k)), 255)
                end do
                hit = all(abs(c - rgb) <= tol)
                if (.not. hit) cycle
                count = count + 1
                x0 = min(x0, col); x1 = max(x1, col)
                y0 = min(y0, row); y1 = max(y1, row)
            end do
        end do
    end subroutine color_bbox

    subroutine read_ppm(path, img, w, h, ok)
        !! Read a binary PPM (P6, maxval 255) as written by pdftoppm.
        character(len=*), intent(in) :: path
        integer(1), allocatable, intent(out) :: img(:)
        integer, intent(out) :: w, h
        logical, intent(out) :: ok
        integer :: unit, ios, pos, maxval_ppm
        character(len=1) :: ch
        integer :: fields(3), nf, val
        logical :: in_num

        ok = .false.; w = 0; h = 0
        open (newunit=unit, file=path, access='stream', form='unformatted', &
            status='old', iostat=ios)
        if (ios /= 0) return
        read (unit, iostat=ios) ch
        if (ios /= 0 .or. ch /= 'P') then
            close (unit); return
        end if
        read (unit, iostat=ios) ch
        if (ios /= 0 .or. ch /= '6') then
            close (unit); return
        end if
        nf = 0; val = 0; in_num = .false.
        do while (nf < 3)
            read (unit, iostat=ios) ch
            if (ios /= 0) then
                close (unit); return
            end if
            if (ch == '#') then
                do
                    read (unit, iostat=ios) ch
                    if (ios /= 0 .or. ch == achar(10)) exit
                end do
                cycle
            end if
            if (ch >= '0' .and. ch <= '9') then
                val = 10*val + (iachar(ch) - iachar('0'))
                in_num = .true.
            else if (in_num) then
                nf = nf + 1; fields(nf) = val; val = 0; in_num = .false.
            end if
        end do
        ! One whitespace byte follows maxval; it was consumed by the loop.
        w = fields(1); h = fields(2); maxval_ppm = fields(3)
        if (maxval_ppm /= 255 .or. w <= 0 .or. h <= 0) then
            close (unit); return
        end if
        inquire (unit=unit, pos=pos)
        allocate (img(3*w*h))
        read (unit, pos=pos, iostat=ios) img
        close (unit)
        ok = ios == 0
    end subroutine read_ppm

    logical function have_command(name) result(found)
        character(len=*), intent(in) :: name
        integer :: stat
        call execute_command_line('command -v '//name//' >/dev/null 2>&1', &
            exitstat=stat)
        found = stat == 0
    end function have_command

    subroutine rasterize_pdf(pdf, dpi, img, w, h, ok)
        !! Rasterise page 1 of pdf with pdftoppm at dpi (no anti-aliasing).
        character(len=*), intent(in) :: pdf
        integer, intent(in) :: dpi
        integer(1), allocatable, intent(out) :: img(:)
        integer, intent(out) :: w, h
        logical, intent(out) :: ok
        character(len=16) :: dpi_s
        integer :: stat

        ok = .false.; w = 0; h = 0
        write (dpi_s, '(i0)') dpi
        call execute_command_line('pdftoppm -r '//trim(dpi_s)// &
            ' -aa no -aaVector no -singlefile "'//pdf// &
            '" "'//pdf//'"', exitstat=stat)
        if (stat /= 0) return
        call read_ppm(pdf//'.ppm', img, w, h, ok)
    end subroutine rasterize_pdf

end module fortplot_test_raster_probe
