//# Copyright (C) 1998,1999,2000,2001,2003
//# Associated Universities, Inc. Washington DC, USA.
//#
//# This program is free software; you can redistribute it and/or modify it
//# under the terms of the GNU General Public License as published by the Free
//# Software Foundation; either version 2 of the License, or (at your option)
//# any later version.
//#
//# This program is distributed in the hope that it will be useful, but WITHOUT
//# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
//# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
//# more details.
//#
//# You should have received a copy of the GNU General Public License along
//# with this program; if not, write to the Free Software Foundation, Inc.,
//# 675 Massachusetts Ave, Cambridge, MA 02139, USA.
//#
//# Correspondence concerning AIPS++ should be addressed as follows:
//#        Internet email: aips2-request@nrao.edu.
//#        Postal address: AIPS++ Project Office
//#                        National Radio Astronomy Observatory
//#                        520 Edgemont Road
//#                        Charlottesville, VA 22903-2475 USA
//#
//# $Id: $

#include <imageanalysis/ImageAnalysis/ImageRegridder.h>

#include <imageanalysis/ImageAnalysis/ImageFactory.h>
#include <imageanalysis/ImageAnalysis/ImageMetaData.h>
#include <imageanalysis/ImageAnalysis/SubImageFactory.h>
#include <casacore/images/Images/ImageConcat.h>
#include <casacore/images/Images/ImageRegrid.h>
#include <casacore/scimath/Mathematics/Geometry.h>

#include <casacore/casa/BasicSL/STLIO.h>
#include <memory>

namespace casa {

template<class T> const String  ImageRegridder<T>::_class = "ImageRegridder";

template<class T> ImageRegridder<T>::ImageRegridder(
    const SPCIIT image, const casacore::Record *const regionRec,
    const casacore::String& maskInp, const casacore::String& outname,
    casacore::Bool overwrite, const casacore::CoordinateSystem& csysTo,
    const casacore::IPosition& axes, const casacore::IPosition& shape
) : ImageRegridderBase<T>(
        image, regionRec, maskInp, outname,
        overwrite, csysTo, axes, shape
    ), _debug(0) {}

template<class T> ImageRegridder<T>::ImageRegridder(
    const SPCIIT image, const casacore::String& outname,
    const SPCIIT templateIm, const casacore::IPosition& axes,
    const casacore::Record *const regionRec, const casacore::String& maskInp,
    casacore::Bool overwrite, const casacore::IPosition& shape
)  : ImageRegridderBase<T>(
        image, regionRec, maskInp, outname, overwrite,
        templateIm->coordinates(), axes, shape
    ),
    _debug(0) {}

template<class T> ImageRegridder<T>::~ImageRegridder() {}

template<class T> SPIIT ImageRegridder<T>::regrid() const {
    _subimage = SubImageFactory<T>::createImage(
        *this->_getImage(), "", *this->_getRegion(), this->_getMask(),
        this->_getDropDegen(), false, false, this->_getStretch()
    );
    auto regridByVel = false;
    const auto axes = this->_getAxes();
    auto hasMultipleBeams = this->_getImage()->imageInfo().hasMultipleBeams();
    const auto& csys = this->_getImage()->coordinates();
    if (
        (this->_getSpecAsVelocity() || hasMultipleBeams)
        && csys.hasSpectralAxis()
        && this->_getTemplateCoords().hasSpectralAxis()
    ) {
        auto inputSpecAxis = csys.spectralAxisNumber(false);
        auto isInputSpecDegen = _subimage->shape()[inputSpecAxis] == 1;
        if (axes.empty()) {
            ThrowIf(
                hasMultipleBeams,
                "An image with multiple beams cannot be regridded along the "
                "spectral axis. You may wish to convolve all channels to a "
                "common resolution and retry"
            );
            if (! isInputSpecDegen && this->_getSpecAsVelocity()) {
                regridByVel = true;
            }
        }
        else {
            auto specAxis = csys.spectralAxisNumber();
            for (uInt i=0; i<axes.size(); ++i) {
                if (axes[i] == specAxis) {
                    ThrowIf(
                        hasMultipleBeams,
                        "An image with multiple beams cannot be regridded "
                        "along the spectral axis. You may wish to convolve all "
                        "channels to a common resolution and retry"
                    );
                    if (! isInputSpecDegen && this->_getSpecAsVelocity()) {
                        regridByVel = true;
                    }
                    break;
                }
            }
        }
    }
    auto workIm = regridByVel ? this->_regridByVelocity() : this->_regrid();
    return this->_prepareOutputImage(*workIm);
}

template<class T> SPIIT ImageRegridder<T>::_regrid() const {
    if (! _subimage) {
        // for when this method is called directly by regridByVelocity
        _subimage = SubImageFactory<T>::createImage(
            *this->_getImage(), "", *this->_getRegion(), this->_getMask(),
            this->_getDropDegen(), false, false, this->_getStretch()
        );
    }
    *this->_getLog() << LogOrigin(_class, __func__);
    ThrowIf(
        ImageMask::isAllMaskFalse(*_subimage),
        "All selected pixels are masked"
    );
    const auto csysFrom = _subimage->coordinates();
    CoordinateSystem csysTo = this->_getTemplateCoords();
    csysTo.setObsInfo(csysFrom.obsInfo());
    std::set<Coordinate::Type> coordsToRegrid;
    auto csys = ImageRegrid<T>::makeCoordinateSystem(
        *this->_getLog(), coordsToRegrid, csysTo, csysFrom, this->_getAxes(),
        _subimage->shape(), false
    );
    ThrowIf(
        csys.nPixelAxes() != this->_getShape().size(),
        "The number of pixel axes in the output shape and Coordinate System "
        "must be the same. Shape has size "
        + casacore::String::toString(this->_getShape().size()) + ". Output "
        "coordinate system has "
        + casacore::String::toString(csys.nPixelAxes()) + " axes"
    );
    this->_checkOutputShape(*_subimage, coordsToRegrid);
    SPIIT workIm(new TempImage<T>(this->_getKludgedShape(), csys));
    ImageUtilities::copyMiscellaneous(*workIm, *_subimage);
    String maskName("");
    ImageMaskAttacher::makeMask(
        *workIm, maskName, true, true, *this->_getLog(), true
    );
    ThrowIf (
        ! this->_doImagesOverlap(_subimage, workIm),
        "There is no overlap between the (region chosen in) the input image"
        " and the output image with respect to the axes being regridded."
    );
    if (
        coordsToRegrid.find(Coordinate::SPECTRAL) != coordsToRegrid.end()
        && fabs(csys.spectralCoordinate().increment()[0])
            > fabs(csysFrom.spectralCoordinate().increment()[0])
    ) {
        *this->_getLog() << LogOrigin(getClass(), __func__) << LogIO::WARN
            << "Warning: template/imagename relative channel size is "
            << fabs(
                csys.spectralCoordinate().increment()[0]
                /csysFrom.spectralCoordinate().increment()[0]
            ) << LogIO::POST;
        *this->_getLog() << LogOrigin(getClass(), __func__)
            << LogIO::WARN << "imregrid/ia.regrid() interpolates over spectral "
            << "channels and does not average channels together. Noise in your "
            << "resulting image will be the noise in the original individual "
            << "channels, not the averaged channel noise. To average output "
            << "channels together, use specsmooth (or ia.boxcar() or "
            << "ia.hanning() to smooth the spectral axis of your input cube to "
            << "close to desired resolution and use imregrid/ia.regrid() to "
            << "regrid it to the desired spectral coordinate grid."
            << LogIO::POST;
    }
    ImageRegrid<T> ir;
    ir.showDebugInfo(_debug);
    ir.disableReferenceConversions(! this->_getDoRefChange());
    ir.regrid(
        *workIm, this->_getMethod(), this->_getAxes(), *_subimage,
        this->_getReplicate(), this->_getDecimate(), true,
        this->_getForceRegrid()
    );
    if (! this->_getOutputStokes().empty()) {
        workIm = this->_decimateStokes(workIm);
    }
    ThrowIf(
        workIm->hasPixelMask() && ImageMask::isAllMaskFalse(*workIm),
        "All output pixels are masked"
        + casacore::String(
            this->_getDecimate() > 1 && this->_regriddingDirectionAxes()
            ? ". You might want to try decreasing the value of decimate if you "
                "are regridding direction axes"
            : ""
        )
    );
    if (this->_getNReplicatedChans() > 1) {
        // spectral channel needs to be replicated _nReplicatedChans times,
        // and spectral coordinate of the template needs to be copied to the
        // output.
        IPosition finalShape = this->_getKludgedShape();
        Int specAxisNumber = workIm->coordinates().spectralAxisNumber(false);
        auto fillerPixels = workIm->get();
        const auto fillerMask = workIm->pixelMask().get();
        finalShape[specAxisNumber] = this->_getNReplicatedChans();
        SPIIT replicatedIm(new casacore::TempImage<T>(finalShape, csys));
        std::dynamic_pointer_cast<casacore::TempImage<T>>(replicatedIm)->attachMask(
            casacore::ArrayLattice<Bool>(finalShape)
        );
        auto& pixelMask = replicatedIm->pixelMask();
        auto n = this->_getNReplicatedChans();
        IPosition where(finalShape.size(), 0);
        for (uInt i=0; i<n; ++i) {
            where[specAxisNumber] = i;
            replicatedIm->putSlice(fillerPixels, where);
            pixelMask.putSlice(fillerMask, where);
        }
        auto spTo = this->_getTemplateCoords().spectralCoordinate();
        auto csysFinal = replicatedIm->coordinates();
        csysFinal.replaceCoordinate(spTo, csysFinal.spectralCoordinateNumber());
        replicatedIm->setCoordinateInfo(csysFinal);
        workIm = replicatedIm;
    }
    return workIm;
}

template<class T> SPIIT ImageRegridder<T>::_decimateStokes(SPIIT workIm) const {
    ImageMetaData<T> md(workIm);
    if (this->_getOutputStokes().size() >= md.nStokes()) {
        return workIm;
    }
    CasacRegionManager rm(workIm->coordinates());
    casacore::String diagnostics;
    casacore::uInt nSelectedChannels = 0;
    if (this->_getOutputStokes().size() == 1) {
        auto stokes = this->_getOutputStokes()[0];
        auto region = rm.fromBCS(
            diagnostics, nSelectedChannels, stokes,
            "", CasacRegionManager::USE_FIRST_STOKES,
            "", workIm->shape()
        ).toRecord("");
        return SubImageFactory<T>::createImage(
            *workIm, "", region, "", false, false, false, false
        );
    }
    else {
        // Only include the wanted stokes
        std::shared_ptr<casacore::ImageConcat<T> > concat(
            new casacore::ImageConcat<T>(
                workIm->coordinates().polarizationAxisNumber(false)
            )
        );
        for(casacore::String stokes: this->_getOutputStokes()) {
            auto region = rm.fromBCS(
                diagnostics, nSelectedChannels, stokes,
                "", CasacRegionManager::USE_FIRST_STOKES,
                "", workIm->shape()
            ).toRecord("");
            concat->setImage(
                *SubImageFactory<T>::createImage(
                    *workIm, "", region, "", false, false, false, false
                ), true
            );
        }
        return concat;
    }
}

template<class T> void ImageRegridder<T>::_checkOutputShape(
    const casacore::SubImage<T>& subImage,
    const std::set<casacore::Coordinate::Type>& coordsToRegrid
) const {
    const auto& csysFrom = subImage.coordinates();
    std::set<casacore::Coordinate::Type> coordsNotToRegrid;
    auto nCoordinates = csysFrom.nCoordinates();
    auto inputShape = subImage.shape();
    auto axes = this->_getAxes();
    auto outputAxisOrder = axes;
    for (uInt i=axes.size(); i<this->_getKludgedShape().size(); ++i) {
        outputAxisOrder.append(casacore::IPosition(1, i));
    }
    const auto coordsToRegridEnd = coordsToRegrid.end();
    for (uInt i=0; i<nCoordinates; ++i) {
        const auto coordType = csysFrom.coordinate(i).type();
        if (coordsToRegrid.find(coordType) == coordsToRegridEnd) {
            auto coordAxes = csysFrom.worldAxes(i);
            for(casacore::uInt oldAxis: coordAxes) {
                casacore::uInt count = 0;
                for(casacore::uInt newAxis: outputAxisOrder) {
                    ThrowIf(
                        newAxis == oldAxis
                        && inputShape[oldAxis]
                            != this->_getKludgedShape()[count],
                        "Input axis " + casacore::String::toString(oldAxis)
                        + " (coordinate type "
                        + casacore::Coordinate::typeToString(coordType)
                        + "), which will not be regridded and corresponds to"
                        + "output axis casacore::String::toString(newAxis), "
                        + "has length "
                        + casacore::String::toString(inputShape[oldAxis])
                        + " where as the specified length of the corresponding "
                        + "output axis is "
                        + casacore::String::toString(
                            this->_getKludgedShape()[count]
                        ) + ". If a coordinate is not regridded, its input and "
                        + "output axes must have the same length."
                    );
                    ++count;
                }
            }
        }
    }
}

template<class T> SPIIT ImageRegridder<T>::_regridByVelocity() const {
    const auto csysTo = this->_getTemplateCoords();
    const auto specCoordTo = csysTo.spectralCoordinate();
    const auto specCoordFrom
        = this->_getImage()->coordinates().spectralCoordinate();
    ThrowIf(
        specCoordTo.frequencySystem(true)
        != specCoordFrom.frequencySystem(true),
        "Image to be regridded has different frequency system from template "
        "coordinate system."
    );
    ThrowIf(
        specCoordTo.restFrequency() == 0,
        "Template spectral coordinate rest frequency is 0, "
        "so cannot regrid by velocity."
    );
    ThrowIf(
        specCoordFrom.restFrequency() == 0,
        "Input image spectral coordinate rest frequency is 0, so cannot regrid "
        "by velocity."
    );
    std::unique_ptr<casacore::CoordinateSystem> csys(
        dynamic_cast<casacore::CoordinateSystem *>(csysTo.clone())
    );
    auto templateSpecCoord = csys->spectralCoordinate();
    std::unique_ptr<casacore::CoordinateSystem> coordClone(
        dynamic_cast<casacore::CoordinateSystem *>(
            _subimage->coordinates().clone()
        )
    );
    auto newSpecCoord = coordClone->spectralCoordinate();
    casacore::Double newVelRefVal = 0;
    std::pair<casacore::Double, casacore::Double> toVelLimits;
    auto inSpecAxis = coordClone->spectralAxisNumber(false);
    casacore::Double newVelInc = 0.0;
    for (casacore::uInt i=0; i<2; ++i) {
        // i == 0 => csysTo, i == 1 => csysFrom
        auto *cs = i == 0 ? csys.get() : coordClone.get();
        // create and replace the coordinate system's spectral coordinate with
        // a linear coordinate which describes the velocity axis. In this way
        // we can regrid by velocity.
        Int specCoordNum = cs->spectralCoordinateNumber();
        auto specCoord = cs->spectralCoordinate();
        if (
            specCoord.frequencySystem(false) != specCoord.frequencySystem(true)
        ) {
            // the underlying conversion system is different from the overlying
            // one, so this is pretty confusing. We want the underlying one also
            // be the overlying one before we regrid.
            casacore::Vector<casacore::Double> newRefVal;
            auto newRefPix = specCoord.referencePixel()[0];
            specCoord.toWorld(
                newRefVal,
                casacore::Vector<casacore::Double>(1, newRefPix)
            );
            casacore::Vector<casacore::Double> newVal;
            specCoord.toWorld(
                newVal, casacore::Vector<casacore::Double>(1, newRefPix+1)
            );
            specCoord = SpectralCoordinate(
                specCoord.frequencySystem(true), newRefVal[0],
                (newVal[0] - newRefVal[0]), newRefPix, specCoord.restFrequency()
            );
            if (cs == coordClone.get()) {
                newSpecCoord = specCoord;
            }
        }
        casacore::Double freqRefVal = specCoord.referenceValue()[0];
        casacore::Double velRefVal;
        ThrowIf(
            ! specCoord.frequencyToVelocity(velRefVal, freqRefVal),
            "Unable to determine reference velocity"
        );
        casacore::Double vel0 = 0;
        casacore::Double vel1 = 0;
        ThrowIf(
            ! specCoord.pixelToVelocity(vel0, 0.0)
            || ! specCoord.pixelToVelocity(vel1, 1.0),
            "Unable to determine velocity increment"
        );
        if (i == 0) {
            toVelLimits.first = vel0;
            specCoord.pixelToVelocity(
                toVelLimits.second, this->_getShape()[inSpecAxis] - 1
            );
            if (toVelLimits.first > toVelLimits.second) {
                std::swap(toVelLimits.first, toVelLimits.second);
            }
        }
        if (i == 1) {
            std::pair<casacore::Double, casacore::Double> fromVelLimits;
            specCoord.pixelToVelocity(fromVelLimits.first, 0);
            specCoord.pixelToVelocity(
                fromVelLimits.second, _subimage->shape()[inSpecAxis] - 1
            );
            if (fromVelLimits.first > fromVelLimits.second) {
                std::swap(fromVelLimits.first, fromVelLimits.second);
            }
            ThrowIf(
                (
                    fromVelLimits.first > toVelLimits.second
                    && ! casacore::near(fromVelLimits.first, toVelLimits.second)
                )
                || (
                    fromVelLimits.second < toVelLimits.first
                    && ! casacore::near(fromVelLimits.second, toVelLimits.first)
                ),
                "Request to regrid by velocity, but input and output velocity "
                "coordinates do not overlap"
            );
        }
        casacore::Matrix<casacore::Double> pc(1, 1, 0);
        pc.diagonal() = 1.0;
        casacore::LinearCoordinate lin(
            casacore::Vector<casacore::String>(1, "velocity"),
            specCoord.worldAxisUnits(),
            casacore::Vector<casacore::Double>(1, velRefVal),
            casacore::Vector<casacore::Double>(1, vel1 - vel0),
            pc, specCoord.referencePixel()
        );
        // don't bother checking the return value of the replaceCoordinate call
        // as it will always be false because the replaced and replacement
        // coordinate types differ, but the coordinate will be replaced anyway.
        // Yes I find it nonintuitive and am scratching my head regarding the
        // usefulness of the return value as well. Just check that replacement
        // coordinate is equal to the coordinate we expect.
        cs->replaceCoordinate(lin, specCoordNum);
        ThrowIf(
            ! lin.near(cs->linearCoordinate(specCoordNum)),
            "Replacement linear coordinate does not match "
            "original linear coordinate because "
            + lin.errorMessage()
        );
        if (cs == csys.get()) {
            newVelRefVal = velRefVal;
            newVelInc = vel1 - vel0;
        }
        else {
            _subimage->setCoordinateInfo(*cs);
        }
    }
    // do not pass the region or the mask, the maskedClone has already had the
    // region and mask applied
    ImageRegridder regridder(
        _subimage, 0, "", this->_getOutname(), this->_getOverwrite(), *csys,
        this->_getAxes(), this->_getShape()
    );
    regridder.setConfiguration(*this);
    auto outImage = regridder._regrid();
    // replace the temporary linear coordinate with the saved spectral
    // coordinate
    std::unique_ptr<casacore::CoordinateSystem> newCoords(
        dynamic_cast<casacore::CoordinateSystem *>(
            outImage->coordinates().clone()
        )
    );
    // make frequencies correct
    casacore::Double newRefFreq;
    ThrowIf(
        ! newSpecCoord.velocityToFrequency(
            newRefFreq, newVelRefVal
        ),
        "Unable to determine new reference frequency"
    );
    // get the new frequency increment
    casacore::Double newFreq;
    ThrowIf (
        ! newSpecCoord.velocityToFrequency(newFreq, newVelRefVal + newVelInc),
        "Unable to determine new frequency increment"
    );
    ThrowIf (
        ! newSpecCoord.setReferenceValue(Vector<Double>(1, newRefFreq)),
        "Unable to set new reference frequency"
    );
    ThrowIf (
        ! newSpecCoord.setIncrement((Vector<Double>(1, newFreq - newRefFreq))),
        "Unable to set new frequency increment"
    );
    ThrowIf(
        ! newSpecCoord.setReferencePixel(
            templateSpecCoord.referencePixel()
        ), "Unable to set new reference pixel"
    );
    ThrowIf(
        ! newCoords->replaceCoordinate(
            newSpecCoord,
            _subimage->coordinates().linearCoordinateNumber())
        && ! newSpecCoord.near(newCoords->spectralCoordinate()),
        "Unable to replace coordinate for velocity regridding"
    );
    outImage->setCoordinateInfo(*newCoords);
    return outImage;
}

template<class T> Bool ImageRegridder<T>::_doImagesOverlap(
    SPCIIT image0, SPCIIT image1
) const {
    const auto csys0 = image0->coordinates();
    const auto csys1 = image1->coordinates();
    auto shape0 = image0->shape();
    auto shape1 = image1->shape();
    ImageMetaData<T> md0(image0);
    ImageMetaData<T> md1(image1);
    Bool overlap = false;
    if (
        csys0.hasDirectionCoordinate()
        && csys1.hasDirectionCoordinate()
    ) {
        const auto dc0 = csys0.directionCoordinate();
        auto dc1 = csys1.directionCoordinate();
        auto dirShape0 = md0.directionShape();
        auto dirShape1 = md1.directionShape();
        auto inc0 = dc0.increment();
        auto inc1 = dc1.increment();
        auto units0 = dc0.worldAxisUnits();
        auto units1 = dc1.worldAxisUnits();
        auto reallyBig = false;
        casacore::Quantity extent;
        casacore::Quantity oneDeg(1, "deg");
        for (uInt i=0; i<2; ++i) {
            extent = casacore::Quantity(dirShape0[i]*abs(inc0[i]), units0[i]);
            if (extent > oneDeg) {
                reallyBig = true;
                break;
            }
            extent = casacore::Quantity(dirShape1[i]*abs(inc1[i]), units1[i]);
            if (extent > oneDeg) {
                reallyBig = true;
                break;
            }
        }
        if (reallyBig) {
            *this->_getLog() << LogOrigin("ImageRegridder", __func__)
                << LogIO::WARN << "At least one of the images "
                << "exceeds one degree on at one side, not checking "
                << "for direction plane overlap." << LogIO::POST;
        }
        else {
            auto sameFrame = dc0.directionType(true) == dc1.directionType(true);
            if (!sameFrame) {
                dc1.setReferenceConversion(dc0.directionType(true));
            }
            auto corners0 = _getDirectionCorners(dc0, dirShape0);
            auto corners1 = _getDirectionCorners(dc1, dirShape1);
            overlap = _doRectanglesIntersect(corners0, corners1);
            if (! overlap) {
                return false;
            }
        }
    }
    if (
        csys0.hasSpectralAxis()
        && csys1.hasSpectralAxis()
    ) {
        const auto sp0 = csys0.spectralCoordinate();
        const auto sp1 = csys1.spectralCoordinate();
        auto nChan0 = md0.nChannels();
        auto nChan1 = md1.nChannels();
        casacore::Double world;
        sp0.toWorld(world, 0);
        auto end00 = world;
        sp0.toWorld(world, nChan0 - 1);
        auto end01 = world;
        sp1.toWorld(world, 0);
        auto end10 = world;
        sp1.toWorld(world, nChan1 - 1);
        auto end11 = world;
        auto minmax0 = minmax(end00, end01);
        auto minmax1 = minmax(end10, end11);
        if (
            (
                minmax0.second < minmax1.first
                && ! casacore::near(minmax0.second, minmax1.first)
            )
            || (
                minmax1.second < minmax0.first
                && ! casacore::near(minmax1.second, minmax0.first)
            )
        ) {
            return false;
        }
    }
    return true;
}

template<class T>
casacore::Vector<std::pair<casacore::Double, casacore::Double>>
ImageRegridder<T>::_getDirectionCorners(
    const casacore::DirectionCoordinate& dc,
    const casacore::IPosition& directionShape
) {
    casacore::Vector<casacore::Double> world;
    casacore::Vector<casacore::Double> pixel(2, 0);
    auto units = dc.worldAxisUnits();
    dc.toWorld(world, pixel);
    casacore::Vector<std::pair<casacore::Double, casacore::Double> > corners(4);
    for (uInt i=0; i<4; ++i) {
        switch(i) {
        case 0:
            // blcx, blcy
            pixel.set(0);
            break;
        case 1:
            // trcx, blcy
            pixel[0] = directionShape[0];
            pixel[1] = 0;
            break;
        case 2:
            // trcx, trcy
            pixel[0] = directionShape[0];
            pixel[1] = directionShape[1];
            break;
        case 3:
            // blcx, trcy
            pixel[0] = 0;
            pixel[1] = directionShape[1];
            break;
        default:
            ThrowCc("Logic Error: This code should never be reached");
            break;
        }
        dc.toWorld(world, pixel);
        auto x = casacore::Quantity(world[0], units[0]).getValue("rad");
        if (fabs(x) >= casacore::C::_2pi) {
            // resolve 2pi ambiguities for x (longitude) coordinate
            x = fmod(x, C::_2pi);
        }
        if (x < 0) {
            // ensure longitude is > 0
            x += casacore::C::_2pi;
        }
        corners[i].first = x;
        corners[i].second = casacore::Quantity(
            world[1], units[1]
        ).getValue("rad");
    }
    auto diff0 = abs(corners[1].first - corners[0].first);  
    auto diff1 = abs(corners[1].first - C::_2pi - corners[0].first);
    if (diff0 > diff1) {
        // image straddles longitude 0 and we have to rationalize the
        // longitude trc coordinate
        corners[1].first -= casacore::C::_2pi;
        corners[2].first -= casacore::C::_2pi;
    }
    return corners;
}

template<class T> casacore::Bool ImageRegridder<T>::_doRectanglesIntersect(
    const casacore::Vector<
        std::pair<casacore::Double, casacore::Double>
    >& corners0,
    const casacore::Vector<
        std::pair<casacore::Double, casacore::Double>
    >& corners1
) {
    auto minx0 = corners0[0].first;
    auto maxx0 = minx0;
    auto miny0 = corners0[0].second;
    auto maxy0 = miny0;
    auto minx1 = corners1[0].first;
    auto maxx1 = minx1;
    auto miny1 = corners1[0].second;
    auto maxy1 = miny1;
    for (casacore::uInt i=1; i<4; ++i) {
        minx0 = min(minx0, corners0[i].first);
        maxx0 = max(maxx0, corners0[i].first);
        miny0 = min(miny0, corners0[i].second);
        maxy0 = max(maxy0, corners0[i].second);

        minx1 = min(minx1, corners1[i].first);
        maxx1 = max(maxx1, corners1[i].first);
        miny1 = min(miny1, corners1[i].second);
        maxy1 = max(maxy1, corners1[i].second);
    }
    if (
        minx0 > maxx1 || maxx0 < minx1
        || miny0 > maxy1 || maxy0 < miny1
    ) {
        // bounds check shows images do not intersect
        return false;
    }
    else if (
        (minx0 >= minx1 && maxx0 <= maxx1 && miny0 >= miny1 && maxy0 <= maxy1)
        || (minx0 < minx1 && maxx0 > maxx1 && miny0 < miny1 && maxy0 > maxy1)
    ) {
        // one image lies completely inside the other
        return true;
    }
    else {
        // determine intersection
        // FIXME There are more efficient algorithms. See eg
        // the Shamos-Hoey Algorithm
        // http://geomalgorithms.com/a09-_intersect-3.html#Pseudo-Code%3a%20S-H
        for (casacore::uInt i=0; i<4; ++i) {
            casacore::Vector<casacore::Double> start0(2, corners0[i].first);
            start0[1] = corners0[i].second;
            casacore::Vector<casacore::Double> end0(
                2,
                i == 3 ? corners0[0].first
                    : corners0[i+1].first
            );
            end0[1] = i == 3 ? corners0[0].second : corners0[i+1].second;

            for (uInt j=0; j<4; ++j) {
                casacore::Vector<casacore::Double> start1(2, corners1[j].first);
                start1[1] = corners1[j].second;
                casacore::Vector<casacore::Double> end1(
                    2,
                    j == 3 ? corners1[0].first
                        : corners1[j+1].first
                );
                end1[1] = j == 3 ? corners1[0].second : corners1[j+1].second;
                if (
                    casacore::Geometry::doLineSegmentsIntersect(
                        start0[0], start0[1], end0[0], end0[1],
                        start1[0], start1[1], end1[0], end1[1]
                    )
                ) {
                    return true;
                    break;
                }
            }
        }
    }
    return false;
}

}