/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { assert, expect } from "chai";
import { DbResult, Id64 } from "@itwin/core-bentley";
import { ImageSourceFormat, IModel } from "@itwin/core-common";
import { ChannelControl, RenderMaterialElement, SnapshotDb, Texture } from "../../core-backend";
import { IModelTestUtils } from "../IModelTestUtils";
import { withEditTxn } from "../../EditTxn";
function removeUndefined(assetProps) {
    const input = assetProps;
    for (const key of Object.keys(input))
        if (input[key] === undefined)
            delete input[key];
    const maps = assetProps.Map;
    if (maps) {
        for (const mapsKey of Object.keys(maps)) {
            const map = maps[mapsKey];
            for (const mapKey of Object.keys(map))
                if (map[mapKey] === undefined)
                    delete map[mapKey];
        }
    }
    return assetProps;
}
function defaultBooleans(assetProps) {
    const boolKeys = ["HasBaseColor", "HasDiffuse", "HasFinish", "HasReflect", "HasReflectColor", "HasSpecular", "HasSpecularColor"];
    for (const boolKey of boolKeys)
        if (undefined === assetProps[boolKey])
            assetProps[boolKey] = false;
    return assetProps;
}
describe("RenderMaterialElement", () => {
    let imodel;
    let materialNumber = 0;
    let textureNumber = 0;
    before(() => {
        const seedFileName = IModelTestUtils.resolveAssetFile("CompatibilityTestSeed.bim");
        const testFileName = IModelTestUtils.prepareOutputFile("ExportGraphics", "ExportGraphicsTest.bim");
        imodel = IModelTestUtils.createSnapshotFromSeed(testFileName, seedFileName);
    });
    after(() => imodel.close());
    function test(params, expected) {
        const name = `material${++materialNumber}`;
        const paletteName = "palette";
        const id = withEditTxn(imodel, (txn) => RenderMaterialElement.insert(txn, IModel.dictionaryId, name, { ...params, paletteName }));
        expect(Id64.isValidId64(id)).to.be.true;
        const mat = imodel.elements.getElement(id);
        const json = mat.toJSON();
        expect(json.jsonProperties?.materialAssets?.renderMaterial).not.to.be.undefined;
        const actual = removeUndefined(json.jsonProperties.materialAssets.renderMaterial);
        if (expected !== undefined) {
            expected = defaultBooleans(expected);
            expect(actual).to.deep.equal(expected);
        }
        return mat;
    }
    function insertTexture() {
        // This is an encoded png containing a 3x3 square with white in top left pixel, blue in middle pixel, and green in
        // bottom right pixel.  The rest of the square is red.
        const pngData = new Uint8Array([
            137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 3, 0, 0, 0, 3, 8, 2, 0, 0, 0, 217,
            74, 34, 232, 0, 0, 0, 1, 115, 82, 71, 66, 0, 174, 206, 28, 233, 0, 0, 0, 4, 103, 65, 77, 65, 0, 0, 177, 143, 11, 252,
            97, 5, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 14, 195, 0, 0, 14, 195, 1, 199, 111, 168, 100, 0, 0, 0, 24, 73, 68, 65,
            84, 24, 87, 99, 248, 15, 4, 12, 12, 64, 4, 198, 64, 46, 132, 5, 162, 254, 51, 0, 0, 195, 90, 10, 246, 127, 175, 154, 145, 0,
            0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130,
        ]);
        const name = `texture${++textureNumber}`;
        const textureId = withEditTxn(imodel, (txn) => Texture.insertTexture(txn, IModel.dictionaryId, name, ImageSourceFormat.Png, pngData));
        expect(Id64.isValidId64(textureId)).to.be.true;
        return textureId;
    }
    describe("read", () => {
        it("should be able to convert TextureId to hexadecimal string outside javascript's double precision range of 2^53", async () => {
            // LargeNumericRenderMaterialTextureId is a database with a single RenderMaterial whose jsonProperties look something like:
            // {"materialAssets":{"renderMaterial":{"Map":{"Diffuse":{"TextureId":9223372036854775807},"Bump":{"TextureId":18446744073709551615},"Finish":{"TextureId":13835058055282163712}}}}}
            // 9223372036854775807 is equivalent to 2^63 - 1 which is equivalent to the hexadecimal string 0x7fffffffffffffff.
            // 18446744073709551615 is equivalent to 2^64 - 1 which is equivalent to the hexadecimal string 0xffffffffffffffff.
            // 13835058055282163712 is equivalent to 2^63 + (2^63 / 2) which is equivalent to the hexadecimal string 0xc000000000000000.
            const seedFileName = IModelTestUtils.resolveAssetFile("LargeNumericRenderMaterialTextureId.bim");
            const db = SnapshotDb.openFile(seedFileName);
            let id;
            // eslint-disable-next-line @typescript-eslint/no-deprecated
            db.withStatement(`SELECT ECInstanceId from Bis.RenderMaterial`, (stmt) => {
                expect(stmt.step()).to.equal(DbResult.BE_SQLITE_ROW);
                id = stmt.getRow().id;
                expect(stmt.step()).to.equal(DbResult.BE_SQLITE_DONE);
            });
            const renderMatElement = db.elements.getElement(id).toJSON();
            expect(renderMatElement.jsonProperties?.materialAssets?.renderMaterial?.Map?.Diffuse?.TextureId).to.equal("0x7fffffffffffffff");
            expect(renderMatElement.jsonProperties?.materialAssets?.renderMaterial?.Map?.Bump?.TextureId).to.equal("0xffffffffffffffff");
            expect(renderMatElement.jsonProperties?.materialAssets?.renderMaterial?.Map?.Finish?.TextureId).to.equal("0xc000000000000000");
            db.close();
        });
    });
    describe("insert", () => {
        it("with default values", () => {
            test({}, {});
        });
        it("should convert TextureIds to hexadecimal string when loading element", () => {
            /* eslint-disable @typescript-eslint/naming-convention */
            const maps = {
                Pattern: { TextureId: 1 },
                Normal: { TextureId: 0x0000bc0000001234 }, // TextureId has a non-zero briefcase Id
                Bump: { TextureId: 0x000000123456789a }, // TextureId has non-zero digits in the upper 32 bits
                Diffuse: { TextureId: 0x0000bc123456789a }, // TextureId has a non-zero briefcase Id and non-zero digits in the upper 32 bits.
                Finish: { TextureId: 5 },
                GlowColor: { TextureId: 6 },
                Reflect: { TextureId: 7 },
                Specular: { TextureId: 8 },
                TranslucencyColor: { OtherProp2: "test" }, // Should be unchanged, still present.
                TransparentColor: { TextureId: 10, OtherProp: 1 }, // OtherProp should be unchanged, still present.
                Displacement: { TextureId: "0x1" },
                OtherProperty: { OtherProperty: "test" }, // Should be unchanged.
            };
            /* eslint-enable @typescript-eslint/naming-convention */
            const material = test({});
            const jsonProps = material.jsonProperties;
            assert(jsonProps?.materialAssets?.renderMaterial && jsonProps.materialAssets.renderMaterial.Map === undefined);
            jsonProps.materialAssets.renderMaterial.Map = maps;
            withEditTxn(imodel, (txn) => material.update(txn));
            const pathName = imodel.pathName;
            imodel.close(); // Need to close so we can load the element back in with strings instead of numbers.
            imodel = SnapshotDb.openForApplyChangesets(pathName);
            imodel.channels.addAllowedChannel(ChannelControl.sharedChannelName);
            const mat = imodel.elements.getElement(material.id);
            const props = mat.toJSON();
            expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map).to.not.be.undefined;
            expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Pattern?.TextureId).to.equal("0x1");
            expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Normal?.TextureId).to.equal("0xbc0000001234");
            expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Bump?.TextureId).to.equal("0x123456789a");
            expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Diffuse?.TextureId).to.equal("0xbc123456789a");
            expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Finish?.TextureId).to.equal("0x5");
            expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.GlowColor?.TextureId).to.equal("0x6");
            expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Reflect?.TextureId).to.equal("0x7");
            expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Specular?.TextureId).to.equal("0x8");
            expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.TranslucencyColor?.TextureId).to.be.undefined;
            expect((props.jsonProperties?.materialAssets?.renderMaterial?.Map.TranslucencyColor).OtherProp2).to.equal("test");
            expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.TransparentColor?.TextureId).to.equal("0xa");
            expect((props.jsonProperties?.materialAssets?.renderMaterial?.Map.TransparentColor).OtherProp).to.equal(1);
            expect(props.jsonProperties?.materialAssets?.renderMaterial?.Map.Displacement?.TextureId).to.equal("0x1");
            expect(((props.jsonProperties?.materialAssets?.renderMaterial?.Map).OtherProperty).OtherProperty).to.equal("test");
        });
        it("with custom values", () => {
            const params = {
                color: [1, 0, 0],
                specularColor: [0, 1, 0],
                finish: 21,
                transmit: 0.5,
                diffuse: 0.2,
                specular: 0.8,
                reflect: 0.5,
                reflectColor: [0, 0, 1],
            };
            /* eslint-disable @typescript-eslint/naming-convention */
            test(params, {
                HasBaseColor: true, color: params.color,
                HasSpecularColor: true, specular_color: params.specularColor,
                HasFinish: true, finish: params.finish,
                HasTransmit: true, transmit: params.transmit,
                HasDiffuse: true, diffuse: params.diffuse,
                HasSpecular: true, specular: params.specular,
                HasReflect: true, reflect: params.reflect,
                HasReflectColor: true, reflect_color: params.reflectColor,
            });
            params.transmit = undefined;
            /* eslint-disable @typescript-eslint/naming-convention */
            test(params, {
                HasBaseColor: true, color: params.color,
                HasSpecularColor: true, specular_color: params.specularColor,
                HasFinish: true, finish: params.finish,
                HasDiffuse: true, diffuse: params.diffuse,
                HasSpecular: true, specular: params.specular,
                HasReflect: true, reflect: params.reflect,
                HasReflectColor: true, reflect_color: params.reflectColor,
            });
            params.transmit = 0.0;
            /* eslint-disable @typescript-eslint/naming-convention */
            test(params, {
                HasBaseColor: true, color: params.color,
                HasSpecularColor: true, specular_color: params.specularColor,
                HasFinish: true, finish: params.finish,
                HasTransmit: true, transmit: params.transmit,
                HasDiffuse: true, diffuse: params.diffuse,
                HasSpecular: true, specular: params.specular,
                HasReflect: true, reflect: params.reflect,
                HasReflectColor: true, reflect_color: params.reflectColor,
            });
        });
        it("pattern map with default values", () => {
            const textureId = insertTexture();
            test({
                patternMap: {
                    TextureId: textureId,
                },
            }, {
                Map: {
                    Pattern: {
                        TextureId: textureId,
                    },
                },
            });
        });
        it("pattern map with custom values", () => {
            const patternMap = {
                TextureId: insertTexture(),
                pattern_angle: 1,
                pattern_u_flip: true,
                pattern_flip: true,
                pattern_scale: [-1, 2],
                pattern_offset: [-2, 1],
                pattern_scalemode: 3,
                pattern_mapping: 4,
                pattern_weight: 0.5,
            };
            test({ patternMap }, { Map: { Pattern: patternMap } });
        });
        it("pattern map with constant lod parameters", () => {
            const patternMap = {
                TextureId: insertTexture(),
                pattern_angle: 1,
                pattern_u_flip: true,
                pattern_flip: true,
                pattern_scale: [-1, 2],
                pattern_offset: [-2, 1],
                pattern_scalemode: 3,
                pattern_mapping: 4,
                pattern_weight: 0.5,
                pattern_useconstantlod: true,
                pattern_constantlod_repetitions: 0.333,
                pattern_constantlod_offset: [1000, 2000],
                pattern_constantlod_mindistanceclamp: 4,
                pattern_constantlod_maxdistanceclamp: 256,
            };
            test({ patternMap }, { Map: { Pattern: patternMap } });
        });
        it("normal and pattern maps with default values", () => {
            const normalId = insertTexture();
            const patternId = insertTexture();
            expect(normalId).not.to.equal(patternId);
            test({
                patternMap: { TextureId: patternId },
                normalMap: { TextureId: normalId },
            }, {
                Map: {
                    Pattern: { TextureId: patternId },
                    Normal: {
                        TextureId: normalId,
                    },
                },
            });
        });
        it("merges mapping params from normal and pattern maps", () => {
            const normalId = insertTexture();
            const patternId = insertTexture();
            expect(normalId).not.to.equal(patternId);
            const patternMap = {
                TextureId: patternId,
                pattern_scale: [-1, 2],
                pattern_weight: 0.5,
            };
            const normalMap = {
                TextureId: normalId,
                pattern_angle: 0.8,
                pattern_flip: true,
                pattern_weight: 1.5,
            };
            const sharedProps = {
                pattern_scale: patternMap.pattern_scale,
                pattern_weight: patternMap.pattern_weight,
                pattern_angle: normalMap.pattern_angle,
                pattern_flip: normalMap.pattern_flip,
            };
            test({ patternMap, normalMap }, {
                Map: {
                    Pattern: {
                        ...sharedProps,
                        TextureId: patternId,
                    },
                    Normal: {
                        ...sharedProps,
                        TextureId: normalId,
                    },
                },
            });
        });
        it("normal map with default values", () => {
            const id = insertTexture();
            test({ normalMap: { TextureId: id } }, { Map: { Normal: { TextureId: id } } });
        });
        it("normal map with inverted green channel", () => {
            const id = insertTexture();
            test({
                normalMap: {
                    TextureId: id,
                    NormalFlags: 1,
                },
            }, {
                Map: {
                    Normal: {
                        NormalFlags: 1,
                        TextureId: id,
                    },
                },
            });
        });
        it("normal map with scale", () => {
            const id = insertTexture();
            const scale = 2.5;
            test({
                normalMap: {
                    TextureId: id,
                    scale,
                },
            }, {
                pbr_normal: 2.5,
                Map: {
                    Normal: {
                        TextureId: id,
                    },
                },
            });
        });
        it("normal map with flags", () => {
            const id = insertTexture();
            test({
                normalMap: {
                    TextureId: id,
                    NormalFlags: 0xff00,
                },
            }, {
                Map: {
                    Normal: {
                        TextureId: id,
                        NormalFlags: 0xff00,
                    },
                },
            });
        });
        it("normal map with constant lod parameters", () => {
            const normalMap = {
                TextureId: insertTexture(),
                NormalFlags: 2,
                pattern_constantlod_repetitions: 0.333,
                pattern_constantlod_offset: [1000, 2000],
                pattern_constantlod_mindistanceclamp: 4,
                pattern_constantlod_maxdistanceclamp: 256,
            };
            test({ normalMap }, { Map: { Normal: normalMap } });
        });
    });
    describe("clone", () => {
        it("clone maps", async () => {
            const textureId = insertTexture();
            const unknownTextureId = "0xffffff";
            expect(Id64.isValidId64(unknownTextureId)).to.be.true;
            expect(imodel.elements.tryGetElementProps(unknownTextureId)).to.be.undefined;
            const maps = {
                Pattern: { TextureId: textureId },
                Normal: { TextureId: textureId },
                Bump: { TextureId: textureId },
                Diffuse: { TextureId: textureId },
                Finish: { TextureId: textureId },
                GlowColor: { TextureId: textureId },
                Reflect: { TextureId: textureId },
                Specular: { TextureId: textureId },
                TranslucencyColor: { TextureId: textureId },
                TransparentColor: { TextureId: textureId },
                Displacement: { TextureId: textureId },
                ["Unknown"]: { TextureId: textureId },
                ["InvalidTexture"]: { TextureId: Id64.invalid },
                ["UnknownTexture"]: { TextureId: unknownTextureId },
                ["NoTextureId"]: { OtherProp: 1 },
            };
            const material = test({});
            const jsonProps = material.jsonProperties;
            assert(jsonProps?.materialAssets?.renderMaterial && jsonProps.materialAssets.renderMaterial.Map === undefined);
            jsonProps.materialAssets.renderMaterial.Map = maps;
            withEditTxn(imodel, (txn) => material.update(txn));
            const context = {
                findTargetElementId: (sourceId) => {
                    expect(typeof sourceId, `bad id: ${sourceId}`).to.equal("string");
                    expect(Id64.isId64(sourceId), `bad id: ${sourceId}`).to.be.true;
                    return "CLONED";
                },
            };
            const sourceProps = material.toJSON();
            const targetProps = structuredClone(sourceProps);
            // eslint-disable-next-line @typescript-eslint/dot-notation
            await RenderMaterialElement["onCloned"](context, sourceProps, targetProps);
            expect(targetProps.jsonProperties?.materialAssets?.renderMaterial?.Map).to.deep.equal({
                Pattern: { TextureId: "CLONED" },
                Normal: { TextureId: "CLONED" },
                Bump: { TextureId: "CLONED" },
                Diffuse: { TextureId: "CLONED" },
                Finish: { TextureId: "CLONED" },
                GlowColor: { TextureId: "CLONED" },
                Reflect: { TextureId: "CLONED" },
                Specular: { TextureId: "CLONED" },
                TranslucencyColor: { TextureId: "CLONED" },
                TransparentColor: { TextureId: "CLONED" },
                Displacement: { TextureId: "CLONED" },
                Unknown: { TextureId: "CLONED" },
                InvalidTexture: { TextureId: Id64.invalid },
                UnknownTexture: { TextureId: "CLONED" },
                NoTextureId: { OtherProp: 1 },
            });
            jsonProps.materialAssets.renderMaterial.Map = { Pattern: undefined };
            // eslint-disable-next-line @typescript-eslint/dot-notation
            await RenderMaterialElement["onCloned"](context, sourceProps, targetProps);
            // keep the sourceMap the same in targetProps
            expect(targetProps.jsonProperties?.materialAssets?.renderMaterial?.Map).to.have.property("Pattern").that.is.undefined;
            jsonProps.materialAssets.renderMaterial.Map = { Pattern: null };
            // eslint-disable-next-line @typescript-eslint/dot-notation
            await RenderMaterialElement["onCloned"](context, sourceProps, targetProps);
            // keep the sourceMap the same in targetProps
            expect(targetProps.jsonProperties?.materialAssets?.renderMaterial?.Map).to.have.property("Pattern").that.is.null;
        });
    });
});
//# sourceMappingURL=RenderMaterialElement.test.js.map