var EventEmitter = require('events');
var subtitlesParser = require('./subtitlesParser');
var subtitlesRenderer = require('./subtitlesRenderer');
var colorConverter = require('./colorConverter');

var COLOR_REGEX = /^#[A-Fa-f0-9]{8}$/;
var ERROR_CODE = Object.freeze({
    FETCH_FAILED: 70,
    PARSE_FAILED: 71
});
var SIZE_COEF = 25;

function HTMLSubtitles(options) {
    var containerElement = options && options.containerElement;
    if (!(containerElement instanceof HTMLElement) || !containerElement.hasAttribute('id')) {
        throw new Error('Instance of HTMLElement with id attribute required');
    }
    if (!document.body.contains(containerElement)) {
        throw new Error('Container element not attached to body');
    }

    var destroyed = false;
    var events = new EventEmitter();
    var cuesByTime = null;
    var tracks = Object.freeze([]);
    var selectedTrackId = null;
    var delay = null;
    var stylesElement = document.createElement('style');
    var subtitlesElement = document.createElement('div');

    events.on('error', function() { });
    containerElement.appendChild(stylesElement);
    var subtitlesContainerStylesIndex = stylesElement.sheet.insertRule('#' + containerElement.id + ' .subtitles { position: absolute; right: 0; bottom: 0; left: 0; z-index: 0; text-align: center; }', stylesElement.sheet.cssRules.length);
    var subtitlesCueStylesIndex = stylesElement.sheet.insertRule('#' + containerElement.id + ' .subtitles .cue { display: inline-block; padding: 0.2em; text-shadow: 0 0 0.03em #222222ff, 0 0 0.03em #222222ff, 0 0 0.03em #222222ff, 0 0 0.03em #222222ff, 0 0 0.03em #222222ff; background-color: #00000000; color: #ffffffff; font-size: 4vmin; }', stylesElement.sheet.cssRules.length);
    stylesElement.sheet.insertRule('#' + containerElement.id + ' .subtitles .cue * { font-size: inherit; }', stylesElement.sheet.cssRules.length);
    containerElement.appendChild(subtitlesElement);
    subtitlesElement.classList.add('subtitles');

    function on(eventName, listener) {
        if (destroyed) {
            return;
        }

        events.on(eventName, listener);
    }
    function addTracks(extraTracks) {
        if (destroyed || !Array.isArray(extraTracks)) {
            return;
        }

        tracks = extraTracks
            .filter(function(track) {
                return track &&
                    typeof track.url === 'string' &&
                    track.url.length > 0 &&
                    typeof track.origin === 'string' &&
                    track.origin.length > 0 &&
                    track.origin !== 'EMBEDDED';
            })
            .map(function(track) {
                return Object.freeze(Object.assign({}, track, {
                    id: track.url
                }));
            })
            .concat(tracks)
            .filter(function(track, index, tracks) {
                for (var i = 0; i < tracks.length; i++) {
                    if (tracks[i].id === track.id) {
                        return i === index;
                    }
                }

                return false;
            });
        Object.freeze(tracks);
        events.emit('propChanged', 'tracks');
    }
    function updateText(mediaTime) {
        while (subtitlesElement.hasChildNodes()) {
            subtitlesElement.removeChild(subtitlesElement.lastChild);
        }

        if (cuesByTime === null || isNaN(mediaTime) || mediaTime === null) {
            return;
        }

        var time = mediaTime + delay;
        subtitlesRenderer.render(cuesByTime, time)
            .forEach(function(cueNode) {
                cueNode.classList.add('cue');
                subtitlesElement.append(cueNode, document.createElement('br'));
            });
    }
    function clearTracks() {
        updateText(NaN);
        cuesByTime = null;
        tracks = Object.freeze([]);
        selectedTrackId = null;
        delay = null;
        events.emit('propChanged', 'tracks');
        events.emit('propChanged', 'selectedTrackId');
        events.emit('propChanged', 'delay');
    }
    function destroy() {
        destroyed = true;
        clearTracks();
        events.emit('propChanged', 'size');
        events.emit('propChanged', 'textColor');
        events.emit('propChanged', 'backgroundColor');
        events.emit('propChanged', 'outlineColor');
        events.emit('propChanged', 'offset');
        events.removeAllListeners();
        events.on('error', function() { });
        containerElement.removeChild(stylesElement);
        containerElement.removeChild(subtitlesElement);
    }

    Object.defineProperties(this, {
        tracks: {
            configurable: false,
            enumerable: true,
            get: function() {
                return Object.freeze(tracks.slice());
            }
        },
        selectedTrackId: {
            configurable: false,
            enumerable: true,
            get: function() {
                return selectedTrackId;
            },
            set: function(value) {
                if (destroyed) {
                    return;
                }

                cuesByTime = null;
                selectedTrackId = null;
                delay = null;
                updateText(NaN);
                var selecterdTrack = tracks.find(function(track) {
                    return track.id === value;
                });
                if (selecterdTrack) {
                    selectedTrackId = selecterdTrack.id;
                    delay = 0;
                    fetch(selecterdTrack.url)
                        .then(function(resp) {
                            return resp.text();
                        })
                        .catch(function(error) {
                            events.emit('error', Object.freeze({
                                code: ERROR_CODE.FETCH_FAILED,
                                message: 'Failed to fetch subtitles from ' + selecterdTrack.origin,
                                track: selecterdTrack,
                                error: error
                            }));
                        })
                        .then(function(text) {
                            if (typeof text === 'string' && selectedTrackId === selecterdTrack.id) {
                                cuesByTime = subtitlesParser.parse(text);
                                if (cuesByTime.times.length === 0) {
                                    throw new Error('parse failed');
                                }

                                events.emit('trackLoaded', selecterdTrack);
                            }
                        })
                        .catch(function(error) {
                            events.emit('error', Object.freeze({
                                code: ERROR_CODE.PARSE_FAILED,
                                message: 'Failed to parse subtitles from ' + selecterdTrack.origin,
                                track: selecterdTrack,
                                error: error
                            }));
                        });
                }

                events.emit('propChanged', 'selectedTrackId');
                events.emit('propChanged', 'delay');
            }
        },
        delay: {
            configurable: false,
            enumerable: true,
            get: function() {
                return delay;
            },
            set: function(value) {
                if (destroyed || isNaN(value) || value === null || selectedTrackId === null) {
                    return;
                }

                delay = parseInt(value);
                updateText(NaN);
                events.emit('propChanged', 'delay');
            }
        },
        size: {
            configurable: false,
            enumerable: true,
            get: function() {
                if (destroyed) {
                    return null;
                }

                return parseInt(stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.fontSize) * SIZE_COEF
            },
            set: function(value) {
                if (destroyed || isNaN(value) || value === null) {
                    return;
                }

                stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.fontSize = Math.floor(value / SIZE_COEF) + 'vmin';
                events.emit('propChanged', 'size');
            }
        },
        offset: {
            configurable: false,
            enumerable: true,
            get: function() {
                if (destroyed) {
                    return null;
                }

                return parseInt(stylesElement.sheet.cssRules[subtitlesContainerStylesIndex].style.bottom);
            },
            set: function(value) {
                if (destroyed || isNaN(value) || value === null) {
                    return;
                }

                stylesElement.sheet.cssRules[subtitlesContainerStylesIndex].style.bottom = Math.max(0, Math.min(100, parseInt(value))) + '%';
                events.emit('propChanged', 'offset');
            }
        },
        textColor: {
            configurable: false,
            enumerable: true,
            get: function() {
                if (destroyed) {
                    return null;
                }

                return colorConverter.rgbaToHex(stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.color);
            },
            set: function(value) {
                if (destroyed || typeof value !== 'string' || value.length !== 9 || !value.match(COLOR_REGEX)) {
                    return;
                }

                stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.color = value;
                events.emit('propChanged', 'textColor');
            }
        },
        backgroundColor: {
            configurable: false,
            enumerable: true,
            get: function() {
                if (destroyed) {
                    return null;
                }

                return colorConverter.rgbaToHex(stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.backgroundColor);
            },
            set: function(value) {
                if (destroyed || typeof value !== 'string' || value.length !== 9 || !value.match(COLOR_REGEX)) {
                    return;
                }

                stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.backgroundColor = value;
                events.emit('propChanged', 'backgroundColor');
            }
        },
        outlineColor: {
            configurable: false,
            enumerable: false,
            get: function() {
                if (destroyed) {
                    return null;
                }

                return colorConverter.rgbaToHex(stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.textShadow);
            },
            set: function(value) {
                if (destroyed || typeof value !== 'string' || value.length !== 9 || !value.match(COLOR_REGEX)) {
                    return;
                }

                stylesElement.sheet.cssRules[subtitlesCueStylesIndex].style.textShadow =
                    value + ' 0 0 0.03em,' +
                    value + ' 0 0 0.03em,' +
                    value + ' 0 0 0.03em,' +
                    value + ' 0 0 0.03em,' +
                    value + ' 0 0 0.03em';
                events.emit('propChanged', 'outlineColor');
            }
        }
    });

    this.on = on;
    this.addTracks = addTracks;
    this.updateText = updateText;
    this.clearTracks = clearTracks;
    this.destroy = destroy;

    Object.freeze(this);
};

Object.freeze(HTMLSubtitles);

module.exports = HTMLSubtitles;
