"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@angular/core");
const store_1 = require("@ngrx/store");
const Observable_1 = require("rxjs/Observable");
require("rxjs/add/observable/fromEvent");
require("rxjs/add/operator/take");
require("../util/takeUntilDestroyed");
const internal_1 = require("@mcph/miix-std/dist/internal");
const console_service_1 = require("../console/console.service");
const memorizingSubject_1 = require("../util/memorizingSubject");
const control_state_sync_service_1 = require("./control-state-sync.service");
const controls_1 = require("./sources/controls");
const group_1 = require("./sources/group");
const participant_1 = require("./sources/participant");
/**
 * The LocalStateSyncService manages synchronizing state between what's in the
 * editor state and what the local controls know about. It uses RPC over
 * postMessage to handle this.
 */
let LocalStateSyncService = class LocalStateSyncService {
    constructor(controlsState, console, store) {
        this.controlsState = controlsState;
        this.console = console;
        this.store = store;
        this.state = 0 /* Loading */;
        this.sources = [new controls_1.ControlsSource(), new group_1.GroupSource(), new participant_1.ParticipantSource()];
        this.closed = new memorizingSubject_1.MemorizingSubject();
        this.sources.forEach(source => {
            source
                .getEvents(store.select('code'))
                .takeUntil(this.closed)
                .subscribe(calls => {
                if (this.state === 1 /* AwaitingValid */) {
                    this.sendInitialState();
                }
                else if (this.state === 2 /* Ready */) {
                    this.sendInteractive(...calls);
                }
            });
        });
    }
    /**
     * Attaches listeners to the iframe.
     */
    bind(frame) {
        this.rpc = new internal_1.RPC(frame.contentWindow, '1.0');
        this.console.bindToRPC(this.rpc);
        this.rpc.expose('controlsReady', () => {
            this.sendInitialState();
        });
        this.rpc.expose('moveVideo', data => {
            this.controlsState.setVideoSize(data);
        });
        this.rpc.expose('log', data => {
            this.console.addLog(data);
        });
        const refresh = this.controlsState.getRefresh();
        Observable_1.Observable.fromEvent(frame, 'loaded')
            .takeUntil(this.closed)
            .takeUntil(refresh)
            .subscribe(() => {
            this.state = 0 /* Loading */;
        });
        refresh
            .takeUntil(this.closed)
            .take(1)
            .subscribe(() => {
            this.rpc.destroy();
            frame.src = frame.src;
            this.bind(frame);
        });
        this.controlsState
            .getFittedVideoSize()
            .takeUntil(this.closed)
            .takeUntil(refresh)
            .subscribe(rect => {
            this.rpc.call('updateVideoPosition', {
                connectedPlayer: Object.assign({}, rect, { channelId: 1 }),
                costreamPlayers: [],
            }, false);
        });
        return this;
    }
    /**
     * Tears down resources associated with the service.
     */
    unbind() {
        if (this.rpc) {
            this.rpc.destroy();
        }
        this.closed.next(undefined);
    }
    ngOnDestroy() {
        this.unbind();
    }
    /**
     * Sends down the initial/current controls state, fired when the controls
     * first call `controlsReady`.
     */
    sendInitialState() {
        const create = this.sources
            .map(src => src.getCreatePacket())
            .reduce((prev, next) => prev.concat(next), []);
        if (create.some(packet => packet === undefined)) {
            this.state = 1 /* AwaitingValid */;
            return;
        }
        this.controlsState
            .getSettings()
            .takeUntil(this.closed)
            .takeUntil(this.controlsState.getRefresh())
            .subscribe(settings => {
            this.rpc.call('updateSettings', settings, false);
        });
        this.store
            .map(s => s.frame.controlsReady)
            .takeUntil(this.controlsState.getRefresh())
            .takeUntil(this.closed)
            .distinctUntilChanged()
            .subscribe(isReady => {
            this.sendInteractive({
                method: 'onReady',
                params: { isReady },
            });
        });
        this.sendInteractive(...create.map(call => call));
        this.state = 2 /* Ready */;
    }
    sendInteractive(...packets) {
        packets.forEach(packet => {
            this.rpc.call('recieveInteractivePacket', packet, false);
        });
    }
};
LocalStateSyncService = __decorate([
    core_1.Injectable(),
    __metadata("design:paramtypes", [control_state_sync_service_1.ControlStateSyncService,
        console_service_1.ConsoleService,
        store_1.Store])
], LocalStateSyncService);
exports.LocalStateSyncService = LocalStateSyncService;
