"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 CodeMirror = require("codemirror");
const json5 = require("json5");
const Observable_1 = require("rxjs/Observable");
require("codemirror/addon/fold/brace-fold");
require("codemirror/addon/fold/foldcode");
require("codemirror/addon/fold/foldgutter");
require("codemirror/addon/lint/lint");
require("codemirror/addon/selection/active-line");
require("codemirror/mode/javascript/javascript");
require("rxjs/add/operator/filter");
require("../util/takeUntilDestroyed");
const code_1 = require("../redux/code");
const project_1 = require("../redux/project");
const drag_1 = require("../util/drag");
CodeMirror.registerHelper('lint', 'javascript', (contents) => {
    try {
        json5.parse(contents);
    }
    catch (err) {
        const parts = /^(.+) at line ([0-9]+) column ([0-9]+)/.exec(err.message);
        if (!parts) {
            return [
                {
                    message: err.message,
                    severity: 'error',
                    from: CodeMirror.Pos(0, 0),
                    to: CodeMirror.Pos(contents.split('\n').length, 0),
                },
            ];
        }
        const [, message, line, column] = parts;
        return [
            {
                message,
                severity: 'error',
                from: CodeMirror.Pos(Number(line) - 1, Number(column)),
                to: CodeMirror.Pos(Number(line), 0),
            },
        ];
    }
    return [];
});
/**
 * The code component displays the code editor for participants/control state/
 */
let CodeComponent = class CodeComponent {
    constructor(store, project, el) {
        this.store = store;
        this.project = project;
        /**
         * Whether we're currently programmatically triggering a CodeMirror change.
         */
        this.isTriggeringChange = false;
        /**
         * Whether the editor is currently enabled.
         */
        this.isEnabled = true;
        this.el = el.nativeElement;
    }
    ngAfterContentInit() {
        const cm = (this.cm = CodeMirror.fromTextArea(this.target.nativeElement, {
            theme: 'monokai',
            mode: 'text/javascript',
            lineNumbers: true,
            tabSize: 2,
            foldGutter: true,
            styleActiveLine: true,
            lint: true,
            gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
        }));
        Observable_1.Observable.fromEvent(this.el, 'mousedown')
            .filter(ev => ev.target.classList.contains('cm-number'))
            .switchMap(ev => drag_1.captureDrag(ev)
            .filter(o => Math.abs(ev.pageX - o.pageX) > 10)
            .take(1)
            .mapTo(ev))
            .do(() => {
            this.project.setFrameMasked(true);
        })
            .switchMap(ev => this.dragNumber(ev))
            .subscribe(() => undefined, () => undefined, () => {
            this.project.setFrameMasked(false);
        });
        this.store
            .select('code')
            .takeUntilDestroyed(this)
            .subscribe(code => {
            this.updateEditorToState(code);
        });
        cm.on('changes', () => {
            this.updateStoredState();
        });
        this.store.takeUntilDestroyed(this).subscribe(state => {
            const enabled = state.connect.state === 0 /* Idle */;
            this.setEnabled(enabled, state.code);
            if (enabled) {
                return;
            }
            const controls = state.connect.controlsState;
            const prop = code_1.stateToProp[state.code.state];
            if (state.code.state > code_1.MaxEditableState || !controls || !controls[prop]) {
                return;
            }
            this.updateEditorContents(json5.stringify(controls[prop], null, 2).split('\n'));
        });
    }
    ngOnDestroy() {
        /* noop */
    }
    setEnabled(enabled, state) {
        if (enabled === this.isEnabled) {
            return;
        }
        this.isEnabled = enabled;
        if (enabled) {
            this.el.classList.remove('disabled');
            this.updateEditorToState(state);
            this.cm.setOption('readOnly', false);
        }
        else {
            this.el.classList.add('disabled');
            this.cm.setOption('readOnly', true);
        }
    }
    updateStoredState() {
        if (this.isTriggeringChange || !this.isEnabled) {
            return;
        }
        this.isTriggeringChange = true;
        this.store.dispatch({
            type: code_1.stateToUpdateAction[this.codeState],
            data: this.cm.getValue().split('\n'),
        });
        this.isTriggeringChange = false;
    }
    updateEditorToState(code) {
        const contents = code[code_1.stateToProp[code.state]];
        if (code.state > code_1.MaxEditableState || this.isTriggeringChange) {
            return;
        }
        this.codeState = code.state;
        this.updateEditorContents(contents);
    }
    updateEditorContents(lines) {
        this.isTriggeringChange = true;
        let formatted = lines.join('\n');
        try {
            formatted = json5.stringify(json5.parse(formatted), null, 2);
        }
        catch (e) {
            // ignored
        }
        this.cm.setValue(`${formatted}\n`);
        this.isTriggeringChange = false;
    }
    dragNumber(startEv) {
        const doc = this.cm.getDoc();
        let pos;
        const selections = doc.listSelections();
        if (selections.length > 0) {
            pos = selections[0].anchor;
        }
        else {
            pos = doc.getCursor();
        }
        const line = doc.getLine(pos.line);
        let start = pos.ch;
        while (/[0-9\.-]/.test(line[start - 1])) {
            start--;
        }
        let end = pos.ch;
        while (/[0-9\.-]/.test(line[end])) {
            end++;
        }
        const originalValue = Number(line.slice(start, end));
        const shouldRound = Math.abs(originalValue) >= 1;
        if (isNaN(originalValue)) {
            return Observable_1.Observable.of(null);
        }
        return drag_1.captureDrag(startEv).do(ev => {
            const inverter = ev.pageX > startEv.pageX ? 1 : -1;
            const newAmount = originalValue * (1 + inverter * Math.pow(((ev.pageX - startEv.pageX) / 500), 2));
            const newText = String(shouldRound ? Math.round(newAmount) : newAmount);
            doc.replaceRange(newText, { line: pos.line, ch: start }, { line: pos.line, ch: end });
            end = start + newText.length;
            doc.setSelection({ line: pos.line, ch: start }, { line: pos.line, ch: end });
        });
    }
};
__decorate([
    core_1.ViewChild('target'),
    __metadata("design:type", core_1.ElementRef)
], CodeComponent.prototype, "target", void 0);
CodeComponent = __decorate([
    core_1.Component({
        selector: 'editor-code',
        templateUrl: './code.component.html',
        styleUrls: ['./code.component.scss'],
        changeDetection: core_1.ChangeDetectionStrategy.OnPush,
    }),
    __metadata("design:paramtypes", [store_1.Store,
        project_1.ProjectService,
        core_1.ElementRef])
], CodeComponent);
exports.CodeComponent = CodeComponent;
