/**
 * jQuery Webfonts.
 *
 * Copyright (C) 2012 Santhosh Thottingal
 *
 * UniversalLanguageSelector is dual licensed GPLv2 or later and MIT. You don't
 * have to do anything special to choose one license or the other and you don't
 * have to notify anyone which license you are using. You are free to use
 * UniversalLanguageSelector in commercial projects as long as the copyright
 * header is left intact. See files GPL-LICENSE and MIT-LICENSE for details.
 *
 * @file
 * @ingroup Extensions
 * @licence GNU General Public Licence 2.0 or later
 * @licence MIT License
 */

( function( $, window, document, undefined ) {
	"use strict";

	var WebFonts = function( element, options ) {
		// Load defaults
		this.options = $.extend( {}, $.fn.webfonts.defaults, options );
		this.$element = $( element );
		this.repository = $.extend( WebFonts.repository, this.options.repository );
		this.fonts = [];
		this.originalFontFamily = this.$element.css( 'font-family' );
		this.language = this.$element.attr( 'lang' ) || $( 'html' ).attr( 'lang' );
		this.init();
	};

	WebFonts.repository = {
		base: 'fonts', // Relative or absolute path to the font repository.
		languages: {}, // languages to font mappings
		fonts: {}, // Font name to font configuration mapping

		// Utility methods to work on the repository.
		defaultFont: function( language ) {
			var defaultFont = null;
			if ( this.languages[language] ) {
				defaultFont = this.languages[language][0];
			}
			return defaultFont;
		},

		get: function( fontFamily ) {
			return this.fonts[fontFamily];
		}
	};

	WebFonts.prototype = {
		constructor: WebFonts,

		getFont: function( language ) {
			if ( this.options.fontSelector ) {
				return this.options.fontSelector( this.repository, language || this.language );
			} else {
				return this.repository.defaultFont( language );
			}
		},

		/**
		 * Initialize.
		 */
		init: function() {
			var fontFamily;
			if ( this.language ) {
				fontFamily = this.getFont( this.language );
				this.apply( fontFamily );
			}
			this.parse();
		},

		refresh: function() {
			this.reset();
			this.init();
		},

		/**
		 * Apply a font for the element.
		 *
		 * @param fontFamily String: font family name
		 */
		apply: function( fontFamily, $element ) {
			var fontStack = this.options.fontStack.slice( 0 );

			$element = $element || this.$element;

			// Loading an empty string is pointless.
			// Putting an empty string into a font-family list doesn't work with
			// jQuery.css().
			if ( fontFamily ) {
				this.load( fontFamily );
				fontStack.unshift( fontFamily );
			}

			// Set the font of this element if it's not excluded
			if ( !$element.is( this.options.exclude ) ) {
				$element.css( 'font-family', fontStack.join() );
			}

			// Set the font of this element's children if they are not excluded.
			// font-family of <input>, <textarea> and <button> must be changed explicitly.
			$element.find( 'textarea, input, button' )
				.not( this.options.exclude )
				.css( 'font-family', fontStack.join() );
		},

		/**
		 * Load a given fontFamily if not loaded already
		 *
		 * @param fontFamily String font family name
		 */
		load: function( fontFamily ) {
			if ( $.inArray( fontFamily, this.fonts ) >= 0 ) {
				return true;
			}
			var styleString = this.getCSS( fontFamily );
			if ( styleString ) {
				injectCSS( styleString );
			} else {
				// Font not found
				return false;
			}
			this.fonts.push( fontFamily );
			return true;
		},

		/**
		 * Parse the element for custom font-family styles and for nodes with
		 * different language than element
		 */
		parse: function() {
			var webfonts = this;

			webfonts.$element.find( '*[lang], [style], [class]' ).each( function( i, element ) {
				var fontFamilyStyle, fontFamily,
					$element = $( element );

				fontFamilyStyle = $element.css( 'fontFamily' );

				if ( fontFamilyStyle ) {
					fontFamily = fontFamilyStyle.split( ',' )[0];

					// Remove the ' and " characters if any.
					fontFamily = $.trim( fontFamily.replace( /["']/g, '' ) );

					if ( webfonts.load( fontFamily ) ) {
						// Font family overrides the lang attribute,
						// but was it the fontfamily allocated for the current
						// language?
						if ( fontFamily === webfonts.getFont( element.lang ) ) {
							return true;
						}
					}
				}

				if ( element.lang && element.lang !== webfonts.$element.attr( 'lang' ) ) {
					fontFamily = webfonts.getFont( element.lang );
					webfonts.apply( fontFamily, $( element ) );
				}
			} );
		},

		/**
		 * List all fonts for the given language
		 *
		 * @param language mixed: [optional] language code. If undefined all
		 *            fonts will be listed
		 * @return Array font names array
		 */
		list: function( language ) {
			var fontName,
				fontNames = [];

			if ( language ) {
				fontNames = this.repository.languages[language] || [];
			} else {
				for ( fontName in this.repository.fonts ) {
					if ( this.repository.fonts.hasOwnProperty( fontName ) ) {
						fontNames.push( fontName );
					}
				}
			}

			return fontNames;
		},

		/**
		 * List all languages supported by the repository
		 *
		 * @return Array language codes
		 */
		languages: function() {
			var language,
				languages = [];

			for ( language in this.repository.languages ) {
				if ( this.repository.languages.hasOwnProperty( language ) ) {
					languages.push( language );
				}
			}
			return languages;
		},

		/**
		 * Set the font repository
		 *
		 * @param {Object} repository The font repository.
		 */
		setRepository: function( repository ) {
			this.repository = $.extend( WebFonts.repository, repository );
		},

		/**
		 * Reset the font-family style.
		 */
		reset: function() {
			this.apply( this.originalFontFamily );
		},

		/**
		 * unbind the plugin
		 */
		unbind: function() {
			this.$element.data( 'webfonts', null );
		},

		/**
		 * Construct the CSS required for the font-family, inject it to the head
		 * of the body so that it gets loaded.
		 *
		 * @param fontFamily The font-family name
		 * @param variant The font variant, eg: bold, italic etc. Default is
		 *            normal.
		 */
		getCSS: function( fontFamily, variant ) {
			var webfonts, fontconfig, base, version, versionSuffix, styleString, userAgent, fontStyle, fontFormats;

			webfonts = this;
			variant = variant || 'normal';
			fontconfig = this.repository.get( fontFamily );
			if ( variant !== 'normal' ) {
				if ( fontconfig.variants !== undefined && fontconfig.variants[variant] ) {
					fontconfig = this.repository.get( fontconfig.variants[variant] );
				}
			}
			if ( !fontconfig ) {
				return false;
			}

			base = this.repository.base;
			version = fontconfig.version;
			versionSuffix = "?version=" + version + '&20120101';
			styleString = "@font-face { font-family: '" + fontFamily + "';\n";
			userAgent = window.navigator.userAgent;
			fontStyle = fontconfig.fontstyle || 'normal';
			fontFormats = [];

			if ( fontconfig.eot ) {
				styleString += "\tsrc: url('" + base + fontconfig.eot + versionSuffix + "');\n";
			}
			styleString += "\tsrc: ";
			// If the font is present locally, use it.
			if ( userAgent.match( /Android 2\.3/ ) === null ) {
				// Android 2.3.x does not respect local() syntax.
				// http://code.google.com/p/android/issues/detail?id=10609
				styleString += "local('" + fontFamily + "'),";
			}
			if ( fontconfig.woff ) {
				fontFormats.push( "\t\turl('" + base + fontconfig.woff + versionSuffix
						+ "') format('woff')" );
			}
			if ( fontconfig.svg ) {
				fontFormats.push( "\t\turl('" + base + fontconfig.svg + versionSuffix + "#"
						+ fontFamily + "') format('svg')" );
			}
			if ( fontconfig.ttf ) {
				fontFormats.push( "\t\turl('" + base + fontconfig.ttf + versionSuffix
						+ "') format('truetype')" );
			}
			styleString += fontFormats.join() + ";\n";
			if ( fontconfig.fontweight ) {
				styleString += "\tfont-weight:" + fontconfig.fontweight + ";";
			}
			styleString += "\tfont-style:" + fontStyle + ";";

			if ( fontconfig.fontweight !== undefined ) {
				styleString += "\tfont-weight:" + fontconfig.fontweight + ";";
			}
			if ( fontconfig.fontstyle !== undefined ) {
				styleString += "\tfont-style:" + fontconfig.fontstyle + ";";
			} else {
				styleString += "\tfont-style: normal;";
			}

			styleString += "}";

			if ( fontconfig.variants !== undefined ) {
				$.each( fontconfig.variants, function ( variant, variantFontFamily ) {
					styleString += webfonts.getCSS( fontFamily, variant );
				} );
			}

			return styleString;
		}
	};

	$.fn.webfonts = function( option ) {
		return this.each( function() {
			var $this, data, options;
			$this = $( this );
			data = $this.data( 'webfonts' );
			options = typeof option === 'object' && option;
			if ( !data ) {
				$this.data( 'webfonts', ( data = new WebFonts( this, options ) ) );
			}
			if ( typeof option === 'string' ) {
				data[option]();
			}
		} );
	};

	$.fn.webfonts.defaults = {
		repository: WebFonts.repository, // Default font repository
		fontStack: [ 'Helvetica', 'Arial', 'sans-serif' ], // Default font fallback
		exclude: '' // jQuery selectors to exclude
	};

	$.fn.webfonts.Constructor = WebFonts;

	// Private methods for the WebFonts prototype
	// =================================

	/**
	 * Create a new style tag and add it to the DOM.
	 *
	 * @param text String: CSS text
	 * @return HTMLStyleElement
	 */
	function injectCSS( css ) {
		var s = document.createElement( 'style' );
		s.type = 'text/css';
		s.rel = 'stylesheet';
		// Insert into document before setting cssText
		document.getElementsByTagName( 'head' )[0].appendChild( s );
		if ( s.styleSheet ) {
			s.styleSheet.cssText = css;
			// IE
		} else {
			// Safari sometimes borks on null
			s.appendChild( document.createTextNode( String( css ) ) );
		}
		return s;
	}

} )( jQuery, window, document );
