import {
    MergableObjectEventArgs,
    MergableObjectEventSource,
    MergableObjectState,
    MergableObjectConflict
} from '../../MergableSet';
import { MarkupEventTypes, MarkupProperty } from '../MarkupEntityTypes';
import { MarkupEntityBase } from './MarkupEntityBase';
import { MarkupSheet2D } from './MarkupSheet2D';

/**
 * Super class for markup entities in the markup API.
 */
export abstract class MarkupEntity extends MarkupEntityBase {
    private _enabled = true;
    /**
     * Do not use. Use {@link MarkupSheet2D.add} instead.
     * @hidden
     */
    constructor(
        id: string,
        private _sheet: MarkupSheet2D,
        properties = new Map<string, MarkupProperty>()
    ) {
        super(id, properties);
    }

    /** @inheritDoc */
    abstract clone(): MarkupEntityBase;
    /** @inheritDoc */
    abstract isEqual(o: MarkupEntityBase): boolean;
    /**
     * Copy the entity to another markup sheet.
     * @param sheet Markup sheet to copy the entity to.
     * @returns The new markup entity.
     */
    abstract copyTo(sheet: MarkupSheet2D): MarkupEntity;

    /** Returns the markup sheet that the entity is attached to */
    get sheet(): MarkupSheet2D {
        return this._sheet;
    }

    /** @inheritDoc */
    get isEnabled(): boolean {
        return this._sheet.isEnabled && this._enabled;
    }

    /** @inheritDoc */
    set isEnabled(b: boolean) {
        this._enabled = b;
    }

    /** @inheritDoc */
    onAdded(o: MergableObjectEventArgs<MarkupEntity>): void {
        this.detectParentSheetConflicts(o);
        this._sheet.parent.observable.notifyObservers({ eventType: MarkupEventTypes.Added, ...o });
    }

    /** @inheritDoc */
    onUpdate(o: MergableObjectEventArgs<MarkupEntity>): void {
        this.detectParentSheetConflicts(o);
        this._sheet.parent.observable.notifyObservers({ eventType: MarkupEventTypes.Update, ...o });
    }

    /** @inheritDoc */
    onDelete(o: MergableObjectEventArgs<MarkupEntity>): void {
        this._sheet.parent.observable.notifyObservers({ eventType: MarkupEventTypes.Delete, ...o });
    }

    private detectParentSheetConflicts(o: MergableObjectEventArgs<MarkupEntity>): void {
        if (o.eventSource === MergableObjectEventSource.Remote && this.assignParentSheet()) {
            const sheetMergableObject = this._sheet.parent.get(this._sheet.id);
            if (!sheetMergableObject) {
                throw new Error('Sheet does not exist in layer, this should be impossible (probably a bug).');
            }
            if (sheetMergableObject.state === MergableObjectState.Deleted && !sheetMergableObject.hasConflict) {
                sheetMergableObject.conflictReason = MergableObjectConflict.RemoteDiffers;
                sheetMergableObject.local.onUpdate({
                    conflictReason: MergableObjectConflict.RemoteDiffers,
                    remote: sheetMergableObject.remote,
                    local: sheetMergableObject.local,
                    eventSource: MergableObjectEventSource.Remote,
                    state: sheetMergableObject.state
                });
            }
        }
    }

    private assignParentSheet(): boolean {
        const tmpSheet = this._sheet.parent._layer.get(this._sheet.id);
        if (!tmpSheet) {
            throw new Error('Layer does not exist.');
        }

        if (tmpSheet.local instanceof MarkupSheet2D) {
            this._sheet = tmpSheet.local;
            return true;
        }

        throw new Error('Markup sheet was not of type MarkupSheet2D.');
    }

    /** @inheritDoc */
    markAsDeleted(): boolean {
        return this._sheet.parent._layer.delete(this.id);
    }

    /** @inheritDoc */
    markAsUpdated(): boolean {
        this._sheet.markAsUpdated();
        return this._sheet.parent._layer.update(this.id, () => {});
    }
}

export interface MarkupEntityConstructorArgs {
    id: string;
    sheet: MarkupSheet2D;
    properties?: Map<string, MarkupProperty>;
}
