import { IZoomView } from "../Common/Interfaces/IZoomView";
import {    MusicSheetDrawer, GraphicalMusicSheet, BoundingBox, DrawingParameters,
            MusicSystem, GraphicalVoiceEntry } from "../MusicalScore/Graphical";
import { ScreenViewingRegion } from "./ScreenViewingRegion";
import { PointF2D, Fraction, SizeF2D } from "../Common/DataObjects";
import { AbstractZoomView } from "./AbstractZoomView";
import { InteractionType } from "../Common/Enums/InteractionType";
import { AbstractDisplayInteractionManager } from "./AbstractDisplayInteractionManager";
import { IUserDisplayInteractionListener } from "../Common/Interfaces/IUserDisplayInteractionListener";
import { PlaybackManager } from "../Playback";

export class SheetRenderingManager extends AbstractZoomView implements IZoomView {
    // protected phonicScoreInterface: IPhonicScoreInterface; // TODO MB: remove this
    protected musicSheetDrawer: MusicSheetDrawer;
    protected graphicalMusicSheet: GraphicalMusicSheet;
    protected mainViewingRegion: ScreenViewingRegion = ScreenViewingRegion.createWithDefaults();
    // protected display: IMusicSheetDisplay;
    // protected renderingFinished: EventWaitHandle = new EventWaitHandle(true, EventResetMode.ManualReset);
    // protected newRedrawWanted: EventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset);
    protected tryAgainToRenderCount: number = 0;
    //private toImageMusicSheetDrawer: MusicSheetDrawer;
    private yOffsetMouseDown: number = Number.MIN_VALUE;
    private unlockCursorDistancePixel: number = 50.0;
    private relativeTopPosition: number = 0.06;
    /* The preview images are supersampled (because of our drawing mechanism with more than 1 rasterization stage,
     * this increases the output image quality significantly)
     **/
    protected internalPreviewImageScale: number = 3.0;
    protected listeners: IUserDisplayInteractionListener[] = [];
    public PlaybackManager: PlaybackManager;

    constructor(displayInteractionManager: AbstractDisplayInteractionManager, playbackManager?: PlaybackManager) {
        super(displayInteractionManager);
        this.addZoomView(this);
        this.lockRanges = true;
        this.TopBarHeightInPixel = 70;
        this.BottomBarHeightInPixel = 0;
        // this.phonicScoreInterface = phonicScoreInterface;
        // this.display = display;
        // this.musicSheetDrawer =
        //     new PsMusicSheetDrawer(phonicScoreInterface, display.Renderer, display.Renderer, FontInfo.Info, false, phonicScoreInterface.HasSoundOutput);
        // this.toImageMusicSheetDrawer =
        //     new PsMusicSheetDrawer(phonicScoreInterface, display.PreviewImageRenderer, display.PreviewImageRenderer, FontInfo.Info, true, false);
        //this.toImageMusicSheetDrawer.splitScreenLineColor = -1;
        // TODO MB: Commented following 2 lines because paintingThreadLoop is to be removed. Do we need to convert this somehow?
        // const redrawPreparationThread: Task = new Task(this.paintingThreadLoop);
        // redrawPreparationThread.Start();
    }

    public addListener(listener: IUserDisplayInteractionListener): void {
        this.listeners.push(listener);
    }
    public SingleTouchDisabled: boolean;
    public DoubleTouchDisabled: boolean;
    public LockDisplayToCursor: boolean = true;
    public ZoomActive: boolean = false;
    protected convertToUnitsReady(): boolean {
        return this.graphicalMusicSheet !== undefined;
    }
    protected unitPosTouched(PosInUnits: PointF2D, relPosX: number, relPosY: number): void {
        // lock(this.mainViewingRegion)
        // {
        // Pass mouse click to click listener
        if (!this.SingleTouchDisabled) {
            const relPos: PointF2D = new PointF2D(relPosX, relPosY);
            this.handleUserDisplayInteraction(relPos, PosInUnits, InteractionType.SingleTouch);
            for (const listener of this.listeners) {
                listener.userDisplayInteraction(relPos, PosInUnits, InteractionType.SingleTouch);
            }
        }
        // }
    }
    protected unitPosDoubleTouched(PosInUnits: PointF2D, relPosX: number, relPosY: number): void {
        // lock(this.mainViewingRegion)
        // {
        if (!this.DoubleTouchDisabled) {
            const relPos: PointF2D = new PointF2D(relPosX, relPosY);
            this.handleUserDisplayInteraction(relPos, PosInUnits, InteractionType.DoubleTouch);
            for (const listener of this.listeners) {
                listener.userDisplayInteraction(relPos, PosInUnits, InteractionType.DoubleTouch);
            }
        }
        // }
    }
    protected unitPosTouchDown(PosInUnits: PointF2D, relPosX: number, relPosY: number): void {
        // lock(this.mainViewingRegion)
        // {
        const relPos: PointF2D = new PointF2D(relPosX, relPosY);
        this.handleUserDisplayInteraction(relPos, PosInUnits, InteractionType.TouchDown);
        for (const listener of this.listeners) {
            listener.userDisplayInteraction(new PointF2D(relPosX, relPosY), PosInUnits, InteractionType.TouchDown);
        }
        this.yOffsetMouseDown = PosInUnits.y;
        // }
    }
    protected unitPosTouchUp(PosInUnits: PointF2D, relPosX: number, relPosY: number): void {
        // lock(this.mainViewingRegion)
        // {
        const relPos: PointF2D = new PointF2D(relPosX, relPosY);
        this.handleUserDisplayInteraction(relPos, PosInUnits, InteractionType.TouchUp);
        for (const listener of this.listeners) {
            listener.userDisplayInteraction(new PointF2D(relPosX, relPosY), PosInUnits, InteractionType.TouchUp);
        }
        if (this.displayInteractionManager.WasZoomGestureActive === false) {
            this.unlockFromCursorIfNecessary(PosInUnits);
        }
        this.yOffsetMouseDown = Number.MIN_VALUE;
        // }
    }
    protected unitPosMove(PosInUnits: PointF2D, relPosX: number, relPosY: number): void {
        // lock(this.mainViewingRegion)
        // {
        const relPos: PointF2D = new PointF2D(relPosX, relPosY);
        this.handleUserDisplayInteraction(relPos, PosInUnits, InteractionType.Move);
        for (const listener of this.listeners) {
            listener.userDisplayInteraction(new PointF2D(relPosX, relPosY), PosInUnits, InteractionType.Move);
        }
        this.unlockFromCursorIfNecessary(PosInUnits);
        // }
    }

    public get MainViewingRegion(): ScreenViewingRegion {
        return this.mainViewingRegion;
    }
    public TopBarHeightInPixel: number;
    public BottomBarHeightInPixel: number;
    // public setRenderers(windowRenderer: IMusicSheetRenderer, previewRenderer: IImageRenderer): void {
    //     this.musicSheetDrawer.setRenderer(windowRenderer);
    //     this.toImageMusicSheetDrawer.setRenderer(previewRenderer);
    // }
    public setMusicSheet(musicSheet: GraphicalMusicSheet): void {
        // this.musicSheetDrawer.disposeAllBackbuffers();
        this.graphicalMusicSheet = musicSheet;
        this.adaptDisplayLimitsToSheet();
        // this.setYOffset(
        //     EngravingRules.Rules.TitleTopDistance - this.topBarHeightInUnits() - this.relativeTopPosition * this.heightWithoutTopBottomBarsInUnits(),
        //     true);
        this.setYOffset(0, true);
        // this.redraw();
    }
    public viewportXChanged(offsetX: number, rangeX: number): void {
        if (this.graphicalMusicSheet === undefined) {
            return;
        }
        // lock(this.mainViewingRegion)
        // {
        this.horizontalViewportChanged(offsetX, rangeX);
        // }
    }
    /**
     * Sets the vertical position and viewing height of the displayed area on the music score.
     */
    public viewportYChanged(offsetY: number, rangeY: number): void {
        if (this.graphicalMusicSheet === undefined) {
            return;
        }
        if (this.yOffsetMouseDown <= Number.MIN_VALUE + 0.5) {
            this.yOffsetMouseDown = offsetY;
        }
        // lock(this.mainViewingRegion)
        // {
        this.verticalViewportChanged(offsetY, rangeY);
        // }
    }
    public displaySizeChanged(width: number, height: number): void {
        super.viewSizeChanged(width, height);
        //TODO: Don't think we need these for web?
        //this.TopBarHeightInPixel = topBarHeightInPixel;
        //this.BottomBarHeightInPixel = bottomBarHeightInPixel;
        // this.musicSheetDrawer.disposeAllBackbuffers();
        if (Math.abs(width - 0) < 0.0000001 || Math.abs(height - 0) < 0.0000001) {
            return;
        }
        // lock(this.mainViewingRegion)
        // {
        if (this.graphicalMusicSheet !== undefined) {
            this.graphicalMusicSheet.EnforceRedrawOfMusicSystems(); // probably not necessary, already handled by OSMD
        }
        this.mainViewingRegion.DisplaySizeInPixel = new SizeF2D (width, height);
        this.adaptDisplayLimitsToSheet();
        // }
    }
    public calcDisplayYPosition(system: MusicSystem): number {
        return  system.PositionAndShape.AbsolutePosition.y + system.PositionAndShape.BorderMarginTop -
                this.topBarHeightInUnits() - this.relativeTopPosition * this.heightWithoutTopBottomBarsInUnits();
    }
    /**
     * The display scroll y-position to show the given system completely on the bottom of the screen
     */
    public yPositionForLastSystem(lastSystem: MusicSystem): number {
        return  lastSystem.PositionAndShape.AbsolutePosition.y + lastSystem.PositionAndShape.BorderMarginBottom -
                this.topBarHeightInUnits() - (1 - this.relativeTopPosition) * this.heightWithoutTopBottomBarsInUnits();
    }
    // TODO MB: What is up with the unused variables here? Also: formatting of parameters.
    public scorePositionChanged(upperCursorPoint: PointF2D, enrolledTimeStamp: Fraction, sheetTimeStamp: Fraction,
                                system: MusicSystem, resetOccurred: boolean, smoothAnimation: boolean): void {
        const positionY: number = this.calcDisplayYPosition(system);
        this.setYPosition(positionY, smoothAnimation);
        // this.redraw();
    }
    public setXPosition(positionXInUnits: number, animated: boolean): void {
        // lock(this.mainViewingRegion)
        // {
        if (this.LockDisplayToCursor) {
            this.setXOffset(positionXInUnits, animated);
        }
        // }
    }
    public setYPosition(positionYInUnits: number, animated: boolean): void {
        // lock(this.mainViewingRegion)
        // {
        if (this.LockDisplayToCursor) {
            this.setYOffset(positionYInUnits, animated);
        }
        // }
    }
    public clearBackbuffers(): void {
        // this.musicSheetDrawer.disposeAllBackbuffers();
    }
    // public redraw(): void {
    //     this.newRedrawWanted.Set();
    // }
    // public render(): void {
    //     // lock(this.display.Renderer)
    //     // {
    //     try {
    //         this.display.Renderer.prepareDrawing();
    //         this.display.Renderer.draw();
    //     } catch (ex) {
    //         log.error("SheetRenderingManager.render", "Renderer PrepareDrawing or draw", ex);
    //         throw ex;
    //     } finally {
    //         try {
    //             this.display.Renderer.finalizeDrawing();
    //         } catch (ex) {
    //             log.error("SheetRenderingManager.render", "Renderer FinalizeDrawing", ex);
    //             throw ex;
    //         } finally {
    //             this.renderingFinished.Set();
    //         }
    //     }
    //     // }
    // }
    public get DrawingParameters(): DrawingParameters {
        return this.musicSheetDrawer.drawingParameters;
    }
    public topBarHeightInUnits(): number {
        return this.mainViewingRegion.transformLengthYToUnitCoordinates(this.TopBarHeightInPixel);
    }
    public bottomBarHeightInUnits(): number {
        return this.mainViewingRegion.transformLengthYToUnitCoordinates(this.BottomBarHeightInPixel);
    }
    public heightWithoutTopBottomBarsInUnits(): number {
        return this.mainViewingRegion.ViewRegionInUnits.height - this.topBarHeightInUnits() - this.bottomBarHeightInUnits();
    }
    public activePositionToBottomBarHeight(): number {
        return  (this.mainViewingRegion.ViewRegionInUnits.height - this.topBarHeightInUnits() - this.bottomBarHeightInUnits()) *
                (1 - 2 * this.relativeTopPosition);
    }
    // public freeMemory(): void {
    //     this.display.Renderer.freeMemory();
    // }
    // public Dispose(): void {
    //     super.dispose();
    // }
    public getClickPosition(relativePositionX: number, relativePositionY: number): PointF2D {
        return this.mainViewingRegion.transformToUnitCoordinates(new PointF2D(relativePositionX, relativePositionY));
    }
    public graphicalObjectIsVisible(boundingBox: BoundingBox): boolean {
        const isCompletelyInside: boolean = false;
        // lock(this.mainViewingRegion)
        // {
        return this.mainViewingRegion.isVisible(boundingBox, isCompletelyInside);
        // }
    }
    /**
     * sets the size of the maximal musicpage seen including the extensions on top resp. bottom
     * !Caution!: settings/offsets have been changed for ScrollIndicator.. won't work anymore if changed again
     */
    public adaptDisplayLimitsToSheet(): void {
        if (   this.graphicalMusicSheet === undefined
            || this.graphicalMusicSheet.MusicPages.length === 0
            || this.graphicalMusicSheet.MusicPages[0].MusicSystems.length === 0) {
            return;
        }

        // set the new limits for viewing:
        this.offsetXMin = 0;
        this.rangeXMin = this.graphicalMusicSheet.MinAllowedSystemWidth;
        this.rangeXMax = 300;
        this.offsetYMin = -0.3 * this.RangeY;
        const lastPagePsi: BoundingBox = this.graphicalMusicSheet.MusicPages.last().PositionAndShape;
        this.offsetYMax = Math.max(0, lastPagePsi.BorderMarginBottom - 0.7 * this.RangeY);
        if (this.OffsetY > this.offsetYMax) {
            this.setYOffset(this.offsetYMax, true);
        }
    }
    protected horizontalViewportChanged(offsetX: number, rangeX: number): void {
        if (this.mainViewingRegion.WidthInUnits !== rangeX) {
            this.mainViewingRegion.WidthInUnits = rangeX;
            // this.redraw();
        }
    }

    protected verticalViewportChanged(offsetY: number, rangeY: number): void {
        this.mainViewingRegion.UpperLeftPositionInUnits = new PointF2D(this.mainViewingRegion.UpperLeftPositionInUnits.x, offsetY);
    }

    private unlockFromCursorIfNecessary(PosInUnits: PointF2D): void {
        if (this.LockDisplayToCursor === false || this.ZoomActive) {
            return;
        }

        if (this.displayInteractionManager.ZoomGestureActive || this.displayInteractionManager.WasZoomGestureActive) {
            return;
        }
        // finally check for the movement distance, to not unlock already at little finger pressure changes...
        // TODO MB: Fix formatting.
        const ydiff: number = Math.abs((PosInUnits.y - this.yOffsetMouseDown) *
                                this.mainViewingRegion.RegionSizeInPixel.height /
                                this.mainViewingRegion.ViewRegionInUnits.height);
        if (ydiff > this.unlockCursorDistancePixel) {
            // this.phonicScoreInterface.requestLockDisplayToCursor(false);
            this.LockDisplayToCursor = false;
        }
    }

    protected getPositionInUnits(relativePositionX: number, relativePositionY: number): PointF2D {
        return this.mainViewingRegion.transformToUnitCoordinates(new PointF2D(relativePositionX, relativePositionY));
    }

    protected handleUserDisplayInteraction( relativePositionOnDisplay: PointF2D, positionOnMusicSheet: PointF2D,
                                            type: InteractionType): void {
        switch (type) {
            case InteractionType.TouchDown:
            case InteractionType.SingleTouch:
            case InteractionType.DoubleTouch: {
                // switch (this.Tool) {
        //             case PsTool.None: {
        //                 if (this.graphicalMusicSheet === undefined || this.renderingManager.TouchMoving) {
        //                     break;
        //                 }

        //                 // check for activity symbol clicked
        //                 if (positionOnMusicSheet.x < 10) {
                            // const activitySymbol: StaffLineActivitySymbol =
                            //     this.graphicalMusicSheet.getClickedObject<StaffLineActivitySymbol>(positionOnMusicSheet);
        //                     if (activitySymbol !== undefined) {
        //                         const instrument: Instrument = activitySymbol.parentStaffLine.ParentStaff.ParentInstrument;
        //                         switch (this.Mode) {
        //                             case PsMode.Following: {
        //                              // KeyValuePair<int, bool> parameters = new KeyValuePair<int, bool>(
        //                              //     activitySymbol.ParentStaffLine.ParentStaff.Id,
        //                              //     !activitySymbol.ParentStaffLine.ParentStaff.Following
        //                              //     );
                                        // instrument.setInstrumentParameter(
                                        //     InstrumentParameters.Follow,
                                        //     !activitySymbol.parentStaffLine.ParentStaff.following
                                        //     );
        //                                 break;
        //                             }
        //                             case PsMode.Midi: {
        //                                 if (this.PitchMonitorEnabled) {
        //                                     const pitchMonitorEnabled: boolean = !activitySymbol.parentStaffLine.ParentStaff.PitchMonitor;
        //                                     //KeyValuePair<int, bool> parameters = new KeyValuePair<int, bool>(
        //                                          // activitySymbol.ParentStaffLine.ParentStaff.Id,
        //                                          // !activitySymbol.ParentStaffLine.ParentStaff.PitchMonitor);
        //                                     instrument.setInstrumentParameter(InstrumentParameters.PitchMonitor, pitchMonitorEnabled);

        //                                     //if (pitchMonitorEnabled) {
        //                                     //        // switch off all other instruments so that only one will be active
        //                                     //    foreach (var instr in this.graphicalMusicSheet.ParentMusicSheet.Instruments) {
        //                                     //        if (instr != instrument) {
        //                                     //            instr.setInstrumentParameter(InstrumentParameters.PitchMonitor, false);
        //                                     //            }
        //                                     //        }
        //                                     //    }
        //                                 }
        //                                 else {
        //                                  KeyValuePair < int, bool > parameters = new KeyValuePair<int, bool>(
        //                                      activitySymbol.ParentStaffLine.ParentStaff.Id,
        //                                      !activitySymbol.ParentStaffLine.ParentStaff.Audible
        //                                  );
        //                                     instrument.setInstrumentParameter(InstrumentParameters.StaffAudible, parameters);
        //                                 }
        //                                 break;
        //                             }
        //                             default:
        //                                 break;
        //                         }

        //                         break;
        //                     }
        //                 }

                const clickVe: GraphicalVoiceEntry = this.graphicalMusicSheet.GetNearestVoiceEntry(positionOnMusicSheet);

        //                 // check for clef clicked

        //                 if (clickedObject is GraphicalClefInstruction) {
        //                     if (this.QueryRequested !== undefined) {
        //                         GraphicalClefInstruction clef = clickedObject as GraphicalClefInstruction;
        //                         this.QueryRequested(QueryType.ClefChange, clef);
        //                     }
        //                     break;
        //                 }

                        /*
                        // *** MARKED AREA and COMMENT Editing ***
                        //check for Marked Area/Comment clicked
                        GraphicalLabel clickableLabel = this.graphicalMusicSheet.getNearestGraphicalObject<GraphicalLabel>(positionOnMusicSheet);
                        if (clickableLabel is Clickable)
                        {
                            Clickable clickable = (Clickable)clickableLabel;
                            if (clickable.DataObject is MarkedArea)
                            {
                                this.annotationManager.editMarkedAreaLabel((MarkedArea)clickable.DataObject);
                                break;
                            }
                            else if (clickable.DataObject is Comment)
                            {
                                this.annotationManager.editCommentLabel((Comment)clickable.DataObject);
                                break;
                            }
                        }
                        */

                        // if (this.Mode != PsMode.Manual) {
                            // lock(this.graphicalMusicSheet) {
                // set cursor and/or start/end marker position

                if (clickVe) {
                    if (clickVe.parentStaffEntry.parentVerticalContainer !== undefined) {
                        const clickedTimeStamp: Fraction = clickVe.parentStaffEntry.parentVerticalContainer.AbsoluteTimestamp;
                        // if (this.Loop === false) {
                        this.setStartPosition(clickedTimeStamp);
                        // } else { //greater than startSymbol
                        //     if (this.loop_setEndMarker) {
                        //         this.setEndPosition(clickedTimeStamp);
                        //         // this.MessageOccurred(MessageBoxType.Info, "", MessageBoxTopic.LoopMarkerChanged, LoopTagType.EndTag);
                        //     } else {
                        //         this.setEndPosition(this.graphicalMusicSheet.ParentMusicSheet.SheetEndTimestamp);
                        //         this.setStartPosition(clickedTimeStamp);
                        //         // this.MessageOccurred(MessageBoxType.Info, "", MessageBoxTopic.LoopMarkerChanged, LoopTagType.StartTag);
                        //     }

                            // toggle marker:
                            // this.loop_setEndMarker = !this.loop_setEndMarker;
                        // }

                        // playback clicked note
                        if (clickVe.notes[0]?.sourceNote.Pitch !== undefined) {
                            this.PlaybackManager?.playVoiceEntry(clickVe.parentVoiceEntry);
                        }
                    }
                }
                            // }
                        // }
    //                     else // manual mode:
    //                     {
                        //     if (relativePositionOnDisplay.y < 0.3) {
                        //         this.scrollPageUp_();
                        //         break;
                        //     }

                        //     if (relativePositionOnDisplay.y > 0.7) {
                        //         this.scrollPageDown_();
                        //         break;
                        //     }
                        // }

    //                     break;
    //                 }

    //                 case PsTool.Zoom:
    //                     break;
                    // case PsTool.Comment: {
                        // const clickedGse: GraphicalStaffEntry =
                        //     this.graphicalMusicSheet.getNearestGraphicalObject<GraphicalStaffEntry>(
                        //         positionOnMusicSheet);
                        // if (clickedGse === undefined || clickedGse.relInMeasureTimestamp === undefined
                        // ) // second would mean grace note
                        // {
                        //     break;
                        // }

                        // const staff: Staff = clickedGse.parentMeasure.ParentStaffLine.ParentStaff;
                        // const staffLineIndex: number = MusicSheet.getIndexFromStaff(staff);
                        // this.annotationManager.createComment(clickedGse.parentVerticalContainer.AbsoluteTimestamp,
                        //     staffLineIndex);
    //                     break;
    //                 }

    //                 case PsTool.Mark: {
                        // const clickedGse: GraphicalStaffEntry =
                        //     this.graphicalMusicSheet.getNearestGraphicalObject<GraphicalStaffEntry>(
                        //         positionOnMusicSheet);
                        // if (clickedGse === undefined || clickedGse.relInMeasureTimestamp === undefined
                        // ) // second would mean grace note
                        // {
                        //     break;
                        // }

                        // this.annotationManager.createMarkedArea(
                        //     clickedGse.parentVerticalContainer.AbsoluteTimestamp);
                        // break;
    //                 }

    //                 default:
    //                     throw new ArgumentOutOfRangeException();
    //             }

                break;
            }

            case InteractionType.TouchUp: {
                // switch (this.Tool) {
                //     case PsTool.None:
                //         break;
                //     case PsTool.Zoom:
                //         this.adaptSystemBreaksToPageWidth_();
                //         break;
                //     case PsTool.Comment:
                //         break;
                //     case PsTool.Mark:
                //         break;
                //     default:
                //         throw new ArgumentOutOfRangeException();
                // }

                break;
            }

            case InteractionType.TouchDown: {
                break;
            }

            case InteractionType.Move: {
                break;
            }

            default:
                throw new Error("type");
        }
    }

    protected setStartPosition(newStartPosition: Fraction): void {
        if (this.graphicalMusicSheet === undefined) {
            return;
        }

        // if (this.Loop && newStartPosition >= this.graphicalMusicSheet.ParentMusicSheet.SelectionEnd) {
        //     const currentMeasureNumber: number = this.graphicalMusicSheet.ParentMusicSheet
        //         .getSourceMeasureFromTimeStamp(newStartPosition).MeasureNumber;
        //     this.setEndPosition(currentMeasureNumber);
        // }

        // this.graphicalMusicSheet.setSelectionStartSymbol(newStartPosition);
        this.graphicalMusicSheet.ParentMusicSheet.SelectionStart = newStartPosition;
        this.PlaybackManager?.reset();
    }

}
