/*!
 * VisualEditor UserInterface MediaSizeWidget class.
 *
 * @copyright See AUTHORS.txt
 */

/**
 * Widget that lets the user edit dimensions (width and height),
 * based on a scalable object.
 *
 * @class
 * @extends OO.ui.Widget
 *
 * @constructor
 * @param {ve.dm.Scalable} [scalable]
 * @param {Object} [config] Configuration options
 * @param {boolean} [config.noDefaultDimensions] The item being sized doesn't have default dimensions
 * @param {string} [config.dimensionsAlign] Alignment for the dimensions widget
 */
ve.ui.MediaSizeWidget = function VeUiMediaSizeWidget( scalable, config ) {
	// Configuration
	config = config || {};

	this.scalable = scalable;

	// Parent constructor
	ve.ui.MediaSizeWidget.super.call( this, config );

	// Properties
	this.ratio = {};
	this.currentDimensions = {};
	this.maxDimensions = {};
	this.valid = null;
	this.noDefaultDimensions = !!config.noDefaultDimensions;
	this.dimensionsAlign = config.dimensionsAlign || 'right';

	// Define button select widget
	this.sizeTypeSelect = new OO.ui.ButtonSelectWidget( {
		classes: [ 've-ui-mediaSizeWidget-section-sizetype' ]
	} );
	this.sizeTypeSelect.addItems( [
		new OO.ui.ButtonOptionWidget( {
			data: 'default',
			label: ve.msg( 'visualeditor-mediasizewidget-sizeoptions-default' )
		} ),
		// TODO: when upright is supported by Parsoid
		// new OO.ui.ButtonOptionWidget( {
		// data: 'scale',
		// label: ve.msg( 'visualeditor-mediasizewidget-sizeoptions-scale' )
		// } ),
		new OO.ui.ButtonOptionWidget( {
			data: 'custom',
			label: ve.msg( 'visualeditor-mediasizewidget-sizeoptions-custom' )
		} )
	] );
	const sizeTypeField = new OO.ui.FieldLayout( this.sizeTypeSelect );

	// Define scale
	/*
	this.scaleInput = new OO.ui.TextInputWidget();
	scalePercentLabel = new OO.ui.LabelWidget( {
		input: this.scaleInput,
		label: ve.msg( 'visualeditor-mediasizewidget-label-scale-percent' )
	} );
	*/

	this.dimensions = new ve.ui.DimensionsWidget( { validate: this.isValid.bind( this ) } );

	// Error label is available globally so it can be displayed and
	// hidden as needed
	this.errorLabel = new OO.ui.LabelWidget( {
		label: ve.msg( 'visualeditor-mediasizewidget-label-defaulterror' )
	} );

	// Field layouts
	/*
	scaleField = new OO.ui.FieldLayout(
		this.scaleInput, {
			align: 'right',
			// TODO: when upright is supported by Parsoid
			// classes: ['ve-ui-mediaSizeWidget-section-scale'],
			label: ve.msg( 'visualeditor-mediasizewidget-label-scale' )
		}
	);
	TODO: when upright is supported by Parsoid
	this.scaleInput.$element.append( scalePercentLabel.$element );
	*/
	const dimensionsField = new OO.ui.FieldLayout(
		this.dimensions, {
			align: this.dimensionsAlign,
			classes: [ 've-ui-mediaSizeWidget-section-custom' ]
		}
	);

	// Build GUI
	this.$element.addClass( 've-ui-mediaSizeWidget' );
	if ( !this.noDefaultDimensions ) {
		this.$element.append( sizeTypeField.$element );
	}
	this.$element.append( dimensionsField.$element );
	// TODO: when upright is supported by Parsoid
	// this.$element.append( scaleField.$element );
	this.$element.append(
		$( '<div>' )
			.addClass( 've-ui-mediaSizeWidget-label-error' )
			.append( this.errorLabel.$element )
	);

	// Events
	this.dimensions.connect( this, {
		widthChange: [ 'onDimensionsChange', 'width' ],
		heightChange: [ 'onDimensionsChange', 'height' ]
	} );
	// TODO: when upright is supported by Parsoid
	// this.scaleInput.connect( this, { change: 'onScaleChange' } );
	this.sizeTypeSelect.connect( this, { choose: 'onSizeTypeChoose' } );

};

/* Inheritance */

OO.inheritClass( ve.ui.MediaSizeWidget, OO.ui.Widget );

/* Events */

/**
 * @event ve.ui.MediaSizeWidget#change
 * @param {Object} dimensions Width and height dimensions
 */

/**
 * @event ve.ui.MediaSizeWidget#valid
 * @param {boolean} isValid Current dimensions are valid
 */

/**
 * @event ve.ui.MediaSizeWidget#changeSizeType
 * @param {string} sizeType 'default', 'custom' or 'scale'
 */

/* Methods */

/**
 * Respond to change in original dimensions in the scalable object.
 * Specifically, enable or disable the 'default' option.
 *
 * @param {Object} dimensions Original dimensions
 */
ve.ui.MediaSizeWidget.prototype.onScalableOriginalSizeChange = function () {
	// Revalidate current dimensions
	this.updateDisabled();
	this.validateDimensions();
};

/**
 * Respond to change in current dimensions in the scalable object.
 *
 * @param {Object} dimensions Original dimensions
 */
ve.ui.MediaSizeWidget.prototype.onScalableCurrentSizeChange = function ( dimensions ) {
	if ( !ve.isEmptyObject( dimensions ) ) {
		this.setCurrentDimensions( dimensions );
		this.validateDimensions();
	}
};

/**
 * Respond to default size or status change in the scalable object.
 *
 * @param {boolean} isDefault Current default state
 */
ve.ui.MediaSizeWidget.prototype.onScalableDefaultSizeChange = function ( isDefault ) {
	// Update the default size into the dimensions widget
	this.updateDefaultDimensions();
	// TODO: When 'scale' ('upright' support) is ready, this will need to be adjusted
	// to support that as well
	this.setSizeType(
		isDefault ?
			'default' :
			'custom'
	);
	this.validateDimensions();
};

/**
 * Respond to width/height input value change. Only update dimensions if
 * the value is numeric. Invoke validation for every change.
 *
 * This is triggered every time the dimension widget has its values changed
 * either by the user or externally. The external call to 'setCurrentDimensions'
 * will result in this event being evoked if the dimension inputs have changed,
 * and same with changing dimensions type.
 *
 * The 'change' event for the entire widget is emitted through this method, as
 * it means that the actual values have changed, regardless of whether they
 * are valid or not.
 *
 * @param {string} type The input that was updated, 'width' or 'height'
 * @param {string} value The new value of the input
 * @fires ve.ui.MediaSizeWidget#change
 */
ve.ui.MediaSizeWidget.prototype.onDimensionsChange = function ( type, value ) {
	if ( +value === 0 && !this.noDefaultDimensions ) {
		this.setSizeType( 'default' );
	} else {
		this.setSizeType( 'custom' );
		if ( !isNaN( +value ) ) {
			const dimensions = {};
			dimensions[ type ] = +value;
			this.setCurrentDimensions( dimensions );
		} else {
			this.validateDimensions();
		}
	}
};

// /**
//  * Respond to change of the scale input
//  */
/*
ve.ui.MediaSizeWidget.prototype.onScaleChange = function () {
	// If the input changed (and not empty), set to 'custom'
	// Otherwise, set to 'default'
	if ( !this.dimensions.isEmpty() ) {
		this.sizeTypeSelect.selectItemByData( 'scale' );
	} else {
		this.sizeTypeSelect.selectItemByData( 'default' );
	}
};
*/

/**
 * Respond to size type change
 *
 * @param {OO.ui.OptionWidget} item Selected size type item
 * @fires ve.ui.MediaSizeWidget#changeSizeType
 */
ve.ui.MediaSizeWidget.prototype.onSizeTypeChoose = function ( item ) {
	const selectedType = item.getData(),
		wasDefault = this.scalable.isDefault();

	this.scalable.toggleDefault( selectedType === 'default' );

	if ( selectedType === 'default' ) {
		// If there are defaults, put them into the values
		if ( !ve.isEmptyObject( this.dimensions.getDefaults() ) ) {
			this.dimensions.clear();
		}
	} else if ( selectedType === 'custom' ) {
		// If we were default size before, set the current dimensions to the default size
		if ( wasDefault && !ve.isEmptyObject( this.dimensions.getDefaults() ) ) {
			this.setCurrentDimensions( this.dimensions.getDefaults() );
		}
		this.validateDimensions();
	}

	this.emit( 'changeSizeType', selectedType );
	this.updateDisabled();
	this.validateDimensions();
};

// /**
//  * Set the placeholder value of the scale input
//  *
//  * @param {number} value Placeholder value
//  * @chainable
//  * @return {ve.ui.MediaSizeWidget}
//  */
/*
ve.ui.MediaSizeWidget.prototype.setScalePlaceholder = function ( value ) {
	this.scaleInput.$element.prop( 'placeholder', value );
	return this;
};
*/

// /**
//  * Get the placeholder value of the scale input
//  *
//  * @return {string} Placeholder value
//  */
/*
ve.ui.MediaSizeWidget.prototype.getScalePlaceholder = function () {
	return this.scaleInput.$element.prop( 'placeholder' );
};
*/

/**
 * Select a size type in the select widget
 *
 * @param {string} sizeType The size type to select
 * @chainable
 * @return {ve.ui.MediaSizeWidget}
 */
ve.ui.MediaSizeWidget.prototype.setSizeType = function ( sizeType ) {
	if (
		this.getSizeType() !== sizeType ||
		// If the dimensions widget has zeros make sure to
		// allow for the change in size type
		+this.dimensions.getWidth() === 0 ||
		+this.dimensions.getHeight() === 0
	) {
		this.sizeTypeSelect.chooseItem(
			this.sizeTypeSelect.findItemFromData( sizeType )
		);
	}
	return this;
};
/**
 * Get the size type from the select widget
 *
 * @return {string} The size type
 */
ve.ui.MediaSizeWidget.prototype.getSizeType = function () {
	return this.sizeTypeSelect.findSelectedItem() ? this.sizeTypeSelect.findSelectedItem().getData() : '';
};

/**
 * Set the scalable object the widget deals with
 *
 * @param {ve.dm.Scalable} scalable A scalable object representing the media source being resized.
 * @chainable
 * @return {ve.ui.MediaSizeWidget}
 */
ve.ui.MediaSizeWidget.prototype.setScalable = function ( scalable ) {
	if ( this.scalable instanceof ve.dm.Scalable ) {
		this.scalable.disconnect( this );
	}
	this.scalable = scalable;
	// Events
	this.scalable.connect( this, {
		defaultSizeChange: 'onScalableDefaultSizeChange',
		originalSizeChange: 'onScalableOriginalSizeChange',
		currentSizeChange: 'onScalableCurrentSizeChange'
	} );

	this.updateDefaultDimensions();

	if ( !this.scalable.isDefault() ) {
		// Reset current dimensions to new scalable object
		this.setCurrentDimensions( this.scalable.getCurrentDimensions() );
	}

	// Call for the set size type according to default or custom settings of the scalable
	if ( this.scalable.getOriginalDimensions() ) {
		this.setSizeType( this.scalable.isDefault() ? 'default' : 'custom' );
	}
	this.updateDisabled();
	this.validateDimensions();
	return this;
};

/**
 * Get the attached scalable object
 *
 * @return {ve.dm.Scalable} The scalable object representing the media
 * source being resized.
 */
ve.ui.MediaSizeWidget.prototype.getScalable = function () {
	return this.scalable;
};

/**
 * Set the image aspect ratio explicitly
 *
 * @param {number} ratio Numerical value of an aspect ratio
 * @chainable
 * @return {ve.ui.MediaSizeWidget}
 */
ve.ui.MediaSizeWidget.prototype.setRatio = function ( ratio ) {
	this.scalable.setRatio( ratio );
	return this;
};

/**
 * Get the current aspect ratio
 *
 * @return {number} Aspect ratio
 */
ve.ui.MediaSizeWidget.prototype.getRatio = function () {
	return this.scalable.getRatio();
};

/**
 * Set the maximum dimensions for the image. These will be limited only if
 * enforcedMax is true.
 *
 * @param {Object} dimensions Height and width
 * @chainable
 * @return {ve.ui.MediaSizeWidget}
 */
ve.ui.MediaSizeWidget.prototype.setMaxDimensions = function ( dimensions ) {
	// Normalize dimensions before setting
	const maxDimensions = ve.dm.Scalable.static.getDimensionsFromValue( dimensions, this.scalable.getRatio() );
	this.scalable.setMaxDimensions( maxDimensions );
	return this;
};

/**
 * Retrieve the currently defined maximum dimensions
 *
 * @return {Object} dimensions Height and width
 */
ve.ui.MediaSizeWidget.prototype.getMaxDimensions = function () {
	return this.scalable.getMaxDimensions();
};

/**
 * Retrieve the current dimensions
 *
 * @return {Object} Width and height
 */
ve.ui.MediaSizeWidget.prototype.getCurrentDimensions = function () {
	return this.currentDimensions;
};

/**
 * @inheritdoc
 */
ve.ui.MediaSizeWidget.prototype.setDisabled = function ( disabled ) {
	// Parent method
	ve.ui.MediaSizeWidget.super.prototype.setDisabled.call( this, disabled );

	this.updateDisabled();
	return this;
};

/**
 * Update the disabled state of sub widgets
 *
 * @chainable
 * @return {ve.ui.MediaSizeWidget}
 */
ve.ui.MediaSizeWidget.prototype.updateDisabled = function () {
	const disabled = this.isDisabled();

	// The 'updateDisabled' method may called before the widgets
	// are fully defined. So, before disabling/enabling anything,
	// make sure the objects exist
	if ( this.sizeTypeSelect &&
		this.dimensions &&
		this.scalable
	) {
		const sizeType = this.getSizeType();

		// Disable the type select
		this.sizeTypeSelect.setDisabled( disabled );

		// Disable the default type options
		this.sizeTypeSelect.findItemFromData( 'default' ).setDisabled(
			ve.isEmptyObject( this.scalable.getDefaultDimensions() )
		);

		// Disable the dimensions widget
		this.dimensions.setDisabled( disabled || sizeType !== 'custom' );

		// Disable the scale widget
		// this.scaleInput.setDisabled( disabled || sizeType !== 'scale' );
	}
	return this;
};

/**
 * Updates the current dimensions in the inputs, either one at a time or both
 *
 * @param {Object} dimensions Dimensions with width and height
 * @fires ve.ui.MediaSizeWidget#change
 */
ve.ui.MediaSizeWidget.prototype.setCurrentDimensions = function ( dimensions ) {
	// Recursion protection
	if ( this.preventChangeRecursion ) {
		return;
	}
	this.preventChangeRecursion = true;

	if ( !this.scalable.isFixedRatio() ) {
		dimensions = ve.extendObject( {}, this.getCurrentDimensions(), dimensions );
	}

	// Normalize the new dimensions
	const normalizedDimensions = ve.dm.Scalable.static.getDimensionsFromValue( dimensions, this.scalable.getRatio() );

	if (
		// Update only if the dimensions object is valid
		ve.dm.Scalable.static.isDimensionsObjectValid( normalizedDimensions ) &&
		// And only if the dimensions object is not default
		!this.scalable.isDefault()
	) {
		this.currentDimensions = normalizedDimensions;
		// This will only update if the value has changed
		// Set width & height individually as they may be 0
		this.dimensions.setWidth( this.currentDimensions.width );
		this.dimensions.setHeight( this.currentDimensions.height );

		// Update scalable object
		this.scalable.setCurrentDimensions( this.currentDimensions );

		this.validateDimensions();
		// Emit change event
		this.emit( 'change', this.currentDimensions );
	}
	this.preventChangeRecursion = false;
};

/**
 * Validate current dimensions.
 * Explicitly call for validating the current dimensions. This is especially
 * useful if we've changed conditions for the widget, like limiting image
 * dimensions for thumbnails when the image type changes. Triggers the error
 * class if needed.
 *
 * @return {boolean} Current dimensions are valid
 * @fires ve.ui.MediaSizeWidget#valid
 */
ve.ui.MediaSizeWidget.prototype.validateDimensions = function () {
	const isValid = this.isValid();

	if ( this.valid !== isValid ) {
		this.valid = isValid;
		this.errorLabel.toggle( !isValid );
		this.dimensions.setValidityFlag();
		// Emit validation change event
		this.emit( 'valid', this.valid );
	}
	return isValid;
};

/**
 * Set default dimensions for the widget. Values are given by scalable's
 * defaultDimensions. If no default dimensions are available,
 * the defaults are removed.
 */
ve.ui.MediaSizeWidget.prototype.updateDefaultDimensions = function () {
	const defaultDimensions = this.scalable.getDefaultDimensions();

	if ( !ve.isEmptyObject( defaultDimensions ) ) {
		this.dimensions.setDefaults( defaultDimensions );
	} else {
		this.dimensions.removeDefaults();
	}
	this.updateDisabled();
	this.validateDimensions();
};

/**
 * Check if the custom dimensions are empty.
 *
 * @return {boolean} Both width/height values are empty
 */
ve.ui.MediaSizeWidget.prototype.isCustomEmpty = function () {
	return this.dimensions.isEmpty();
};

// /**
//  * Check if the scale input is empty.
//  *
//  * @return {boolean} Scale input value is empty
//  */
/*
ve.ui.MediaSizeWidget.prototype.isScaleEmpty = function () {
	return ( this.scaleInput.getValue() === '' );
};
*/

/**
 * Check if all inputs are empty.
 *
 * @return {boolean} All input values are empty
 */
ve.ui.MediaSizeWidget.prototype.isEmpty = function () {
	return this.isCustomEmpty();
	// return this.isCustomEmpty() && this.isScaleEmpty();
};

/**
 * Check whether the current value inputs are valid
 * 1. If placeholders are visible, the input is valid
 * 2. If inputs have non numeric values, input is invalid
 * 3. If inputs have numeric values, validate through scalable
 *    calculations to see if the dimensions follow the rules.
 *
 * @return {boolean} Valid or invalid dimension values
 */
ve.ui.MediaSizeWidget.prototype.isValid = function () {
	const itemType = this.sizeTypeSelect.findSelectedItem() ?
		this.sizeTypeSelect.findSelectedItem().getData() : 'custom';

	// TODO: when upright is supported by Parsoid add validation for scale

	if ( itemType === 'custom' ) {
		if (
			this.dimensions.getDefaults() &&
			this.dimensions.isEmpty()
		) {
			return true;
		} else if (
			!isNaN( +this.dimensions.getWidth() ) &&
			!isNaN( +this.dimensions.getHeight() )
		) {
			return this.scalable.isCurrentDimensionsValid();
		} else {
			return false;
		}
	} else {
		// Default images are always valid size
		return true;
	}
};
