﻿package {

	import flash.display.*;
	import flash.events.*;
	import flash.media.*;
	import flash.net.*;
	import flash.utils.setTimeout;
	import flash.system.*;
	import flash.external.*;

	import org.mangui.hls.HLS;
	import org.mangui.hls.HLSSettings;
	import org.mangui.hls.event.HLSEvent;
	import org.mangui.hls.constant.HLSPlayStates;
	import org.mangui.hls.utils.Log;

	public class HlsMediaElement extends Sprite {

		private var _playqueued: Boolean = false;
		private var _autoplay: Boolean = false;
		private var _hls: HLS;
		private var _url: String;
		private var _video: Video;
		private var _hlsState: String = HLSPlayStates.IDLE;

		private var _position: Number = 0;
		private var _duration: Number = 0;
		private var _isManifestLoaded: Boolean = false;
		private var _isPaused: Boolean = true;
		private var _isEnded: Boolean = false;
		private var _volume: Number = 1;
		private var _isMuted: Boolean = false;
		private var _readyState:Number = 0;

		private var _bufferedTime: Number = 0;
		private var _videoWidth: Number = -1;
		private var _videoHeight: Number = -1;

		private var _id: String;

		private var _stageWidth: Number;
		private var _stageHeight: Number;

		/**
		 * @constructor
		 */
		public function HlsMediaElement() {

			if (isIllegalQuerystring()) {
				return;
			}

			var flashVars: Object = LoaderInfo(this.root.loaderInfo).parameters;

			// Use this for CDN
			if (flashVars.allowScriptAccess == 'always') {
				Security.allowDomain(['*']);
				Security.allowInsecureDomain(['*']);
			}

			_id = flashVars.uid;
			_autoplay = (flashVars.autoplay == true);

			// stage setup
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			_stageWidth = stage.stageWidth;
			_stageHeight = stage.stageHeight;

			stage.addEventListener(MouseEvent.MOUSE_DOWN, stageClickHandler);
			stage.addEventListener(MouseEvent.MOUSE_OVER, stageMouseOverHandler);
			stage.addEventListener(Event.MOUSE_LEAVE, stageMouseLeaveHandler);
			stage.addEventListener(Event.RESIZE, fire_setSize);

			// video setup
			_display = new Sprite();
			addChild(_display);

			_video = new Video();
			(_video as Video).smoothing = true;
			_display.addChild(_video);
			_display.addEventListener(MouseEvent.MOUSE_OVER, stageMouseOverHandler);

			_display.x = _video.x = 0;
			_display.y = _video.y = 0;
			_display.width = _video.width = _stageWidth;
			_display.height = _video.height = _stageHeight;


			// Settings to be overridden for HLS
			HLSSettings.logInfo = false;

			_hls = new HLS();
			_hls.addEventListener(HLSEvent.MANIFEST_LOADED, _manifestHandler);
			_hls.addEventListener(HLSEvent.LEVEL_LOADING, _levelLoadingHandler);
			_hls.addEventListener(HLSEvent.LEVEL_LOADED, _levelLoadedHandler);
			_hls.addEventListener(HLSEvent.LEVEL_SWITCH, _levelSwitchHandler);
			_hls.addEventListener(HLSEvent.LEVEL_ENDLIST, _levelEndlistHandler);
			_hls.addEventListener(HLSEvent.FRAGMENT_LOADING, _fragmentLoadingHandler);
			_hls.addEventListener(HLSEvent.FRAGMENT_LOADED, _fragmentLoadedHandler);
			_hls.addEventListener(HLSEvent.FRAGMENT_PLAYING, _fragmentPlayingHandler);
			_hls.addEventListener(HLSEvent.FRAGMENT_SKIPPED, _fragmentSkippedHandler);
			_hls.addEventListener(HLSEvent.AUDIO_TRACKS_LIST_CHANGE, _audioTracksListChangeHandler);
			_hls.addEventListener(HLSEvent.AUDIO_TRACK_SWITCH, _audioTracksSwitchHandler);
			_hls.addEventListener(HLSEvent.AUDIO_LEVEL_LOADING, _audioLevelLoadingHandler);
			_hls.addEventListener(HLSEvent.AUDIO_LEVEL_LOADED, _audioLevelLoadedHandler);
			_hls.addEventListener(HLSEvent.TAGS_LOADED, _tagsLoadedHandler);
			_hls.addEventListener(HLSEvent.MEDIA_TIME, _mediaTimeHandler);
			_hls.addEventListener(HLSEvent.PLAYBACK_STATE, _stateHandler);
			_hls.addEventListener(HLSEvent.PLAYBACK_COMPLETE, _completeHandler);
			_hls.addEventListener(HLSEvent.SEEK_STATE, _seekHandler);
			_hls.addEventListener(HLSEvent.PLAYLIST_DURATION_UPDATED, _playlistDurationUpdatedHandler);
			_hls.addEventListener(HLSEvent.ID3_UPDATED, _id3UpdatedHandler);
			_hls.addEventListener(HLSEvent.LIVE_LOADING_STALLED, _liveLoadingStalledHandler);
			_hls.addEventListener(HLSEvent.ERROR, _errorHandler);

			_hls.stream.soundTransform = new SoundTransform(_volume);
			_video.attachNetStream(_hls.stream);

			if (ExternalInterface.available) {
				ExternalInterface.addCallback('get_src', get_src);
				ExternalInterface.addCallback('get_volume', get_volume);
				ExternalInterface.addCallback('get_currentTime', get_currentTime);
				ExternalInterface.addCallback('get_muted', get_muted);
				ExternalInterface.addCallback('get_buffered', get_buffered);
				ExternalInterface.addCallback('get_duration', get_duration);
				ExternalInterface.addCallback('get_paused', get_paused);
				ExternalInterface.addCallback('get_ended', get_ended);
				ExternalInterface.addCallback('get_readyState', get_readyState);

				ExternalInterface.addCallback('set_src', set_src);
				ExternalInterface.addCallback('set_volume', set_volume);
				ExternalInterface.addCallback('set_currentTime', set_currentTime);
				ExternalInterface.addCallback('set_muted', set_muted);

				ExternalInterface.addCallback('fire_load', fire_load);
				ExternalInterface.addCallback('fire_play', fire_play);
				ExternalInterface.addCallback('fire_pause', fire_pause);
				ExternalInterface.addCallback('fire_setSize', fire_setSize);
				ExternalInterface.addCallback('fire_stop', fire_stop);

				ExternalInterface.call('(function(){window["__ready__' + _id + '"]()})()', null);
			}
		}

		private function isIllegalQuerystring():Boolean {
			var query:String = '';
			var pos:Number = root.loaderInfo.url.indexOf('?') ;

			if ( pos > -1 ) {
			    query = root.loaderInfo.url.substring( pos );
			    if ( ! /^\?\d+$/.test( query ) ) {
			        return true;
			    }
			}

			return false;
		}

		//
		// Javascript bridged methods
		//
		private function fire_load(): void {
			if (_url) {
				sendEvent("loadstart");
				_hls.load(_url);
			}
		}
		private function fire_play(): void {
			if (!_isManifestLoaded) {
				fire_load();
				return;
			}

			if (_hlsState == HLSPlayStates.PAUSED || _hlsState == HLSPlayStates.PAUSED_BUFFERING) {
				_hls.stream.resume();
			} else {
				_hls.stream.play();
			}

		}
		private function fire_pause(): void {
			if (!_isManifestLoaded) {
				return;
			}
			_hls.stream.pause();
		}
		private function fire_stop(): void {
			_hls.stream.close();
			_video.clear();

			_isManifestLoaded = false;
			_duration = 0;
			_position = 0;
			_playqueued = false;

			sendEvent("stop");
		}
		private function fire_setSize(width: Number=-1, height: Number=-1): void {
			var fill:Boolean = false;
			var contWidth:Number;
			var contHeight:Number;
			var stageRatio:Number;
			var nativeRatio:Number;

			_video.x = 0;
			_video.y = 0;
			contWidth = stage.stageWidth;
			contHeight = stage.stageHeight;

			if(width == -1){
				width = _video.width;
			}
			if(height == -1){
				height = _video.height;
			}

			if (width <= 0 || height <= 0) {
				fill = true;
			}

			if (fill) {
				_video.width = width;
				_video.height = height;
			} else {
				stageRatio = contWidth/contHeight;
				nativeRatio = _videoWidth/_videoHeight;
				// adjust size and position
				if (nativeRatio > stageRatio) {
					_video.width = contWidth;
					_video.height =  _videoHeight * contWidth / _videoWidth;
					_video.y = contHeight/2 - _video.height/2;
				} else if (stageRatio > nativeRatio) {
					_video.width = _videoWidth * contHeight / _videoHeight;
					_video.height =  contHeight;
					_video.x = contWidth/2 - _video.width/2;
				} else if (stageRatio == nativeRatio) {
					_video.width = contWidth;
					_video.height = contHeight;
				}
			}
		}

		//
		// Setters
		//
		private function set_src(url: String): void {
			fire_stop();
			_url = url;
			_hls.load(_url);
		}
		private function set_currentTime(pos: Number): void {
			if (!_isManifestLoaded) {
				return;
			}

			sendEvent("seeking");
			_hls.stream.seek(pos);
		}
		private function set_volume(vol: Number): void {
			_volume = vol;
			_isMuted = (_volume == 0);
			_hls.stream.soundTransform = new SoundTransform(vol);
			sendEvent("volumechange");
		}
		private function set_muted(muted: Boolean): void {
			// ignore if no change
			if (muted === _isMuted) {
				return;
			}

			_isMuted = muted;

			if (muted) {
				_hls.stream.soundTransform = new SoundTransform(0);
			} else {
				set_volume(_volume);
			}
			sendEvent("volumechange");
		}

		//
		// Getters
		//
		private function get_src(): String {
			return _url;
		}
		private function get_currentTime(): Number {
			return _position;
		}
		private function get_volume(): Number {
			return _isMuted ? 0 :_volume;
		}
		private function get_muted(): Boolean {
			return _isMuted;
		}
		private function get_paused(): Boolean {
			return _isPaused;
		}
		private function get_ended(): Boolean {
			return _isEnded;
		}
		private function get_duration(): Number {
			return _duration;
		}
		private function get_buffered(): Number {
			var progress: Number = 0;
			if (_duration != 0) {
				progress = Math.round((_bufferedTime / _duration) * 100);
			}
			return progress;
		}
		private function get_readyState():Number {
			return _readyState;
		}

		//
		// Events
		//
		private function _completeHandler(): void {
			_isEnded = true;
			_isPaused = true;
			sendEvent('ended');
		}
		private function _errorHandler(event: HLSEvent): void {
			sendEvent('error', event.error.toString());
		}
		private function _levelLoadingHandler(event: HLSEvent): void {
			sendEvent('onLevelLoading', JSON.stringify(event));
		}
		private function _levelLoadedHandler(event: HLSEvent): void {
			sendEvent('onLevelLoaded', JSON.stringify(event.loadMetrics));
		}
		private function _levelSwitchHandler(event: HLSEvent): void {
			sendEvent('onLevelSwitch', JSON.stringify(event));
		}
		private function _levelEndlistHandler(): void {
			sendEvent('onLevelEndlist');
		}
		private function _fragmentLoadingHandler(event: HLSEvent): void {
			sendEvent('onFragmentLoading', JSON.stringify(event));
		}
		private function _fragmentLoadedHandler(event: HLSEvent): void {
			sendEvent('onFragmentLoaded', JSON.stringify(event.loadMetrics));
		}
		private function _fragmentPlayingHandler(event: HLSEvent): void {
			sendEvent('onFragmentPlaying', JSON.stringify(event.playMetrics));
		}
		private function _fragmentSkippedHandler(event: HLSEvent): void {
			sendEvent('onFragmentPlaying', JSON.stringify(event));
		}
		private function _audioTracksListChangeHandler(): void {
			sendEvent('onAudioTracksListChange');
		}
		private function _audioTracksSwitchHandler(): void {
			sendEvent('onAudioTracksSwitch');
		}
		private function _audioLevelLoadingHandler(event: HLSEvent): void {
			sendEvent('onAudioLevelLoading', JSON.stringify(event));
		}
		private function _audioLevelLoadedHandler(event: HLSEvent): void {
			sendEvent('onAudioLevelLoaded', JSON.stringify(event.loadMetrics));
		}
		private function _tagsLoadedHandler(event: HLSEvent): void {
			sendEvent('onTagsLoaded', JSON.stringify(event.loadMetrics));
		}
		private function _seekHandler(event: HLSEvent): void {
			sendEvent('onSeek', JSON.stringify(event));
		}
		private function _playlistDurationUpdatedHandler(event: HLSEvent): void {
			sendEvent('onPlaylistDurationUpdated', JSON.stringify(event));
		}
		private function _id3UpdatedHandler(event: HLSEvent): void {
			sendEvent('onID3Updated', JSON.stringify(event.ID3Data));
		}
		private function _liveLoadingStalledHandler(): void {
			sendEvent('onLiveLoadingStalled');
		}
		private function _manifestHandler(event: HLSEvent): void {
			_duration = event.levels[0].duration;
			_videoWidth = event.levels[0].width;
			_videoHeight = event.levels[0].height;
			_isManifestLoaded = true;
			_hls.stage = _video.stage;

			sendEvent("loadedmetadata");
			sendEvent("canplay");
			_readyState = 4;
			//Set Size on loaded metadata
			fire_setSize();

			if (_playqueued) {
				_playqueued = false;
			}

			if (_autoplay) {
				_hls.stream.play();
			}
			sendEvent('onManifestLoaded', JSON.stringify(event.levels));
		}
		private function _mediaTimeHandler(event: HLSEvent): void {
			_position = event.mediatime.position;
			_duration = event.mediatime.duration;
			_bufferedTime = event.mediatime.buffer + event.mediatime.position;

			var videoWidth:int = _video.videoWidth;
			var videoHeight:int = _video.videoHeight;
			var neverSetted:Boolean = _videoWidth <= 0 || _videoHeight <= 0;
			if (videoWidth && videoHeight && neverSetted) {
				_videoHeight = videoHeight;
				_videoWidth = videoWidth;
				fire_setSize(_videoWidth, _videoHeight);
			}

			if (parseInt(_position) >= parseInt(_duration) && !_isEnded) {
				_isPaused = true;
				_isEnded = true;
				sendEvent("pause");
				sendEvent("ended");
			}

			sendEvent("progress");
			sendEvent("timeupdate");
		}
		private function _stateHandler(event: HLSEvent): void {
			_hlsState = event.state;
			switch (event.state) {
				case HLSPlayStates.PLAYING:
					_isPaused = false;
					_isEnded = false;
					_video.visible = true;
					sendEvent("play");
					sendEvent("playing");
					break;
				case HLSPlayStates.PAUSED:
					_isPaused = true;
					_isEnded = false;
					sendEvent("pause");
					break;
				case HLSPlayStates.IDLE:
					if (parseInt(_position) >= parseInt(_duration) && !_isEnded) {
						_isPaused = true;
						_isEnded = true;
						sendEvent("pause");
						sendEvent("ended");
					}
					break;
			}
		}

		//
		// Event handlers
		//
		private function stageClickHandler(e: MouseEvent): void {
			sendEvent("click");
		}
		private function stageMouseOverHandler(e: MouseEvent): void {
			sendEvent("mouseover");
		}
		private function stageMouseLeaveHandler(e: Event): void {
			sendEvent("mouseout");
			sendEvent("mouseleave");
		}

		//
		// Utilities
		//
		private function sendEvent(eventName: String, data: String = ''): void {
			ExternalInterface.call('(function(){window["__event__' +  _id + '"]("' + eventName + '", \'' + data + '\')})()', null);
		}
		private function log(): void {
			if (ExternalInterface.available) {
				ExternalInterface.call('console.log', arguments);
			} else {
				trace(arguments);
			}
		}
	}
}
