import { AvatarGuideStatus, IConnection, IRoomCreator, IVector3D, LegacyDataType, ObjectRolling, PetType, RoomObjectType, RoomObjectUserType, RoomObjectVariable } from '@nitrots/api';
import { AreaHideMessageEvent, DiceValueMessageEvent, FloorHeightMapEvent, FurnitureAliasesComposer, FurnitureAliasesEvent, FurnitureDataEvent, FurnitureFloorAddEvent, FurnitureFloorDataParser, FurnitureFloorEvent, FurnitureFloorRemoveEvent, FurnitureFloorUpdateEvent, FurnitureWallAddEvent, FurnitureWallDataParser, FurnitureWallEvent, FurnitureWallRemoveEvent, FurnitureWallUpdateEvent, GetCommunication, GetRoomEntryDataMessageComposer, GuideSessionEndedMessageEvent, GuideSessionErrorMessageEvent, GuideSessionStartedMessageEvent, IgnoreResultEvent, ItemDataUpdateMessageEvent, ObjectsDataUpdateEvent, ObjectsRollingEvent, OneWayDoorStatusMessageEvent, PetExperienceEvent, PetFigureUpdateEvent, RoomEntryTileMessageEvent, RoomEntryTileMessageParser, RoomHeightMapEvent, RoomHeightMapUpdateEvent, RoomPaintEvent, RoomReadyMessageEvent, RoomUnitChatEvent, RoomUnitChatShoutEvent, RoomUnitChatWhisperEvent, RoomUnitDanceEvent, RoomUnitEffectEvent, RoomUnitEvent, RoomUnitExpressionEvent, RoomUnitHandItemEvent, RoomUnitIdleEvent, RoomUnitInfoEvent, RoomUnitNumberEvent, RoomUnitRemoveEvent, RoomUnitStatusEvent, RoomUnitTypingEvent, RoomVisualizationSettingsEvent, UserInfoEvent, YouArePlayingGameEvent } from '@nitrots/communication';
import { GetRoomSessionManager, GetSessionDataManager } from '@nitrots/session';
import { Vector3d } from '@nitrots/utils';
import { GetRoomEngine } from './GetRoomEngine';
import { RoomVariableEnum } from './RoomVariableEnum';
import { RoomPlaneParser } from './object/RoomPlaneParser';
import { FurnitureStackingHeightMap, LegacyWallGeometry } from './utils';

export class RoomMessageHandler
{
    private _connection: IConnection = null;
    private _roomEngine: IRoomCreator = null;
    private _planeParser = new RoomPlaneParser();
    private _latestEntryTileEvent: RoomEntryTileMessageEvent = null;

    private _currentRoomId: number = 0;
    private _ownUserId: number = 0;
    private _initialConnection: boolean = true;
    private _guideId: number = -1;
    private _requesterId: number = -1;

    public async init(): Promise<void>
    {
        this._connection = GetCommunication().connection;
        this._roomEngine = GetRoomEngine();

        this._connection.addMessageEvent(new UserInfoEvent(this.onUserInfoEvent.bind(this)));
        this._connection.addMessageEvent(new RoomReadyMessageEvent(this.onRoomReadyMessageEvent.bind(this)));
        this._connection.addMessageEvent(new RoomPaintEvent(this.onRoomPaintEvent.bind(this)));
        this._connection.addMessageEvent(new FloorHeightMapEvent(this.onRoomModelEvent.bind(this)));
        this._connection.addMessageEvent(new RoomHeightMapEvent(this.onRoomHeightMapEvent.bind(this)));
        this._connection.addMessageEvent(new RoomHeightMapUpdateEvent(this.onRoomHeightMapUpdateEvent.bind(this)));
        this._connection.addMessageEvent(new RoomVisualizationSettingsEvent(this.onRoomThicknessEvent.bind(this)));
        this._connection.addMessageEvent(new RoomEntryTileMessageEvent(this.onRoomDoorEvent.bind(this)));
        this._connection.addMessageEvent(new ObjectsRollingEvent(this.onRoomRollingEvent.bind(this)));
        this._connection.addMessageEvent(new ObjectsDataUpdateEvent(this.onObjectsDataUpdateEvent.bind(this)));
        this._connection.addMessageEvent(new FurnitureAliasesEvent(this.onFurnitureAliasesEvent.bind(this)));
        this._connection.addMessageEvent(new FurnitureFloorAddEvent(this.onFurnitureFloorAddEvent.bind(this)));
        this._connection.addMessageEvent(new FurnitureFloorEvent(this.onFurnitureFloorEvent.bind(this)));
        this._connection.addMessageEvent(new FurnitureFloorRemoveEvent(this.onFurnitureFloorRemoveEvent.bind(this)));
        this._connection.addMessageEvent(new FurnitureFloorUpdateEvent(this.onFurnitureFloorUpdateEvent.bind(this)));
        this._connection.addMessageEvent(new FurnitureWallAddEvent(this.onFurnitureWallAddEvent.bind(this)));
        this._connection.addMessageEvent(new FurnitureWallEvent(this.onFurnitureWallEvent.bind(this)));
        this._connection.addMessageEvent(new FurnitureWallRemoveEvent(this.onFurnitureWallRemoveEvent.bind(this)));
        this._connection.addMessageEvent(new FurnitureWallUpdateEvent(this.onFurnitureWallUpdateEvent.bind(this)));
        this._connection.addMessageEvent(new FurnitureDataEvent(this.onFurnitureDataEvent.bind(this)));
        this._connection.addMessageEvent(new ItemDataUpdateMessageEvent(this.onItemDataUpdateMessageEvent.bind(this)));
        this._connection.addMessageEvent(new OneWayDoorStatusMessageEvent(this.onOneWayDoorStatusMessageEvent.bind(this)));
        this._connection.addMessageEvent(new AreaHideMessageEvent(this.onAreaHideMessageEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitDanceEvent(this.onRoomUnitDanceEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitEffectEvent(this.onRoomUnitEffectEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitEvent(this.onRoomUnitEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitExpressionEvent(this.onRoomUnitExpressionEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitHandItemEvent(this.onRoomUnitHandItemEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitIdleEvent(this.onRoomUnitIdleEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitInfoEvent(this.onRoomUnitInfoEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitNumberEvent(this.onRoomUnitNumberEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitRemoveEvent(this.onRoomUnitRemoveEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitStatusEvent(this.onRoomUnitStatusEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitChatEvent(this.onRoomUnitChatEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitChatShoutEvent(this.onRoomUnitChatEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitChatWhisperEvent(this.onRoomUnitChatEvent.bind(this)));
        this._connection.addMessageEvent(new RoomUnitTypingEvent(this.onRoomUnitTypingEvent.bind(this)));
        this._connection.addMessageEvent(new PetFigureUpdateEvent(this.onPetFigureUpdateEvent.bind(this)));
        this._connection.addMessageEvent(new PetExperienceEvent(this.onPetExperienceEvent.bind(this)));
        this._connection.addMessageEvent(new YouArePlayingGameEvent(this.onYouArePlayingGameEvent.bind(this)));
        this._connection.addMessageEvent(new DiceValueMessageEvent(this.onDiceValueMessageEvent.bind(this)));
        this._connection.addMessageEvent(new IgnoreResultEvent(this.onIgnoreResultEvent.bind(this)));
        this._connection.addMessageEvent(new GuideSessionStartedMessageEvent(this.onGuideSessionStartedMessageEvent.bind(this)));
        this._connection.addMessageEvent(new GuideSessionEndedMessageEvent(this.onGuideSessionEndedMessageEvent.bind(this)));
        this._connection.addMessageEvent(new GuideSessionErrorMessageEvent(this.onGuideSessionErrorMessageEvent.bind(this)));
    }

    public setRoomId(id: number): void
    {
        if(this._currentRoomId !== 0)
        {
            if(this._roomEngine) this._roomEngine.destroyRoom(this._currentRoomId);
        }

        this._currentRoomId = id;
        this._latestEntryTileEvent = null;
    }

    public clearRoomId(): void
    {
        this._currentRoomId = 0;
        this._latestEntryTileEvent = null;
    }

    private onUserInfoEvent(event: UserInfoEvent): void
    {
        if(!(event instanceof UserInfoEvent) || !event.connection) return;

        const parser = event.getParser();

        if(!parser) return;

        this._ownUserId = parser.userInfo.userId;
    }

    private onRoomReadyMessageEvent(event: RoomReadyMessageEvent): void
    {
        const parser = event.getParser();

        if(this._currentRoomId !== parser.roomId)
        {
            this.setRoomId(parser.roomId);
        }

        if(this._roomEngine)
        {
            this._roomEngine.setRoomInstanceModelName(parser.roomId, parser.name);
        }

        if(this._initialConnection)
        {
            event.connection.send(new FurnitureAliasesComposer());

            this._initialConnection = false;

            return;
        }

        event.connection.send(new GetRoomEntryDataMessageComposer());
    }

    private onRoomPaintEvent(event: RoomPaintEvent): void
    {
        if(!(event instanceof RoomPaintEvent)) return;

        const parser = event.getParser();

        if(!parser) return;

        const floorType = parser.floorType;
        const wallType = parser.wallType;
        const landscapeType = parser.landscapeType;

        if(this._roomEngine)
        {
            this._roomEngine.updateRoomInstancePlaneType(this._currentRoomId, floorType, wallType, landscapeType);
        }
    }

    private onRoomModelEvent(event: FloorHeightMapEvent): void
    {
        if(!(event instanceof FloorHeightMapEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        if(!parser) return;

        const wallGeometry = this._roomEngine.getLegacyWallGeometry(this._currentRoomId);

        if(!wallGeometry) return;

        this._planeParser.reset();

        const width = parser.width;
        const height = parser.height;

        this._planeParser.initializeTileMap(width, height);

        let entryTile: RoomEntryTileMessageParser = null;

        if(this._latestEntryTileEvent) entryTile = this._latestEntryTileEvent.getParser();

        let doorX = -1;
        let doorY = -1;
        let doorZ = 0;
        let doorDirection = 0;

        let y = 0;

        while(y < height)
        {
            let x = 0;

            while(x < width)
            {
                const tileHeight = parser.getHeight(x, y);

                if(((((y > 0) && (y < (height - 1))) || ((x > 0) && (x < (width - 1)))) && (!(tileHeight == RoomPlaneParser.TILE_BLOCKED))) && ((entryTile == null) || ((x == entryTile.x) && (y == entryTile.y))))
                {
                    if(((parser.getHeight(x, (y - 1)) == RoomPlaneParser.TILE_BLOCKED) && (parser.getHeight((x - 1), y) == RoomPlaneParser.TILE_BLOCKED)) && (parser.getHeight(x, (y + 1)) == RoomPlaneParser.TILE_BLOCKED))
                    {
                        doorX = (x + 0.5);
                        doorY = y;
                        doorZ = tileHeight;
                        doorDirection = 90;
                    }

                    if(((parser.getHeight(x, (y - 1)) == RoomPlaneParser.TILE_BLOCKED) && (parser.getHeight((x - 1), y) == RoomPlaneParser.TILE_BLOCKED)) && (parser.getHeight((x + 1), y) == RoomPlaneParser.TILE_BLOCKED))
                    {
                        doorX = x;
                        doorY = (y + 0.5);
                        doorZ = tileHeight;
                        doorDirection = 180;
                    }
                }

                this._planeParser.setTileHeight(x, y, tileHeight);

                x++;
            }

            y++;
        }

        this._planeParser.setTileHeight(Math.floor(doorX), Math.floor(doorY), doorZ);
        this._planeParser.initializeFromTileData(parser.wallHeight);
        this._planeParser.setTileHeight(Math.floor(doorX), Math.floor(doorY), (doorZ + this._planeParser.wallHeight));

        wallGeometry.scale = LegacyWallGeometry.DEFAULT_SCALE;
        wallGeometry.initialize(width, height, this._planeParser.floorHeight);

        let heightIterator = (parser.height - 1);

        while(heightIterator >= 0)
        {
            let widthIterator = (parser.width - 1);

            while(widthIterator >= 0)
            {
                wallGeometry.setHeight(widthIterator, heightIterator, this._planeParser.getTileHeight(widthIterator, heightIterator));
                widthIterator--;
            }

            heightIterator--;
        }

        const roomMap = this._planeParser.getMapData();

        roomMap.doors.push({
            x: doorX,
            y: doorY,
            z: doorZ,
            dir: doorDirection
        });

        this._roomEngine.createRoomInstance(this._currentRoomId, roomMap);
    }

    private onRoomHeightMapEvent(event: RoomHeightMapEvent): void
    {
        if(!(event instanceof RoomHeightMapEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        if(!parser) return;

        const width = parser.width;
        const height = parser.height;
        const heightMap = new FurnitureStackingHeightMap(width, height);

        let y = 0;

        while(y < height)
        {
            let x = 0;

            while(x < width)
            {
                heightMap.setTileHeight(x, y, parser.getTileHeight(x, y));
                heightMap.setStackingBlocked(x, y, parser.getStackingBlocked(x, y));
                heightMap.setIsRoomTile(x, y, parser.isRoomTile(x, y));

                x++;
            }

            y++;
        }

        this._roomEngine.setFurnitureStackingHeightMap(this._currentRoomId, heightMap);
    }

    private onRoomHeightMapUpdateEvent(event: RoomHeightMapUpdateEvent): void
    {
        if(!(event instanceof RoomHeightMapUpdateEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        if(!parser) return;

        const heightMap = this._roomEngine.getFurnitureStackingHeightMap(this._currentRoomId);

        if(!heightMap) return;

        while(parser.next())
        {
            heightMap.setTileHeight(parser.x, parser.y, parser.tileHeight());
            heightMap.setStackingBlocked(parser.x, parser.y, parser.isStackingBlocked());
            heightMap.setIsRoomTile(parser.x, parser.y, parser.isRoomTile());
        }

        this._roomEngine.refreshTileObjectMap(this._currentRoomId, 'RoomMessageHandler.onRoomHeightMapUpdateEvent()');
    }

    private onRoomThicknessEvent(event: RoomVisualizationSettingsEvent): void
    {
        if(!(event instanceof RoomVisualizationSettingsEvent)) return;

        const parser = event.getParser();

        if(!parser) return;

        const visibleWall = !parser.hideWalls;
        const visibleFloor = true;
        const thicknessWall = parser.thicknessWall;
        const thicknessFloor = parser.thicknessFloor;

        if(this._roomEngine)
        {
            this._roomEngine.updateRoomInstancePlaneVisibility(this._currentRoomId, visibleWall, visibleFloor);
            this._roomEngine.updateRoomInstancePlaneThickness(this._currentRoomId, thicknessWall, thicknessFloor);
        }
    }

    private onRoomDoorEvent(event: RoomEntryTileMessageEvent): void
    {
        if(!(event instanceof RoomEntryTileMessageEvent)) return;

        this._latestEntryTileEvent = event;
    }

    private onRoomRollingEvent(event: ObjectsRollingEvent): void
    {
        if(!(event instanceof ObjectsRollingEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        this._roomEngine.updateRoomObjectFloor(this._currentRoomId, parser.rollerId, null, null, 1, null);
        this._roomEngine.updateRoomObjectFloor(this._currentRoomId, parser.rollerId, null, null, 2, null);

        const furnitureRolling = parser.itemsRolling;

        if(furnitureRolling && furnitureRolling.length)
        {
            for(const rollData of furnitureRolling)
            {
                if(!rollData) continue;

                this._roomEngine.rollRoomObjectFloor(this._currentRoomId, rollData.id, rollData.location, rollData.targetLocation);
            }
        }

        const unitRollData = parser.unitRolling;

        if(unitRollData)
        {
            this._roomEngine.updateRoomObjectUserLocation(this._currentRoomId, unitRollData.id, unitRollData.location, unitRollData.targetLocation);

            const object = this._roomEngine.getRoomObjectUser(this._currentRoomId, unitRollData.id);

            if(object && object.type !== RoomObjectUserType.MONSTER_PLANT)
            {
                let posture = 'std';

                switch(unitRollData.movementType)
                {
                    case ObjectRolling.MOVE:
                        posture = 'mv';
                        break;
                    case ObjectRolling.SLIDE:
                        posture = 'std';
                        break;
                }

                this._roomEngine.updateRoomObjectUserPosture(this._currentRoomId, unitRollData.id, posture);
            }
        }
    }

    private onObjectsDataUpdateEvent(event: ObjectsDataUpdateEvent): void
    {
        if(!(event instanceof ObjectsDataUpdateEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        if(!parser) return;

        for(const object of parser.objects)
        {
            this._roomEngine.updateRoomObjectFloor(this._currentRoomId, object.id, null, null, object.state, object.data);
        }
    }

    private onFurnitureAliasesEvent(event: FurnitureAliasesEvent): void
    {
        if(!(event instanceof FurnitureAliasesEvent) || !event.connection || !this._roomEngine) return;

        const alises = event.getParser().aliases;

        this._connection.send(new GetRoomEntryDataMessageComposer());
    }

    private onFurnitureFloorAddEvent(event: FurnitureFloorAddEvent): void
    {
        if(!(event instanceof FurnitureFloorAddEvent) || !event.connection || !this._roomEngine) return;

        const item = event.getParser().item;

        if(!item) return;

        this.addRoomObjectFurnitureFloor(this._currentRoomId, item);
    }

    private onFurnitureFloorEvent(event: FurnitureFloorEvent): void
    {
        if(!(event instanceof FurnitureFloorEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        if(!parser) return;

        const totalObjects = parser.items.length;

        let iterator = 0;

        while(iterator < totalObjects)
        {
            const object = parser.items[iterator];

            if(object) this.addRoomObjectFurnitureFloor(this._currentRoomId, object);

            iterator++;
        }
    }

    private onFurnitureFloorRemoveEvent(event: FurnitureFloorRemoveEvent): void
    {
        if(!(event instanceof FurnitureFloorRemoveEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        if(!parser) return;

        if(parser.delay > 0)
        {
            setTimeout(() =>
            {
                this._roomEngine.removeRoomObjectFloor(this._currentRoomId, parser.itemId, (parser.isExpired) ? -1 : parser.userId, true);
            }, parser.delay);
        }
        else
        {
            this._roomEngine.removeRoomObjectFloor(this._currentRoomId, parser.itemId, (parser.isExpired) ? -1 : parser.userId, true);
        }
    }

    private onFurnitureFloorUpdateEvent(event: FurnitureFloorUpdateEvent): void
    {
        if(!(event instanceof FurnitureFloorUpdateEvent) || !event.connection || !this._roomEngine) return;

        const item = event.getParser().item;

        if(!item) return;

        const location: IVector3D = new Vector3d(item.x, item.y, item.z);
        const direction: IVector3D = new Vector3d(item.direction);

        this._roomEngine.updateRoomObjectFloor(this._currentRoomId, item.itemId, location, direction, item.data.state, item.data, item.extra);
        this._roomEngine.updateRoomObjectFloorHeight(this._currentRoomId, item.itemId, item.stackHeight);
        this._roomEngine.updateRoomObjectFloorExpiration(this._currentRoomId, item.itemId, item.expires);
    }

    private onFurnitureWallAddEvent(event: FurnitureWallAddEvent): void
    {
        if(!(event instanceof FurnitureWallAddEvent) || !event.connection || !this._roomEngine) return;

        const data = event.getParser().item;

        if(!data) return;

        this.addRoomObjectFurnitureWall(this._currentRoomId, data);
    }

    private onFurnitureWallEvent(event: FurnitureWallEvent): void
    {
        if(!(event instanceof FurnitureWallEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        if(!parser) return;

        const totalObjects = parser.items.length;

        let iterator = 0;

        while(iterator < totalObjects)
        {
            const data = parser.items[iterator];

            if(data) this.addRoomObjectFurnitureWall(this._currentRoomId, data);

            iterator++;
        }
    }

    private onFurnitureWallRemoveEvent(event: FurnitureWallRemoveEvent): void
    {
        if(!(event instanceof FurnitureWallRemoveEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        if(!parser) return;

        this._roomEngine.removeRoomObjectWall(this._currentRoomId, parser.itemId, parser.userId);
    }

    private onFurnitureWallUpdateEvent(event: FurnitureWallUpdateEvent): void
    {
        if(!(event instanceof FurnitureWallUpdateEvent) || !event.connection || !this._roomEngine) return;

        const wallGeometry = this._roomEngine.getLegacyWallGeometry(this._currentRoomId);

        if(!wallGeometry) return;

        const item = event.getParser().item;

        if(!item) return;

        const location = wallGeometry.getLocation(item.width, item.height, item.localX, item.localY, item.direction);
        const direction = new Vector3d(wallGeometry.getDirection(item.direction));

        this._roomEngine.updateRoomObjectWall(this._currentRoomId, item.itemId, location, direction, item.state, item.stuffData);
        this._roomEngine.updateRoomObjectWallExpiration(this._currentRoomId, item.itemId, item.secondsToExpiration);
    }

    private onFurnitureDataEvent(event: FurnitureDataEvent): void
    {
        if(!(event instanceof FurnitureDataEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        this._roomEngine.updateRoomObjectFloor(this._currentRoomId, parser.furnitureId, null, null, parser.objectData.state, parser.objectData);
    }

    private onItemDataUpdateMessageEvent(event: ItemDataUpdateMessageEvent): void
    {
        if(!(event instanceof ItemDataUpdateMessageEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        this._roomEngine.updateRoomObjectWallItemData(this._currentRoomId, parser.furnitureId, parser.data);
    }

    private onOneWayDoorStatusMessageEvent(event: OneWayDoorStatusMessageEvent): void
    {
        if(!(event instanceof OneWayDoorStatusMessageEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        this._roomEngine.updateRoomObjectFloor(this._currentRoomId, parser.itemId, null, null, parser.state, new LegacyDataType());
    }

    private onAreaHideMessageEvent(event: AreaHideMessageEvent): void
    {
        if(!(event instanceof AreaHideMessageEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();
        const areaData = parser.areaData;

        this._roomEngine.updateAreaHide(this._currentRoomId, areaData.furniId, areaData.on, areaData.rootX, areaData.rootY, areaData.width, areaData.length, areaData.invert);
    }

    private onDiceValueMessageEvent(event: DiceValueMessageEvent): void
    {
        if(!(event instanceof DiceValueMessageEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        this._roomEngine.updateRoomObjectFloor(this._currentRoomId, parser.itemId, null, null, parser.value, new LegacyDataType());
    }

    private onRoomUnitDanceEvent(event: RoomUnitDanceEvent): void
    {
        if(!(event instanceof RoomUnitDanceEvent) || !event.connection || !this._roomEngine) return;

        this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, event.getParser().unitId, RoomObjectVariable.FIGURE_DANCE, event.getParser().danceId);
    }

    private onRoomUnitEffectEvent(event: RoomUnitEffectEvent): void
    {
        if(!(event instanceof RoomUnitEffectEvent) || !event.connection || !this._roomEngine) return;

        this._roomEngine.updateRoomObjectUserEffect(this._currentRoomId, event.getParser().unitId, event.getParser().effectId, event.getParser().delay);
    }

    private onRoomUnitEvent(event: RoomUnitEvent): void
    {
        if(!(event instanceof RoomUnitEvent) || !event.connection || !this._roomEngine) return;

        const users = event.getParser().users;

        if(!users || !users.length) return;

        for(const user of users)
        {
            if(!user) continue;

            const location = new Vector3d(user.x, user.y, user.z);
            const direction = new Vector3d(user.dir);

            this._roomEngine.addRoomObjectUser(this._currentRoomId, user.roomIndex, location, direction, user.dir, user.userType, user.figure);

            if(user.webID === this._ownUserId)
            {
                this._roomEngine.setRoomSessionOwnUser(this._currentRoomId, user.roomIndex);
                this._roomEngine.updateRoomObjectUserOwn(this._currentRoomId, user.roomIndex);
            }

            this._roomEngine.updateRoomObjectUserFigure(this._currentRoomId, user.roomIndex, user.figure, user.sex, user.subType, user.isRiding);

            if(RoomObjectUserType.getTypeString(user.userType) === RoomObjectUserType.PET)
            {
                if(this._roomEngine.getPetTypeId(user.figure) === PetType.MONSTERPLANT)
                {
                    this._roomEngine.updateRoomObjectUserPosture(this._currentRoomId, user.roomIndex, user.petPosture);
                }
            }

            this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, user.roomIndex, RoomObjectVariable.FIGURE_IS_MUTED, (GetSessionDataManager().isUserIgnored(user.name) ? 1 : 0));
        }

        this.updateGuideMarker();
    }

    private onRoomUnitExpressionEvent(event: RoomUnitExpressionEvent): void
    {
        if(!(event instanceof RoomUnitExpressionEvent) || !event.connection || !this._roomEngine) return;

        this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, event.getParser().unitId, RoomObjectVariable.FIGURE_EXPRESSION, event.getParser().expression);
    }

    private onRoomUnitHandItemEvent(event: RoomUnitHandItemEvent): void
    {
        if(!(event instanceof RoomUnitHandItemEvent) || !event.connection || !this._roomEngine) return;

        this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, event.getParser().unitId, RoomObjectVariable.FIGURE_CARRY_OBJECT, event.getParser().handId);
    }

    private onRoomUnitIdleEvent(event: RoomUnitIdleEvent): void
    {
        if(!(event instanceof RoomUnitIdleEvent) || !event.connection || !this._roomEngine) return;

        this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, event.getParser().unitId, RoomObjectVariable.FIGURE_SLEEP, (event.getParser().isIdle ? 1 : 0));
    }

    private onRoomUnitInfoEvent(event: RoomUnitInfoEvent): void
    {
        if(!(event instanceof RoomUnitInfoEvent) || !event.connection || !this._roomEngine) return;

        this._roomEngine.updateRoomObjectUserFigure(this._currentRoomId, event.getParser().unitId, event.getParser().figure, event.getParser().gender);
    }

    private onRoomUnitNumberEvent(event: RoomUnitNumberEvent): void
    {
        if(!(event instanceof RoomUnitNumberEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        if(!parser) return;

        this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, parser.unitId, RoomObjectVariable.FIGURE_NUMBER_VALUE, parser.value);
    }

    private onRoomUnitRemoveEvent(event: RoomUnitRemoveEvent): void
    {
        if(!(event instanceof RoomUnitRemoveEvent) || !event.connection || !this._roomEngine) return;

        this._roomEngine.removeRoomObjectUser(this._currentRoomId, event.getParser().unitId);

        this.updateGuideMarker();
    }

    private onRoomUnitStatusEvent(event: RoomUnitStatusEvent): void
    {
        if(!(event instanceof RoomUnitStatusEvent) || !event.connection || !this._roomEngine) return;

        const statuses = event.getParser().statuses;

        if(!statuses || !statuses.length) return;

        const roomInstance = this._roomEngine.getRoomInstance(this._currentRoomId);

        if(!roomInstance) return;

        const zScale = (roomInstance.model.getValue<number>(RoomVariableEnum.ROOM_Z_SCALE) || 1);

        for(const status of statuses)
        {
            if(!status) continue;

            let height = status.height;

            if(height) height = (height / zScale);

            const location = new Vector3d(status.x, status.y, (status.z + height));
            const direction = new Vector3d(status.direction);

            let goal: IVector3D = null;

            if(status.didMove) goal = new Vector3d(status.targetX, status.targetY, status.targetZ);

            this._roomEngine.updateRoomObjectUserLocation(this._currentRoomId, status.id, location, goal, status.canStandUp, height, direction, status.headDirection);
            this._roomEngine.updateRoomObjectUserFlatControl(this._currentRoomId, status.id, null);

            let isPosture = true;
            let postureUpdate = false;
            let postureType = RoomObjectVariable.STD;
            let parameter = '';
            let moveUpdate = false;
            let swimUpdate = false;

            if(status.actions && status.actions.length)
            {
                for(const action of status.actions)
                {
                    if(!action) continue;

                    switch(action.action)
                    {
                        case 'flatctrl':
                            this._roomEngine.updateRoomObjectUserFlatControl(this._currentRoomId, status.id, action.value);
                            break;
                        case 'sign':
                            if(status.actions.length === 1) isPosture = false;

                            this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, status.id, RoomObjectVariable.FIGURE_SIGN, parseInt(action.value));
                            break;
                        case 'gst':
                            if(status.actions.length === 1) isPosture = false;

                            this._roomEngine.updateRoomObjectUserPetGesture(this._currentRoomId, status.id, action.value);
                            break;
                        case 'wav':
                        case 'mv':
                            moveUpdate = true;
                            postureUpdate = true;
                            postureType = action.action;
                            parameter = action.value;
                            break;
                        case 'swim':
                            swimUpdate = true;
                            postureUpdate = true;
                            postureType = action.action;
                            parameter = action.value;
                            break;
                        case 'trd': break;
                        default:
                            postureUpdate = true;
                            postureType = action.action;
                            parameter = action.value;
                            break;
                    }
                }
            }

            if(!moveUpdate && swimUpdate)
            {
                postureUpdate = true;
                postureType = 'float';
            }

            if(postureUpdate) this._roomEngine.updateRoomObjectUserPosture(this._currentRoomId, status.id, postureType, parameter);
            else if(isPosture) this._roomEngine.updateRoomObjectUserPosture(this._currentRoomId, status.id, RoomObjectVariable.STD, '');
        }

        this.updateGuideMarker();
    }

    private onRoomUnitChatEvent(event: RoomUnitChatEvent): void
    {
        if(!event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        if(!parser) return;

        this._roomEngine.updateRoomObjectUserGesture(this._currentRoomId, parser.roomIndex, parser.gesture);
        this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, parser.roomIndex, RoomObjectVariable.FIGURE_TALK, (parser.message.length / 10));
    }

    private onRoomUnitTypingEvent(event: RoomUnitTypingEvent): void
    {
        if(!(event instanceof RoomUnitTypingEvent) || !event.connection || !this._roomEngine) return;

        this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, event.getParser().unitId, RoomObjectVariable.FIGURE_IS_TYPING, event.getParser().isTyping ? 1 : 0);
    }

    private onPetFigureUpdateEvent(event: PetFigureUpdateEvent): void
    {
        if(!(event instanceof PetFigureUpdateEvent) || !event.connection || !this._roomEngine) return;

        const parser = event.getParser();

        if(!parser) return;

        this._roomEngine.updateRoomObjectUserFigure(this._currentRoomId, parser.roomIndex, parser.figureData.figuredata, '', '', parser.isRiding);
    }

    private onPetExperienceEvent(event: PetExperienceEvent): void
    {
        const parser = event.getParser();

        if(!parser) return;

        this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, parser.roomIndex, RoomObjectVariable.FIGURE_GAINED_EXPERIENCE, parser.gainedExperience);
    }

    private onYouArePlayingGameEvent(event: YouArePlayingGameEvent): void
    {
        if(!event) return;

        const parser = event.getParser();

        if(!parser) return;

        this._roomEngine.setRoomEngineGameMode(this._currentRoomId, parser.isPlaying);
    }

    private addRoomObjectFurnitureFloor(roomId: number, data: FurnitureFloorDataParser): void
    {
        if(!data || !this._roomEngine) return;

        const location = new Vector3d(data.x, data.y, data.z);
        const direction = new Vector3d(data.direction);

        if(data.spriteName)
        {
            this._roomEngine.addFurnitureFloorByTypeName(roomId, data.itemId, data.spriteName, location, direction, data.state, data.data, data.extra, data.expires, data.usagePolicy, data.userId, data.username, true, true, data.stackHeight);
        }
        else
        {
            this._roomEngine.addFurnitureFloor(roomId, data.itemId, data.spriteId, location, direction, data.state, data.data, data.extra, data.expires, data.usagePolicy, data.userId, data.username, true, true, data.stackHeight);
        }
    }

    private addRoomObjectFurnitureWall(roomId: number, data: FurnitureWallDataParser): void
    {
        if(!data || !this._roomEngine) return;

        const wallGeometry = this._roomEngine.getLegacyWallGeometry(roomId);

        if(!wallGeometry) return;

        let location: IVector3D = null;

        if(!data.isOldFormat)
        {
            location = wallGeometry.getLocation(data.width, data.height, data.localX, data.localY, data.direction);
        }
        else
        {
            //location = wallGeometry.getLocationOldFormat(data.y, data.z, data.direction);
        }

        const direction = new Vector3d(wallGeometry.getDirection(data.direction));

        this._roomEngine.addFurnitureWall(roomId, data.itemId, data.spriteId, location, direction, data.state, data.stuffData, data.secondsToExpiration, data.usagePolicy, data.userId, data.username);
    }

    private onIgnoreResultEvent(event: IgnoreResultEvent): void
    {
        if(!event) return;

        const parser = event.getParser();

        if(!parser) return;

        const roomSession = GetRoomSessionManager().getSession(this._currentRoomId);

        if(!roomSession) return;

        const userData = roomSession.userDataManager.getUserDataByName(parser.name);

        if(!userData) return;

        switch(parser.result)
        {
            case 1:
            case 2:
                this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, userData.roomIndex, RoomObjectVariable.FIGURE_IS_MUTED, 1);
                return;
            case 3:
                this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, userData.roomIndex, RoomObjectVariable.FIGURE_IS_MUTED, 0);
                return;
        }
    }

    private onGuideSessionStartedMessageEvent(event: GuideSessionStartedMessageEvent): void
    {
        const parser = event.getParser();

        this._guideId = parser.guideUserId;
        this._requesterId = parser.requesterUserId;

        this.updateGuideMarker();
    }

    private onGuideSessionEndedMessageEvent(k: GuideSessionEndedMessageEvent): void
    {
        this.removeGuideMarker();
    }

    private onGuideSessionErrorMessageEvent(k: GuideSessionErrorMessageEvent): void
    {
        this.removeGuideMarker();
    }

    private updateGuideMarker(): void
    {
        const userId = GetSessionDataManager().userId;

        this.setUserGuideStatus(this._guideId, ((this._requesterId === userId) ? AvatarGuideStatus.GUIDE : AvatarGuideStatus.NONE));
        this.setUserGuideStatus(this._requesterId, ((this._guideId === userId) ? AvatarGuideStatus.REQUESTER : AvatarGuideStatus.NONE));
    }

    private removeGuideMarker(): void
    {
        this.setUserGuideStatus(this._guideId, AvatarGuideStatus.NONE);
        this.setUserGuideStatus(this._requesterId, AvatarGuideStatus.NONE);

        this._guideId = -1;
        this._requesterId = -1;
    }

    private setUserGuideStatus(userId: number, status: number): void
    {
        const roomSession = GetRoomSessionManager().getSession(this._currentRoomId);

        if(!roomSession) return;

        const userData = roomSession.userDataManager.getDataByType(userId, RoomObjectType.USER);

        if(!userData) return;

        this._roomEngine.updateRoomObjectUserAction(this._currentRoomId, userData.roomIndex, RoomObjectVariable.FIGURE_GUIDE_STATUS, status);
    }

    // public _SafeStr_10580(event:_SafeStr_2242): void
    // {
    //     var arrayIndex: number;
    //     var discoColours:Array;
    //     var discoTimer:Timer;
    //     var eventParser:_SafeStr_4576 = (event.parser as _SafeStr_4576);
    //     switch (eventParser._SafeStr_7025)
    //     {
    //         case 0:
    //             _SafeStr_4588.init(250, 5000);
    //             _SafeStr_4588._SafeStr_6766();
    //             return;
    //         case 1:
    //             _SafeStr_4231.init(250, 5000);
    //             _SafeStr_4231._SafeStr_6766();
    //             return;
    //         case 2:
    //             NitroEventDispatcher.dispatchEvent(new _SafeStr_2821(this._SafeStr_10593, -1, true));
    //             return;
    //         case 3:
    //             arrayIndex = 0;
    //             discoColours = [29371, 16731195, 16764980, 0x99FF00, 29371, 16731195, 16764980, 0x99FF00, 0];
    //             discoTimer = new Timer(1000, (discoColours.length + 1));
    //             discoTimer.addEventListener(TimerEvent.TIMER, function (k:TimerEvent): void
    //             {
    //                 if (arrayIndex == discoColours.length)
    //                 {
    //                     _SafeStr_10592._SafeStr_21164(_SafeStr_10593, discoColours[arrayIndex++], 176, true);
    //                 } else
    //                 {
    //                     _SafeStr_10592._SafeStr_21164(_SafeStr_10593, discoColours[arrayIndex++], 176, false);
    //                 };
    //             });
    //             discoTimer.start();
    //             return;
    //     };
    // }

    public get currentRoomId(): number
    {
        return this._currentRoomId;
    }
}
