import NullAudio from './Audio/NullAudio';
import NullClock from './Clock/NullClock';
import Events from './Events/Events';
import NullGRoot from './GRoot/NullGRoot';
import NullLocales from './Locales/NullLocales';
import NullNetwork from './Network/NullNetwork';
import NullRenderer from './Renderer/NullRenderer';
import NullScenes from './Scenes/NullScenes';
import NullStorageService from './Storage/NullStorageService';
import NullDevUser from './User/NullDevUser';
import NullManifestService from './Manifest/NullManifestService';
import type { ManifestService as ManifestServiceType } from './Manifest';
import { NullViewport } from './Viewport';

import type Audio from './Audio/Audio';
import type AudioSprites from './Audio/AudioSprites';
import type LogAudio from './Audio/LogAudio';
import type MixerAudio from './Audio/MixerAudio';
import type MixerAudioSprites from './Audio/MixerAudioSprites';
import type Clock from './Clock/Clock';
import type DevClock from './Clock/DevClock';
import type GRoot from './GRoot/GRoot';
import type DevLocales from './Locales/DevLocales';
import type Locales from './Locales/Locales';
import type LocalNetwork from './Network/LocalNetwork';
import type MockNetwork from './Network/MockNetwork';
import type Network from './Network/Network';
import type Renderer2D from './Renderer/Renderer2D';
import type DevScenes from './Scenes/DevScenes';
import type Scenes from './Scenes/Scenes';
import type StorageService from './Storage/StorageService';
import type { Service } from './types';
import type DevUser from './User/DevUser';
import type Viewport from './Viewport/Viewport';
import {BaseAnalytics} from "./Analytics/Analytics";
import {NullAnalytics} from "./Analytics";

type AudioService =
    | Audio
    | AudioSprites
    | LogAudio
    | MixerAudio
    | MixerAudioSprites
    | NullAudio;

type ClockService = Clock | DevClock | NullClock;
type RendererService = Renderer2D | NullRenderer;
type NetworkService = NullNetwork | LocalNetwork | Network | MockNetwork;
type LocaleService = Locales | DevLocales | NullLocales;
type ManifestServiceUnion = ManifestServiceType | NullManifestService;
type SceneService = Scenes | DevScenes | NullScenes;
type StorageServiceType = StorageService | NullStorageService;
type UserService = DevUser | NullDevUser;
type AnalyticsService = BaseAnalytics
type ViewportService = Viewport | NullViewport;
type GrootService = GRoot | NullGRoot;

type ServiceRegistry = {
    user: UserService;
    clock: ClockService;
    renderer2D: RendererService;
    network: NetworkService;
    manifest: ManifestServiceUnion,
    viewport: ViewportService;
    events: Events;
    audio: AudioService;
    storage: StorageServiceType;
    analytics: AnalyticsService;
    locales: LocaleService;
    scenes: SceneService;
    groot: GrootService;
};

/** Central registry that wires game components to their concrete service implementations. */
export default class Locator
{
    private static _services: ServiceRegistry = {
        user: new NullDevUser(),
        clock: new NullClock(),
        renderer2D: new NullRenderer(),
        network: new NullNetwork(),
        manifest: new NullManifestService(),
        viewport: new NullViewport(),
        events: new Events(),
        audio: new NullAudio(),
        storage: new NullStorageService(),
        analytics: new NullAnalytics(),
        locales: new NullLocales(),
        scenes: new NullScenes(),
        groot: new NullGRoot(),
    };

    public static provide<T extends keyof ServiceRegistry>(service: ServiceRegistry[T] & Service & { serviceType: T }): void
    {
        if (!service)
        {
            return;
        }

        const key = service.serviceType;

        if (!(key in this._services))
        {
            throw new Error(`Locator doesn't provide ${service.serviceType}`);
        }

        this._services[key] = service;
    }

    public static get services(): ServiceRegistry
    {
        return this._services;
    }

    public static get user(): UserService
    {
        return this._services.user;
    }

    public static get clock(): ClockService
    {
        return this._services.clock;
    }

    public static get renderer(): RendererService
    {
        return this._services.renderer2D;
    }

    public static get network(): NetworkService
    {
        return this._services.network;
    }

    public static get manifest(): ManifestServiceUnion
    {
        return this._services.manifest;
    }

    public static get viewport(): ViewportService
    {
        return this._services.viewport;
    }

    public static get events(): Events
    {
        return this._services.events;
    }

    public static get audio(): AudioService
    {
        return this._services.audio;
    }

    public static get storage(): StorageServiceType
    {
        return this._services.storage;
    }

    public static get analytics(): AnalyticsService
    {
        return this._services.analytics;
    }

    public static get locales(): LocaleService
    {
        return this._services.locales;
    }

    public static get groot(): GrootService
    {
        return this._services.groot;
    }

    public static get scenes(): SceneService
    {
        return this._services.scenes;
    }
}
