// Copyright (C) 2021-2022 Intel Corporation
//
// SPDX-License-Identifier: MIT

import { ImageProcessing } from './opencv-interfaces';

export interface HistogramEqualization extends ImageProcessing{
    processImage: (src:ImageData, frameNumber: number)=>ImageData;
}

interface HashedImage{
    frameNumber: number,
    frameData: ImageData,
    timestamp: number,
}

export default class HistogramEqualizationImplementation implements HistogramEqualization {
    private readonly bufferSize: number = 20;
    private cv:any;
    private histHash: HashedImage[];
    public currentProcessedImage: number | undefined;

    constructor(cv:any) {
        this.cv = cv;
        this.histHash = [];
    }

    public processImage(src:ImageData, frameNumber: number) : ImageData {
        const hashedFrame = this.hashedFrame(frameNumber);
        if (!hashedFrame) {
            const { cv } = this;
            let matImage = null;
            const RGBImage = new cv.Mat();
            const YUVImage = new cv.Mat();
            const RGBDist = new cv.Mat();
            const YUVDist = new cv.Mat();
            const RGBADist = new cv.Mat();
            let channels = new cv.MatVector();
            const equalizedY = new cv.Mat();
            try {
                this.currentProcessedImage = frameNumber;
                matImage = cv.matFromImageData(src);
                cv.cvtColor(matImage, RGBImage, cv.COLOR_RGBA2RGB, 0);
                cv.cvtColor(RGBImage, YUVImage, cv.COLOR_RGB2YUV, 0);
                cv.split(YUVImage, channels);
                const [Y, U, V] = [channels.get(0), channels.get(1), channels.get(2)];
                channels.delete();
                channels = null;
                cv.equalizeHist(Y, equalizedY);
                Y.delete();
                channels = new cv.MatVector();
                channels.push_back(equalizedY); equalizedY.delete();
                channels.push_back(U); U.delete();
                channels.push_back(V); V.delete();
                cv.merge(channels, YUVDist);
                cv.cvtColor(YUVDist, RGBDist, cv.COLOR_YUV2RGB, 0);
                cv.cvtColor(RGBDist, RGBADist, cv.COLOR_RGB2RGBA, 0);
                const arr = new Uint8ClampedArray(RGBADist.data, RGBADist.cols, RGBADist.rows);
                const imgData = new ImageData(arr, src.width, src.height);
                this.hashFrame(imgData, frameNumber);
                return imgData;
            } catch (e) {
                throw new Error(e.toString());
            } finally {
                if (matImage) matImage.delete();
                if (channels) channels.delete();
                RGBImage.delete();
                YUVImage.delete();
                RGBDist.delete();
                YUVDist.delete();
                RGBADist.delete();
            }
        } else {
            this.currentProcessedImage = frameNumber;
            return hashedFrame;
        }
    }

    private hashedFrame(frameNumber: number): ImageData|null {
        const hashed = this.histHash.find((_hashed) => _hashed.frameNumber === frameNumber);
        if (hashed) {
            hashed.timestamp = Date.now();
        }
        return hashed?.frameData || null;
    }

    private hashFrame(frameData:ImageData, frameNumber:number):void{
        if (this.histHash.length >= this.bufferSize) {
            const leastRecentlyUsed = this.histHash[0];
            const currentTimestamp = Date.now();
            let diff = currentTimestamp - leastRecentlyUsed.timestamp;
            let leastIndex = 0;
            for (let i = 1; i < this.histHash.length; i++) {
                const currentDiff = currentTimestamp - this.histHash[i].timestamp;
                if (currentDiff > diff) {
                    diff = currentDiff;
                    leastIndex = i;
                }
            }
            this.histHash.splice(leastIndex, 1);
        }
        this.histHash.push({
            frameData,
            frameNumber,
            timestamp: Date.now(),
        });
    }
}
