import {describe, test, expect} from 'vitest';
import {type Tile} from './tile';
import {TileCache} from './tile_cache';
import {OverscaledTileID} from './tile_id';

const idA = new OverscaledTileID(10, 0, 10, 0, 1);
const idB = new OverscaledTileID(10, 0, 10, 0, 2);
const idC = new OverscaledTileID(10, 0, 10, 0, 3);
const idD = new OverscaledTileID(10, 0, 10, 0, 4);
const tileA = {tileID: idA} as Tile;
const tileA2 = {tileID: idA} as Tile;
const tileB = {tileID: idB} as Tile;
const tileC = {tileID: idC} as Tile;
const tileD = {tileID: idD} as Tile;

function keysExpected(cache, ids) {
    expect(cache.order).toEqual(ids.map((id) => id.key));
}
describe('TileCache', () => {
    test('complex flow', () => {
        const cache = new TileCache(10, (removed) => {
            expect(removed).toBe('dc');
        });
        expect(cache.getAndRemove(idC)).toBeNull();
        expect(cache.add(idA, tileA)).toBe(cache);
        keysExpected(cache, [idA]);
        expect(cache.has(idA)).toBe(true);
        expect(cache.getAndRemove(idA)).toBe(tileA);
        expect(cache.getAndRemove(idA)).toBeNull();
        expect(cache.has(idA)).toBe(false);
        keysExpected(cache, []);
    });

    test('get without removing', () => {
        const cache = new TileCache(10, () => {
            throw new Error('test "get without removing" failed');
        });
        expect(cache.add(idA, tileA)).toBe(cache);
        expect(cache.get(idA)).toBe(tileA);
        keysExpected(cache, [idA]);
        expect(cache.get(idA)).toBe(tileA);
    });

    test('duplicate add', () => {
        const cache = new TileCache(10, () => {
            throw new Error('test "duplicate add" failed');
        });
        cache.add(idA, tileA);
        cache.add(idA, tileA2);

        keysExpected(cache, [idA, idA]);
        expect(cache.has(idA)).toBeTruthy();
        expect(cache.getAndRemove(idA)).toBe(tileA);
        expect(cache.has(idA)).toBeTruthy();
        expect(cache.getAndRemove(idA)).toBe(tileA2);
    });

    test('expiry', () => {
        const cache = new TileCache(10, (removed) => {
            expect(cache.has(idB)).toBeTruthy();
            expect(removed).toBe(tileA2);
        });

        cache.add(idB, tileB, 0);
        cache.getAndRemove(idB);
        // removing clears the expiry timeout
        cache.add(idB, null);

        cache.add(idA, tileA);
        cache.add(idA, tileA2, 0); // expires immediately and `onRemove` is called.
    });

    test('remove', () => {
        const cache = new TileCache(10, () => {});

        cache.add(idA, tileA);
        cache.add(idB, tileB);
        cache.add(idC, tileC);

        keysExpected(cache, [idA, idB, idC]);
        expect(cache.has(idB)).toBeTruthy();

        cache.remove(idB);

        keysExpected(cache, [idA, idC]);
        expect(cache.has(idB)).toBeFalsy();

        expect(cache.remove(idB)).toBeTruthy();

    });

    test('overflow', () => {
        const cache = new TileCache(1, (removed) => {
            expect(removed).toBe(tileA);
        });
        cache.add(idA, tileA);
        cache.add(idB, tileB);

        expect(cache.has(idB)).toBeTruthy();
        expect(cache.has(idA)).toBeFalsy();
    });

    test('.reset', () => {
        let called;
        const cache = new TileCache(10, (removed) => {
            expect(removed).toBe(tileA);
            called = true;
        });
        cache.add(idA, tileA);
        expect(cache.reset()).toBe(cache);
        expect(cache.has(idA)).toBe(false);
        expect(called).toBeTruthy();
    });

    test('.setMaxSize', () => {
        let numRemoved = 0;
        const cache = new TileCache(10, () => {
            numRemoved++;
        });
        cache.add(idA, tileA);
        cache.add(idB, tileB);
        cache.add(idC, tileC);
        expect(numRemoved).toBe(0);
        cache.setMaxSize(15);
        expect(numRemoved).toBe(0);
        cache.setMaxSize(1);
        expect(numRemoved).toBe(2);
        cache.add(idD, tileD);
        expect(numRemoved).toBe(3);
    });
});
