/*!
 * jQuery ClassyGradient
 * www.class.pm
 *
 * Written by Marius Stanciu - Sergiu <marius@class.pm>
 * Licensed under the MIT license www.class.pm/LICENSE-MIT
 * Version 1.1.0
 *
 */

( function ( $ ) {
	$.ClassyGradient = function ( element, options ) {
		const defaults = {
			gradient: '0% #02CDE8, 100% #000000',
			width: 300,
			height: 18,
			point: 8,
			orientation: 'vertical',
			target: null,
			onChange() {},
			onInit() {},
		};
		let $element = $( element ),
			_container,
			_canvas,
			$pointsContainer,
			$pointsInfos,
			$pointsInfosContent,
			$pointColor,
			$pointPosition,
			$btnPointDelete,
			_context,
			_selPoint;
		let points = new Array();
		this.settings = {};
		this.__constructor = function () {
			this.settings = $.extend( {}, defaults, options );
			this.update();
			this.settings.onInit();
			return this;
		};
		this.update = function () {
			this._setupPoints();
			this._setup();
			this._render();
		};
		this.getCSS = function () {
			let out = '',
				svgX = '0%',
				svgY = '100%',
				webkitDir = 'left bottom',
				defDir = 'top';
			if ( this.settings.orientation === 'horizontal' ) {
				svgX = '100%';
				svgY = '0%';
				webkitDir = 'right top';
				defDir = 'left';
			}

			defDir = this.settings.orientation; // hack by amit

			let svg =
				'<svg xmlns="http://www.w3.org/2000/svg">' +
				'<defs>' +
				'<linearGradient id="gradient" x1="0%" y1="0%" x2="' +
				svgX +
				'" y2="' +
				svgY +
				'">';
			let webkitCss = '-webkit-gradient(linear, left top, ' + webkitDir;
			let defCss = '';
			$.each( points, function ( i, el ) {
				webkitCss += ', color-stop(' + el[ 0 ] + ', ' + el[ 1 ] + ')';
				defCss += ',' + el[ 1 ] + ' ' + el[ 0 ] + '';
				svg +=
					'<stop offset="' +
					el[ 0 ] +
					'" style="stop-color:' +
					el[ 1 ] +
					';" />';
			} );
			webkitCss += ')';
			defCss = defCss.substr( 1 );
			svg +=
				'</linearGradient>' +
				'</defs>' +
				'<rect fill="url(#gradient)" height="100%" width="100%" />' +
				'</svg>';
			svg = this._base64( svg );
			out +=
				'background: url(data:image/svg+xml;base64,' +
				svg +
				');' +
				'\n';
			out += 'background: ' + webkitCss + ';\n';
			out +=
				'background: ' +
				'-moz-linear-gradient(' +
				defDir +
				',' +
				defCss +
				');' +
				'\n';
			out +=
				'background: ' +
				'-webkit-linear-gradient(' +
				defDir +
				',' +
				defCss +
				');' +
				'\n';
			out +=
				'background: ' +
				'-o-linear-gradient(' +
				defDir +
				',' +
				defCss +
				');' +
				'\n';
			out +=
				'background: ' +
				'-ms-linear-gradient(' +
				defDir +
				',' +
				defCss +
				');' +
				'\n';
			out +=
				'background: ' +
				'linear-gradient(' +
				defDir +
				',' +
				defCss +
				');';
			return out;
		};
		this.getArray = function () {
			return points;
		};
		this.getString = function () {
			let out = '';
			$.each( points, function ( i, el ) {
				out += el[ 0 ] + ' ' + el[ 1 ] + ',';
			} );
			out = out.substr( 0, out.length - 1 );
			return out;
		};
		this.setOrientation = function ( orientation ) {
			this.settings.orientation = orientation;
			this._renderToTarget();
		};
		this._setupPoints = function () {
			points = new Array();
			if ( $.isArray( this.settings.gradient ) ) {
				points = this.settings.gradient;
			} else {
				points = this._getGradientFromString( this.settings.gradient );
			}
		};
		this._setup = function () {
			const self = this;
			$element.empty();
			_container = $( '<div class="ClassyGradient"></div>' );
			_canvas = $(
				'<canvas class="canvas" width="' +
					this.settings.width +
					'" height="' +
					this.settings.height +
					'"></canvas>'
			);
			_container.append( _canvas );
			_context = _canvas.get( 0 ).getContext( '2d' );
			$pointsContainer = $( '<div class="points"></div>' );
			$pointsContainer.css(
				'width',
				this.settings.width +
					Math.round( this.settings.point / 2 + 1 ) +
					'px'
			);
			_container.append( $pointsContainer );
			$pointsInfos = $( '<div class="info"></div>' );
			$pointsInfos.append( '<div class="arrow"></div>' );
			_container.append( $pointsInfos );
			$pointsInfosContent = $( '<div class="content"></div>' );
			$pointsInfos.append( $pointsInfosContent );
			$element.hover(
				function () {
					$element.addClass( 'hover' );
				},
				function () {
					$element.removeClass( 'hover' );
				}
			);
			$pointColor = $(
				'<div class="point-color"><div style="background-color: #00ff00"></div></div>'
			);
			$pointPosition = $( '<span class="point-position">%</span>' );
			$btnPointDelete = $( '<a href="#" class="delete"></a>' );
			$pointsInfosContent.append(
				$pointColor,
				$pointPosition,
				$btnPointDelete
			);
			$element.append( _container );
			$pointColor.ColorPicker( {
				color: '#00ff00',
				onSubmit( hsb, hex, rgb ) {
					$element
						.find( '.point-color div' )
						.css( 'backgroundColor', '#' + hex );
					_selPoint.css( 'backgroundColor', '#' + hex );
					self._renderCanvas();
					self._renderToTarget();
				},
				onChange( hsb, hex, rgb ) {
					$element
						.find( '.point-color div' )
						.css( 'backgroundColor', '#' + hex );
					_selPoint.css( 'backgroundColor', '#' + hex );
					self._renderCanvas();
					self._renderToTarget();
				},
			} );
			$( document ).bind( 'click', function () {
				if ( ! $element.is( '.hover' ) ) {
					$pointsInfos.hide( 'fast' );
				}
			} );
			_canvas.unbind( 'click' );
			_canvas.bind( 'click', function ( e ) {
				let offset = _canvas.offset(),
					clickPosition = e.pageX - offset.left;
				clickPosition = Math.round(
					( clickPosition * 100 ) / self.settings.width
				);
				let defaultColor = '#000000',
					minDist = 999999999999;
				$.each( points, function ( i, el ) {
					if (
						parseInt( el[ 0 ] ) < clickPosition &&
						clickPosition - parseInt( el[ 0 ] ) < minDist
					) {
						minDist = clickPosition - parseInt( el[ 0 ] );
						defaultColor = el[ 1 ];
					} else if (
						parseInt( el[ 0 ] ) > clickPosition &&
						parseInt( el[ 0 ] ) - clickPosition < minDist
					) {
						minDist = parseInt( el[ 0 ] ) - clickPosition;
						defaultColor = el[ 1 ];
					}
				} );
				points.push( [ clickPosition + '%', defaultColor ] );
				points.sort( self._sortByPosition );
				self._render();
				$.each( points, function ( i, el ) {
					if ( el[ 0 ] == clickPosition + '%' ) {
						self._selectPoint(
							$pointsContainer.find( '.point:eq(' + i + ')' )
						);
					}
				} );
			} );
		};
		this._render = function () {
			this._initGradientPoints();
			this._renderCanvas();
			this._renderToTarget();
		};
		this._initGradientPoints = function () {
			const self = this;
			$pointsContainer.empty();
			$.each( points, function ( i, el ) {
				$pointsContainer.append(
					'<div class="point" style="background-color: ' +
						el[ 1 ] +
						'; left:' +
						( parseInt( el[ 0 ] ) * self.settings.width ) / 100 +
						'px;"></div>'
				);
			} );
			$pointsContainer
				.find( '.point' )
				.css( 'width', this.settings.point + 'px' )
				.css( 'height', this.settings.point + 'px' )
				.mouseup( function () {
					self._selectPoint( this );
				} )
				.draggable( {
					axis: 'x',
					containment: 'parent',
					drag() {
						self._selectPoint( this );
						self._renderCanvas();
						self._renderToTarget();
					},
				} );
		};
		this._selectPoint = function ( el ) {
			const self = this;
			_selPoint = $( el );
			let color = $( el ).css( 'backgroundColor' ),
				position = parseInt( $( el ).css( 'left' ) );
			position = Math.round( ( position / this.settings.width ) * 100 );
			color = color.substr( 4, color.length );
			color = color.substr( 0, color.length - 1 );
			$pointColor.ColorPickerSetColor(
				this._rgbToHex( color.split( ',' ) )
			);
			$pointColor
				.find( 'div' )
				.css( 'backgroundColor', this._rgbToHex( color.split( ',' ) ) );
			$pointPosition.html( 'Position: ' + position + '%' );
			$btnPointDelete.unbind( 'click' ).bind( 'click', function () {
				if ( points.length > 1 ) {
					points.splice( _selPoint.index(), 1 );
					self._render();
					$element.find( '.info' ).hide( 'fast' );
				}
				return false;
			} );
			$element
				.find( '.info' )
				.css(
					'marginLeft',
					parseInt( $( el ).css( 'left' ) ) - 30 + 'px'
				)
				.show( 'fast' );
		};
		this._renderCanvas = function () {
			const self = this;
			points = new Array();
			$element.find( '.point' ).each( function ( i, el ) {
				const position = Math.round(
					( parseInt( $( el ).css( 'left' ) ) /
						self.settings.width ) *
						100
				);
				let color = $( el )
					.css( 'backgroundColor' )
					.substr( 4, $( el ).css( 'backgroundColor' ).length - 5 );
				color = self._rgbToHex( color.split( ',' ) );
				points.push( [ position + '%', color ] );
			} );
			points.sort( self._sortByPosition );
			this._renderToCanvas();
			this.settings.onChange(
				this.getString(),
				this.getCSS(),
				this.getArray()
			);
		};
		this._renderToElement = function ( target, gradient ) {
			let svgX = '0%',
				svgY = '100%',
				webkitDir = 'left bottom',
				defDir = 'top';
			if (
				target === _canvas ||
				this.settings.orientation === 'horizontal'
			) {
				svgX = '100%';
				svgY = '0%';
				webkitDir = 'right top';
				defDir = 'left';
			}

			defDir = this.settings.orientation; // hack by amit

			let svg =
				'<svg xmlns="http://www.w3.org/2000/svg">' +
				'<defs>' +
				'<linearGradient id="gradient" x1="0%" y1="0%" x2="' +
				svgX +
				'" y2="' +
				svgY +
				'">';
			let webkitCss = '-webkit-gradient(linear, left top, ' + webkitDir;
			let defCss = '';
			$.each( gradient, function ( i, el ) {
				webkitCss += ', color-stop(' + el[ 0 ] + ', ' + el[ 1 ] + ')';
				defCss += ',' + el[ 1 ] + ' ' + el[ 0 ] + '';
				svg +=
					'<stop offset="' +
					el[ 0 ] +
					'" style="stop-color:' +
					el[ 1 ] +
					';" />';
			} );
			webkitCss += ')';
			defCss = defCss.substr( 1 );
			svg += '</linearGradient>' + '</defs>';
			if ( target === $pointsInfosContent ) {
				const tooltipRadius = parseInt(
					$pointsInfosContent.css( 'borderRadius' )
				);
				svg +=
					'<rect fill="url(#gradient)" height="100%" width="100%" rx="' +
					tooltipRadius +
					'" ry="' +
					tooltipRadius +
					'" />';
			} else {
				svg +=
					'<rect fill="url(#gradient)" height="100%" width="100%" />';
			}
			svg += '</svg>';
			svg = this._base64( svg );
			target.css(
				'background',
				'url(data:image/svg+xml;base64,' + svg + ')'
			);
			target.css( 'background', webkitCss );
			target.css(
				'background',
				'-moz-linear-gradient(' + defDir + ',' + defCss + ')'
			);
			target.css(
				'background',
				'-webkit-linear-gradient(' + defDir + ',' + defCss + ')'
			);
			target.css(
				'background',
				'-o-linear-gradient(' + defDir + ',' + defCss + ')'
			);
			target.css(
				'background',
				'-ms-linear-gradient(' + defDir + ',' + defCss + ')'
			);
			target.css(
				'background',
				'linear-gradient(' + defDir + ',' + defCss + ')'
			);
		};
		this._renderToTarget = function () {
			if ( this.settings.target !== null ) {
				this._renderToElement( $( this.settings.target ), points );
			}
		};
		this._renderToCanvas = function () {
			const gradient = _context.createLinearGradient(
				0,
				0,
				this.settings.width,
				0
			);
			$.each( points, function ( i, el ) {
				gradient.addColorStop( parseInt( el[ 0 ] ) / 100, el[ 1 ] );
			} );
			_context.clearRect(
				0,
				0,
				this.settings.width,
				this.settings.height
			);
			_context.fillStyle = gradient;
			_context.fillRect(
				0,
				0,
				this.settings.width,
				this.settings.height
			);
			this.settings.onChange(
				this.getString(),
				this.getCSS(),
				this.getArray()
			);
		};
		this._getGradientFromString = function ( gradient ) {
			const arr = new Array(),
				_t = gradient.split( ',' );
			$.each( _t, function ( i, el ) {
				let position;
				if (
					el.substr( el.indexOf( '%' ) - 3, el.indexOf( '%' ) ) ==
						'100' ||
					el.substr( el.indexOf( '%' ) - 3, el.indexOf( '%' ) ) ==
						'100%'
				) {
					position = '100%';
				} else if ( el.indexOf( '%' ) > 1 ) {
					position = parseInt(
						el.substr( el.indexOf( '%' ) - 2, el.indexOf( '%' ) )
					);
					position += '%';
				} else {
					position = parseInt(
						el.substr( el.indexOf( '%' ) - 1, el.indexOf( '%' ) )
					);
					position += '%';
				}
				const color = el.substr( el.indexOf( '#' ), 7 );
				arr.push( [ position, color ] );
			} );
			return arr;
		};
		this._rgbToHex = function ( rgb ) {
			const R = rgb[ 0 ],
				G = rgb[ 1 ],
				B = rgb[ 2 ];
			function toHex( n ) {
				n = parseInt( n, 10 );
				if ( isNaN( n ) ) return '00';
				n = Math.max( 0, Math.min( n, 255 ) );
				return (
					'0123456789ABCDEF'.charAt( ( n - ( n % 16 ) ) / 16 ) +
					'0123456789ABCDEF'.charAt( n % 16 )
				);
			}
			return '#' + toHex( R ) + toHex( G ) + toHex( B );
		};
		this._sortByPosition = function ( data_A, data_B ) {
			if ( parseInt( data_A[ 0 ] ) < parseInt( data_B[ 0 ] ) ) {
				return -1;
			}
			if ( parseInt( data_A[ 0 ] ) > parseInt( data_B[ 0 ] ) ) {
				return 1;
			}
			return 0;
		};
		this._base64 = function ( input ) {
			let keyStr =
					'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=',
				output = '',
				chr1,
				chr2,
				chr3,
				enc1,
				enc2,
				enc3,
				enc4,
				i = 0;
			while ( i < input.length ) {
				chr1 = input.charCodeAt( i++ );
				chr2 = input.charCodeAt( i++ );
				chr3 = input.charCodeAt( i++ );
				enc1 = chr1 >> 2;
				enc2 = ( ( chr1 & 3 ) << 4 ) | ( chr2 >> 4 );
				enc3 = ( ( chr2 & 15 ) << 2 ) | ( chr3 >> 6 );
				enc4 = chr3 & 63;
				if ( isNaN( chr2 ) ) {
					enc3 = enc4 = 64;
				} else if ( isNaN( chr3 ) ) {
					enc4 = 64;
				}
				output +=
					keyStr.charAt( enc1 ) +
					keyStr.charAt( enc2 ) +
					keyStr.charAt( enc3 ) +
					keyStr.charAt( enc4 );
			}
			return output;
		};
		return this.__constructor();
	};
	$.fn.ClassyGradient = function ( options ) {
		return this.each( function () {
			if ( $( this ).data( 'ClassyGradient' ) === undefined ) {
				const plugin = new $.ClassyGradient( this, options );
				$( this ).data( 'ClassyGradient', plugin );
			}
		} );
	};
} )( jQuery );
