/**
 * Copy a patch from another sampler with a margin.
 * This is useful for texture rendering where filtering can cause bleeding along the edges of the patch.
 * @param {Sampler2D} destination
 * @param {Sampler2D} source where to copy from
 * @param {Number} sourceX where to start reading from, X coordinate
 * @param {Number} sourceY where to start reading from, X coordinate
 * @param {Number} destinationX where to start writing to, X coordinate
 * @param {Number} destinationY where to start writing to, X coordinate
 * @param {Number} width size of the patch that is to be copied
 * @param {Number} height size of the patch that is to be copied
 * @param {Number} marginLeft
 * @param {Number} marginRight
 * @param {Number} marginTop
 * @param {Number} marginBottom
 */
export function sampler2d_copy_with_margins(
    destination,
    source,
    sourceX, sourceY,
    destinationX, destinationY,
    width, height,
    marginLeft,
    marginRight,
    marginTop,
    marginBottom
) {
    const dItemSize = destination.itemSize;
    const sItemSize = source.itemSize;
    const _itemSize = Math.min(dItemSize, sItemSize);


    const dRowSize = dItemSize * destination.width;
    const sRowSize = sItemSize * source.width;

    const sData = source.data;
    const dData = destination.data;

    let x, y, i;

    let xMax, yMax;

    let dA, sA, dOffset, sOffset;
    //Write top-left corner
    sOffset = sourceY * sRowSize + sourceX * dItemSize;
    for (y = Math.max(0, destinationY - marginTop), yMax = destinationY; y < yMax; y++) {
        dA = y * dRowSize;

        for (x = Math.max(0, destinationX - marginLeft), xMax = destinationX; x < xMax; x++) {

            dOffset = dA + x * dItemSize;

            for (i = 0; i < _itemSize; i++) {
                dData[dOffset + i] = sData[sOffset + i];
            }
        }
    }
    //Write top margin
    sA = sourceY * sRowSize;
    for (y = Math.max(0, destinationY - marginTop), yMax = destinationY; y < yMax; y++) {
        dA = y * dRowSize;

        for (x = 0; x < width; x++) {

            dOffset = dA + (x + destinationX) * dItemSize;
            sOffset = sA + (x + sourceX) * dItemSize;
            for (i = 0; i < _itemSize; i++) {
                dData[dOffset + i] = sData[sOffset + i];
            }
        }
    }
    //Write top-right corner
    sOffset = sourceY * sRowSize + (sourceX + width - 1) * dItemSize;
    for (y = Math.max(0, destinationY - marginTop), yMax = destinationY; y < yMax; y++) {
        dA = y * dRowSize;

        for (x = destinationX + width, xMax = Math.min(destination.width, x + marginRight); x < xMax; x++) {

            dOffset = dA + x * dItemSize;

            for (i = 0; i < _itemSize; i++) {
                dData[dOffset + i] = sData[sOffset + i];
            }
        }
    }
    //Write left margin
    for (y = 0; y < height; y++) {
        dA = (y + destinationY) * dRowSize;
        sA = (y + sourceY) * sRowSize;

        sOffset = sA + (sourceX) * dItemSize;

        for (x = Math.max(0, destinationX - marginLeft), xMax = destinationX; x < xMax; x++) {

            dOffset = dA + x * dItemSize;

            for (i = 0; i < _itemSize; i++) {
                dData[dOffset + i] = sData[sOffset + i];
            }
        }
    }
    //write actual patch
    destination.copy(source, sourceX, sourceY, destinationX, destinationY, width, height);

    //Write right margin
    for (y = 0; y < height; y++) {
        dA = (y + destinationY) * dRowSize;
        sA = (y + sourceY) * sRowSize;

        sOffset = sA + (sourceX + width - 1) * dItemSize;

        for (x = destinationX + width, xMax = Math.min(destination.width, x + marginRight); x < xMax; x++) {

            dOffset = dA + x * dItemSize;

            for (i = 0; i < _itemSize; i++) {
                dData[dOffset + i] = sData[sOffset + i];
            }
        }
    }

    //Write Bottom-left margin
    sOffset = (sourceY + height - 1) * sRowSize + sourceX * dItemSize;
    for (y = destinationY + width, yMax = Math.min(destination.height, y + marginBottom); y < yMax; y++) {
        dA = y * dRowSize;

        for (x = Math.max(0, destinationX - marginLeft), xMax = destinationX; x < xMax; x++) {

            dOffset = dA + x * dItemSize;

            for (i = 0; i < _itemSize; i++) {
                dData[dOffset + i] = sData[sOffset + i];
            }
        }
    }
    //Write Bottom margin
    sA = (sourceY + height - 1) * sRowSize;
    for (y = destinationY + width, yMax = Math.min(destination.height, y + marginBottom); y < yMax; y++) {
        dA = y * dRowSize;

        for (x = 0; x < width; x++) {

            dOffset = dA + (x + destinationX) * dItemSize;
            sOffset = sA + (x + sourceX) * dItemSize;
            for (i = 0; i < _itemSize; i++) {
                dData[dOffset + i] = sData[sOffset + i];
            }
        }
    }
    //Write Bottom-right margin
    sOffset = (sourceY + height - 1) * sRowSize + (sourceX + width - 1) * dItemSize;
    for (y = destinationY + width, yMax = Math.min(destination.height, y + marginBottom); y < yMax; y++) {
        dA = y * dRowSize;

        for (x = destinationX + width, xMax = Math.min(destination.width, x + marginRight); x < xMax; x++) {

            dOffset = dA + x * dItemSize;

            for (i = 0; i < _itemSize; i++) {
                dData[dOffset + i] = sData[sOffset + i];
            }
        }
    }

    destination.version++;
}