# Source: affine-transform.js

    const hasard = require('hasard');
    const AbstractAugmenter = require('./abstract');

    /**
    * @typedef {Number  | Hasard.<Number>} NumberArgument
    */
    /**
    * @typedef {Boolean  | Hasard.<Boolean>} BooleanArgument
    */

    /**
    * A color (3 channels or 4 channels) to use in the augmentation step
    * @typedef {Array.<Number>  | Hasard.<Array.<Number>>} ColorArgument
    */
    /**
    * @typedef {String  |  Hasard.<String>} BorderTypeArgument
    * describe how to handle the new pixel created by the augmentation step
    * * "constant" : use a constant variable
    * * "transparent" : use transparent pixels
    * * "replicate" : replicate the border's colors
    */

    /**
    *  Applies affine transformations to images.
    * @constructor
    * @param {Object} opts options
    * @param {ArrayXY} [opts.translatePercent = [0,0]] translates
    * @param {ArrayXY} [opts.scale = [1,1]] scale percent
    * @param {NumberArgument} [opts.rotate=0] degrees of rotation
    * @param {NumberArgument} [opts.shear=0] degrees of shear
    * @param {BooleanArgument} [keepSize=false]
    * If true, the output image plane size will be fitted
    * to the distorted image size, i.e. images rotated by 45deg
    * will not be partially outside of the image plane.
    * WARNING : not implemented yet
    * @param {ColorArgument} [opts.borderValue=[0,0,0]] if borderType is "constant" this is used as border pixel values
    * @param {BorderTypeArgument} [opts.borderType="constant"] "constant", "replicate", "transparent"
    * @example
    // Simple usage, Affine transform with scale change
    ia.affine({scale: 1.2});
    * @example
    // Affine transform with rotation of 90° and random scale between 100% and 150%
    ia.affine({scale: h.number(1, 1.5), rotate: 90});
    */

    class AffineTransformAugmenter extends AbstractAugmenter {
    	constructor(opts, ia) {
    		super(opts, ia);
    		const {scale = [1, 1], translatePercent = [0, 0], rotate = 0, shear = 0, keepSize = false, borderValue = [0, 0, 0], borderType = 'constant'} = opts;
    		this.scale = this.toSize2(scale);
    		this.translatePercent = this.toSize2(translatePercent);
    		this.rotate = rotate;
    		this.shear = shear;
    		this.keepSize = keepSize;
    		this.borderValue = borderValue;
    		this.borderType = borderType;
    	}

    	buildParams() {
    		const {scale, translatePercent, rotate, shear, borderValue, borderType} = this;
    		return new hasard.Object({
    			scale,
    			translatePercent,
    			rotate,
    			shear,
    			borderValue,
    			borderType
    		});
    	}

    	getMatrix({
    		scale,
    		translatePercent,
    		rotate,
    		shear,
    		width,
    		height
    	}) {
    		const centerX = width / 2;
    		const centerY = height / 2;
    		// From http://scikit-image.org/docs/dev/api/skimage.transform.html#skimage.transform.AffineTransform

    		const rot = rotate / 180 * Math.PI;
    		const shr = shear / 180 * Math.PI;

    		const a0 = scale[0] * Math.cos(rot);
    		const a1 = -1 * scale[1] * Math.sin(rot + shr);

    		const a2Base = (translatePercent[0] * width);

    		const a2 = a2Base - (a0 * centerX) - (a1 * centerY) + centerX;

    		const b0 = scale[0] * Math.sin(rot);
    		const b1 = scale[1] * Math.cos(rot + shr);

    		const b2Base = (translatePercent[1] * height);
    		const b2 = b2Base - (b0 * centerY) - (b1 * centerX) + centerY;
    		return this.backend.floatMatrix([[a0, a1, a2], [b0, b1, b2]]);
    	}

    	augmentImage({width, height, image}, {
    		scale,
    		translatePercent,
    		rotate,
    		shear,
    		borderValue,
    		borderType
    	}) {
    		const affineMatrix = this.getMatrix({
    			scale,
    			translatePercent,
    			rotate,
    			shear,
    			width,
    			height
    		});

    		const res = this.backend.affine(image, {
    			affineMatrix,
    			size: [width, height],
    			borderType,
    			borderValue
    		});

    		this.backend.dispose(affineMatrix);
    		return res;
    	}

    	augmentPoints({points, width, height}, attr) {
    		const affineMatrix = this.getMatrix(Object.assign({}, attr, {width, height}));
    		const transformPoint = point => {
    			const p3 = this.backend.point3(point.x, point.y, 1);
    			const res = this.backend.matMul(affineMatrix, p3, affineMatrix.type);
    			const p2 = this.backend.pointFromMat(res);
    			const res2 = this.backend.pointToArray(p2);
    			this.backend.dispose(res);
    			return res2;
    		};

    		const res = points.map(transformPoint);
    		this.backend.dispose(affineMatrix);
    		return res;
    	}
    }

    module.exports = AffineTransformAugmenter;