import React from 'react';
import PropTypes from 'prop-types';

import classNames from 'classnames';
import IDUtil from '../../util/IDUtil';
import RegexUtil from '../../util/RegexUtil';
import TranscriptUtil from '../../util/TranscriptUtil';
import FlexPlayerUtil from '../../util/FlexPlayerUtil';

import AVPlayer from './mediaColumn/AVPlayer';
import ImageViewer from './mediaColumn/ImageViewer';
import PlaylistDropdown from './mediaColumn/PlaylistDropdown';
import SimpleWordCloud from './mediaColumn/SimpleWordCloud';

import ColumnHeader from './ColumnHeader';
import BaseColumn from './BaseColumn';
import Info from '../shared/Info';

import TierView from './mediaColumn/TierView';
import Timeline from './mediaColumn/timeline/Timeline';
import SegmentActions from './mediaColumn/tiers/SegmentActions';

import MediaEvents from './_MediaEvents';
import Strings from './_Strings';

import {AnnotationEvents} from './AnnotationClient';

import { ResourceViewerContext } from './ResourceViewerContext';

/*
    This component takes on 3 possible playlists (for video, audio & images). From that it generates"
    - a playlist for given media objects (segments will be added to the timeline; not part of the playlist)
	- viewer for the active media object
    - viewer
        - video: player + timeline
        - audio: player + timeline
        - image: image viewer?

	Playlists for testing

	http://0.0.0.0:5304/tool/resource-viewer?id=FLM77223&cid=eye-desmet-films&st=werk
	http://0.0.0.0:5304/tool/resource-viewer?id=482379@program&cid=nisv-catalogue-aggr&st=%22openbaar%20vervoer%22
	http://0.0.0.0:5304/tool/resource-viewer?id=50263@program&cid=nisv-catalogue-aggr
	http://0.0.0.0:5304/tool/resource-viewer?id=5313446@program&cid=nisv-catalogue-aggr&st=aardkloot
	http://0.0.0.0:5304/tool/resource-viewer?id=49689@program&cid=nisv-catalogue-aggr-18-158
	https://mediasuite.xlab.nl/tool/resource-viewer?id=5176026@program&cid=nisv-catalogue-aggr
	https://mediasuite-test.rdlabs.beeldengeluid.nl/tool/resource-viewer?id=5230684@program&cid=nisv-catalogue-aggr&st=wereld
	http://mediasuite.clariah.nl/tool/resource-viewer?id=498233@program&cid=nisv-catalogue-aggr&st=smaaknu

*/

export default class MediaColumn extends React.Component {
    static contextType = ResourceViewerContext;

    constructor(props) {
        super(props);
        this.state = {
            playerAPI: null, // called after an AVPlayer has loaded a new mediaObject (via onPlayerReady)
            duration: -1,
            showPlaylist: false
        };

        this.videoPlayerRef = React.createRef(); // used to call the setActiveMediaObject() on the video player (when clicking a playlist item)
        this.timelineRef = React.createRef();
    }

    componentDidMount() {
        this.context.mediaEvents.bind(
            MediaEvents.PLAYER_POS,
            this.updateTimelinePosition
        );
        this.context.annotationClient.events.bind(
            AnnotationEvents.ON_SET_SELECTION,
            this.onSetSelection
        );
    }

    componentWillUnmount() {
        this.context.mediaEvents.unbind(
            MediaEvents.SET_PLAYER_POS,
            this.onPlayerSeek
        );
        this.context.mediaEvents.unbind(
            MediaEvents.PLAYER_POS,
            this.updateTimelinePosition
        );
    }

    /* ----------------------------- COMPONENT INTERACTION FUNCTIONS ------------------------ */

    setActiveMediaObject = mediaObject => {
        this.context.setActiveMediaObject(mediaObject); // communicate the new active media object to the context

        if (this.videoPlayerRef.current) {
            this.videoPlayerRef.current.setActiveMediaObject(mediaObject); // directly call the player with the selected object
        } else {
            console.debug('AVPlayer not rendered when component did mount?');
        }

        this.setState({ showPlaylist: false });
    };

    // Receives a playerAPI after the AVPlayer was loaded
    onPlayerReady = playerAPI => {
        this.setState({ playerAPI: playerAPI }, () => {
            // reset, and register the SET_PLAYER_POS event in the mediaEvents
            // so other components can directly control the player
            this.context.mediaEvents
                .reset(MediaEvents.SET_PLAYER_POS)
                .bind(MediaEvents.SET_PLAYER_POS, this.onPlayerSeek);
        });
    };

    // Seek on player and timeline
    onPlayerSeek = pos => {
        if (!this.context.activeMediaObject || !this.state.playerAPI) {
            return;
        }
        this.onAirSeek(pos);
    };

    onAirSeek = pos => {
        FlexPlayerUtil.seekRelativeToOnAir(
            this.state.playerAPI,
            pos,
            this.context.activeMediaObject
        );
    };

    //Here you can delegate time to other components via a ref (faster than using state)
    onGetPosition = realPos => {
        if (this.context.activeMediaObject) {
            // the relative position is used to shield off off-air content
            const relativePosition = FlexPlayerUtil.timeRelativeToOnAir(
                realPos,
                this.context.activeMediaObject
            );

            // Trigger event
            this.context.mediaEvents.trigger(
                MediaEvents.PLAYER_POS,
                relativePosition
            );

            // Trigger RAW event
            this.context.mediaEvents.trigger(
                MediaEvents.PLAYER_POS_RAW,
                realPos
            );
        }
    };

    onGetDuration = onAirDuration => this.setState({ duration: onAirDuration }); // whenever the player calculated a duration (required for drawing tiers/timeline)

    /* ----------------------------- PLAYLIST FUNCTIONS ------------------------ */

    // Toggle playlist visiblity
    togglePlaylist = () => {
        this.setState({ showPlaylist: !this.state.showPlaylist });
    };

    onPlaylistNext = () => {
        this.navPlaylist(1);
    };

    onPlaylistPrev = () => {
        this.navPlaylist(-1);
    };

    // Navigate playlist
    navPlaylist = diff => {
        if (!this.context.activeMediaObject) {
            return;
        }
        const activeId = this.context.activeMediaObject.assetId;
        let activeIndex = 0;
        this.props.mediaObjects.some((mediaObject, index) => {
            if (mediaObject.assetId == activeId) {
                activeIndex = index;
                return true;
            }
            return false;
        });

        // Use diff to set a new active MediaObject
        this.setActiveMediaObject(
            this.props.mediaObjects[
                (activeIndex + diff + this.props.mediaObjects.length) %
                    this.props.mediaObjects.length
            ],
            null
        );
    };

    /* ----------------------------- ANNOTATION CLIENT EVENTS ------------------------ */

    onSetSelection = () => this.forceUpdate();

    /* ----------------------------- RENDER FUNCTIONS ------------------------ */

    //renders the playlist for the list of video/audio mediaObjects (test with: ?id=FLM77223&cid=eye-desmet-films)
    renderAVPlaylist = (
        mediaObjects,
        transcripts,
        initialSearchTerm,
        activeId
    ) => {
        if (
            !mediaObjects ||
            mediaObjects.length === 0 ||
            (mediaObjects.length === 1 && !mediaObjects[0].segments)
        )
            return null;

        return (
            <PlaylistDropdown
                mediaObjects={mediaObjects}
                transcriptMatches={TranscriptUtil.calcTranscriptMatchesPerMediaObject(
                    mediaObjects,
                    transcripts,
                    initialSearchTerm
                )}
                activeId={activeId}
                onSelect={this.setActiveMediaObject}
            />
        );
    };

    renderImagePlaylist = (mediaObjects, initialSearchTerm) =>
        console.debug('Implement this for flipping through images');

    renderAVPlayer = (activeMediaObject, collectionConfig) => {
        return (
            <AVPlayer
                ref={this.videoPlayerRef}
                mediaObject={activeMediaObject} // instance of MediaObject; TODO remove playlist from the AVPlayer
                useCredentials={collectionConfig.requiresPlayoutAccess()} // whether the player needs to send a cookie or not
                hideOffAirContent={collectionConfig.hideOffAirContent()} // whether to hide start- & end offsets or not
                onPlayerReady={this.onPlayerReady}
                onGetPosition={this.onGetPosition}
                onGetDuration={this.onGetDuration}
            />
        );
    };

    //TODO this is currently being wired up
    renderImageViewer = (activeMediaObject, collectionConfig, mediaObjects) => {
        if (!activeMediaObject || !mediaObjects) return null;
        if (mediaObjects.findIndex(mo => mo.cors === false) === -1) {
            //image viewer requires CORS
            //TODO rewrite with just the annotationClient as prop
            return (
                <ImageViewer
                    activeMediaObject={activeMediaObject}
                    mediaObjects={mediaObjects} //image viewer uses an internal playlist (for now)
                    useCredentials={collectionConfig.requiresPlayoutAccess()}
                    annotationClient={this.context.annotationClient}
                />
            );
        } else {
            //just return a list of img tags
            return mediaObjects.map(mo => <img src={mo.url} />);
        }
    };

    // Renders a player depending on the given mediaobject
    renderPlayer = (
        activeMediaObject,
        collectionConfig,
        mediaObjects = null
    ) => {
        const mediaType = activeMediaObject.mimeType.split('/')[0];

        switch (mediaType) {
            case 'video':
                return this.renderAVPlayer(activeMediaObject, collectionConfig);

            case 'audio':
                return this.renderAVPlayer(activeMediaObject, collectionConfig);

            case 'image':
                return this.renderImageViewer(
                    activeMediaObject,
                    collectionConfig,
                    mediaObjects
                );
            case '':
            case undefined:
            case null:
                return (
                    <div className="error">
                        No Media Object available for this resource
                    </div>
                );
            default:
                return (
                    <div className="error">
                        Error: Unknown media type: {mediaType}
                    </div>
                );
        }
    };

    renderTierView = (playerAPI, duration) => {
        if (!playerAPI || duration <= 0) return null;
        return <TierView playerAPI={playerAPI} duration={duration} />;
    };

    renderTimeline = (mediaObject, end, mediaEvents, resource) => {
        if (!mediaObject || end == -1 || !resource) {
            return null;
        }

        // segments
        const segmentLayer = {
            id: 'layer-segments',
            title: Strings.TIMELINE_LAYER_SEGMENTS_TITLE,
            description: Strings.TIMELINE_LAYER_SEGMENTS_HELP,
            className: 'segment',
            sections: mediaObject.segments
                ? mediaObject.segments
                      .filter(segment => !segment.programSegment)
                      .sort((a, b) => a.start - b.start)
                      .map((segment, index) => ({
                          id: 'tl_seg_' + index,
                          start: FlexPlayerUtil.timeRelativeToOnAir(
                              segment.start,
                              mediaObject
                          ),
                          end: FlexPlayerUtil.timeRelativeToOnAir(
                              segment.end,
                              mediaObject
                          ),
                          rawData: segment.title
                              ? segment.title.toLowerCase()
                              : '',
                          data: <span>{segment.title}</span>
                      }))
                : []
        };

        // get raw transcript
        const transcript = TranscriptUtil.getTranscript(
            resource.rawData.layer__asr || null,
            mediaObject
        );

        // ARS sentences
        const asrSentenceLayer = {
            id: 'layer-asr-data',
            title: Strings.TIMELINE_LAYER_ASR_SENTENCES_TITLE,
            description: Strings.TIMELINE_LAYER_ASR_SENTENCES_HELP,
            className: 'asr-sentence',
            sections: transcript
                ? transcript.map(item => ({
                      id: 'tl_asr_se_' + item.sequenceNr,
                      start: FlexPlayerUtil.timeRelativeToOnAir(
                          item.start / 1000,
                          mediaObject
                      ),
                      end: item.wordTimes
                          ? FlexPlayerUtil.timeRelativeToOnAir(
                                item.wordTimes[item.wordTimes.length - 1] /
                                    1000,
                                mediaObject
                            )
                          : start,
                      rawData: item.words ? item.words.toLowerCase() : '',
                      data: (
                          <div
                              title={item.words}
                              style={{
                                  opacity: item.wordTimes
                                      ? Math.min(1, item.wordTimes.length / 40)
                                      : 0.1
                              }}
                          ></div>
                      )
                  }))
                : []
        };

        // ASR wordcloud every %step% seconds
        const step = 30; //sec -- if you change this line; you may want to update the description in _Strings.js
        const buckets = {};
        let wordTimes = [];
        let words = [];

        // collect all words/wordtimes
        if (transcript) {
            transcript.forEach(item => {
                words = words.concat(item.words.split(' '));
                wordTimes = wordTimes.concat(item.wordTimes);
            });
        }

        // create buckets from wordtimes
        wordTimes.forEach((t, index) => {
            // relative time
            t = FlexPlayerUtil.timeRelativeToOnAir(t / 1000, mediaObject);

            const bucketIndex = Math.floor(t / step);

            // create bucket
            if (!(bucketIndex in buckets)) {
                buckets[bucketIndex] = {
                    id: 'tl_asr_wo_' + bucketIndex,
                    start: bucketIndex * step,
                    end: bucketIndex * step + step,
                    words: []
                };
            }

            // add word
            buckets[bucketIndex].words.push(words[index]);
        });

        // create sections from buckets
        const bucketSections = Object.values(buckets).map(bucket => {
            bucket.rawData = bucket.words.join(' ').toLowerCase();
            bucket.data = <SimpleWordCloud size={20} words={bucket.words} />;
            return bucket;
        });

        // create the ASR words layer
        const asrWordsLayer = {
            id: 'layer-asr-words',
            title: Strings.TIMELINE_LAYER_ASR_WORDS_TITLE,
            description: Strings.TIMELINE_LAYER_ASR_WORDS_HELP,
            className: 'asr-words',
            height: 150,
            sections: bucketSections
        };

        // Combine layers
        const layers = [];
        segmentLayer.sections.length && layers.push(segmentLayer);
        asrSentenceLayer.sections.length && layers.push(asrSentenceLayer);
        asrWordsLayer.sections.length && layers.push(asrWordsLayer);

        if (layers.length === 0) {
            return null;
        }

        // render timeline
        return (
            <Timeline
                key={mediaObject.assetId}
                ref={this.timelineRef}
                start={FlexPlayerUtil.timeRelativeToOnAir(0, mediaObject)}
                end={end}
                viewStart={0}
                viewEnd={
                    // always show full first segment, or 120 sec
                    segmentLayer && segmentLayer.sections.length > 0
                        ? segmentLayer.sections[0].end
                        : 120 < end
                        ? 120
                        : end
                }
                layers={layers}
                setPosition={position => {
                    mediaEvents.trigger(MediaEvents.SET_PLAYER_POS, position);
                }}
            />
        );
    };

    updateTimelinePosition = position => {
        if (this.timelineRef.current) {
            this.timelineRef.current.setPositionExternal(position);
        }
    };

    renderHeader(togglePlaylist, activeAssetId, down, onNext, onPrev) {
        return (
            <ColumnHeader>
                <div className="playlist">
                    {/* Playlist label */}
                    <strong>
                        <Info
                            id="playlist_info"
                            text={Strings.PLAYLIST_HELP}
                            className="black left"
                        />
                        {Strings.PLAYLIST_TITLE}
                    </strong>

                    {/* Active media object (selector) */}
                    <span
                        className={classNames('active', { down })}
                        onClick={togglePlaylist}
                    >
                        {activeAssetId}
                    </span>
                </div>

                {/* Playlist actions */}
                <div className="actions">
                    <button
                        className="btn prev"
                        title={Strings.PLAYLIST_PREV_TITLE}
                        onClick={onPrev}
                    ></button>
                    <button
                        className="btn next"
                        title={Strings.PLAYLIST_NEXT_TITLE}
                        onClick={onNext}
                    ></button>
                </div>
            </ColumnHeader>
        );
    }

    render() {
        // Render player based on the selected media type
        // optionally add a class for locked content
        const player = this.context.activeMediaObject ? (
            <div
                className={classNames('player', {
                    locked:
                        this.context.collectionConfig.requiresPlayoutAccess() &&
                        !this.context.activeMediaObject.playoutAccess
                })}
            >
                {this.renderPlayer(
                    this.context.activeMediaObject,
                    this.context.collectionConfig,
                    this.props.mediaObjects
                )}
            </div>
        ) : null;

        // Renders the old segmentation & annotation timeline, but later is replaced by the new tiered view
        const tierView = this.renderTierView(
            this.state.playerAPI,
            this.state.duration
        );

        // Temporary fix to show/hide the timeline
        const showTimeline =
            document.location.hash.indexOf('timeline') > -1 ||
            (window.sessionStorage &&
                window.sessionStorage.getItem('timeline'));

        const timeline = showTimeline
            ? this.renderTimeline(
                  this.context.activeMediaObject,
                  this.state.duration,
                  this.context.mediaEvents,
                  this.context.resource
              )
            : null;

        // Renders the playlist dropdown
        const playlist = this.state.showPlaylist
            ? this.renderAVPlaylist(
                  this.props.mediaObjects,
                  this.props.transcripts,
                  this.context.query ? this.context.query.term : null,
                  this.context.activeMediaObject
                      ? this.context.activeMediaObject.assetId
                      : ''
              )
            : null;

        // Only show the header if there are multiple MediaObjects or Segments
        const header =
            this.props.mediaObjects.length > 1
                ? this.renderHeader(
                      this.togglePlaylist,
                      this.context.activeMediaObject
                          ? this.context.activeMediaObject.assetId
                          : null,
                      this.state.showPlaylist,
                      this.onPlaylistNext,
                      this.onPlaylistPrev
                  )
                : null;

        // Segment actions (temporary)
        const segmentActions = (
            <SegmentActions
                addSegment={this.context.annotationClient.newTemporalSegment}
                // Only set when there is an active annotation:
                addSegmentAfterActiveSegment={
                    this.context.annotationClient.activeSelection ?
                        this.context.annotationClient.newTemporalSegmentFromLast.bind(
                            this, this.onAirSeek
                        ) : null
                }
                deleteSegment={
                    this.context.annotationClient.activeSelection ?
                        async function() {
                            await this.context.annotationClient.delete(
                                this.context.annotationClient.activeAnnotation
                            )
                        }.bind(this) :
                        null
                }
                saveSegment={
                    this.context.annotationClient.activeSelection ?
                        async function() {
                            await this.context.annotationClient.saveSelection()
                        }.bind(this) :
                        null
                }
            />
        );

        return (
            <BaseColumn className={IDUtil.cssClassName('viewer-column')}>
                {header}

                <div className="column-content">
                    {/* Playlist */}
                    {playlist}

                    {/* Player */}
                    {player}

                    {/* Timeline */}
                    {timeline}

                    {/* TierView */}
                    {tierView}

                    {/* Segment actions */}
                    {segmentActions}
                </div>
            </BaseColumn>
        );
    }
}

MediaColumn.propTypes = {
    mediaObjects: PropTypes.array, // TODO further specify (arrayOf(MediaObject.getProptypes(false)))
    transcripts: PropTypes.array
};
