import * as utils from './utils.js'

export function runMetrics(links, boundingboxes_in) {
    boundingboxes = boundingboxes_in;

    dissimilarities = links.map(link => 1-link.similarity);
    let distances = links.map(link => getMinimumDistanceBetweenRectangles(link, boundingboxes));

    console.group("Metrics:");

    let distortion = calculateDistortion(dissimilarities, distances);
    console.log("Distortion:" + distortion);

    let { realizedAdjacencies, realizedLinks } = calculateRealizedAdjacencies(links);
    console.log("Realized adjacency ratio: " + realizedAdjacencies)

    let compactness = calculateCompactness(boundingboxes);
    console.log("Compactness: " + compactness);

    console.groupEnd();

    return {
        distortion: distortion,
        realizedAdjacencies: realizedAdjacencies,
        compactness: compactness,
        realizedLinks: realizedLinks
    }
}

const TOLERANCE = 0.2;
let boundingboxes;
let dissimilarities;

export function approximateDistortion(links, boundingrects) {
    let distances = links.map(link => getMinimumDistanceBetweenRectangles(link, boundingrects));
    return calculateDistortion(dissimilarities, distances);
}


function getDistanceBetweenCenters(link) {
    return Math.sqrt(Math.pow((link.source.x - link.target.x),2) + Math.pow((link.source.y-link.target.y),2));
}

function getMinimumDistanceBetweenCorners(link, boundingboxes) {
    let corners1 = getCorners(boundingboxes[link.source.id]);
    let corners2 = getCorners(boundingboxes[link.target.id]);

    let dist = Number.POSITIVE_INFINITY;
    for (const cornerA of corners1) {
        for (const cornerB of corners2) {
            dist = Math.min(dist, getEuclideanDistance(cornerA, cornerB))
        }
    }
    return dist;
}

export function getMinimumDistanceBetweenRectangles(link, boundingboxes) {
    let rect1 = boundingboxes[link.source.id];
    let rect2 = boundingboxes[link.target.id];

    // if the rectangles overlap, take 0
    if (areIntervalsIntersecting(Math.max(rect1.width, rect2.width), rect1.left, rect1.right, rect2.left, rect2.right)
    && areIntervalsIntersecting(Math.max(rect1.height, rect2.height), rect1.top, rect1.bottom, rect2.top, rect2.bottom)) {
        return 0;
    }// if the rectangles overlap in x-direction, take y-distance
    else if (areIntervalsIntersecting(Math.max(rect1.width, rect2.width), rect1.left, rect1.right, rect2.left, rect2.right)) {
        return Math.min(Math.abs(rect1.bottom - rect2.top), Math.abs(rect1.top - rect2.bottom));
    } // if the rectangles overlap in y-direction, take x-distance
    else if (areIntervalsIntersecting(Math.max(rect1.height, rect2.height), rect1.top, rect1.bottom, rect2.top, rect2.bottom)) {
        return Math.min(Math.abs(rect1.right - rect2.left), Math.abs(rect1.left - rect2.right));
    }
    else {
        // otherwise, take the minimum distance between the corners
        let corners1 = getCorners(rect1);
        let corners2 = getCorners(rect2);

        let dist = Number.POSITIVE_INFINITY;
        for (const cornerA of corners1) {
            for (const cornerB of corners2) {
                dist = Math.min(dist, getEuclideanDistance(cornerA, cornerB))
            }
        }  
        return dist;
    }
}

function getEuclideanDistance(p1, p2) {
    return Math.sqrt(Math.pow((p1.x - p2.x),2) + Math.pow((p1.y-p2.y),2));
}

function getCorners(rect) {
    return [
        {x: rect.left, y: rect.top},
        {x: rect.right, y: rect.top},
        {x: rect.left, y: rect.bottom},
        {x: rect.right, y: rect.top}
    ]
}

function calculateDistortion(dissimilarities, distances) {
    let avgDistance = distances.reduce((a,b) => a+b, 0) / distances.length || 0;
    let avgDissimilarity = dissimilarities.reduce((a,b) => a+b, 0) / dissimilarities.length || 0;
    
    let nominator = 0;
    let denominator1 = 0;
    let denominator2 = 0;
    for (let i = 0; i < distances.length; i++) {
        nominator += (dissimilarities[i] - avgDissimilarity)*(distances[i] - avgDistance);
        denominator1 += Math.pow((dissimilarities[i] - avgDissimilarity), 2);
        denominator2 += Math.pow((distances[i] - avgDistance), 2);
    }

    let correlationCoefficient = nominator / Math.sqrt(denominator1*denominator2);
    let distortion = (correlationCoefficient+1)/2;

    return distortion;
}

function calculateRealizedAdjacencies(links) {
    let realizedAdjacencies = 0;
    let totalAdjacencies = 0;
    let realizedLinks = [];
    console.groupCollapsed("List of Realized Adjacencies:")
    for(let link of links) {
        let rect1 = boundingboxes[link.source.id];
        let rect2 = boundingboxes[link.target.id];
        if (areRectsTouching(rect1,rect2)) {
            realizedLinks.push(link);
            console.log(link.source.word + "-" + link.target.word);
            realizedAdjacencies += link.similarity;
        }
        totalAdjacencies +=link.similarity;
    }
    console.groupEnd();
    return { realizedAdjacencies: realizedAdjacencies/totalAdjacencies, realizedLinks: realizedLinks };
}

function calculateCompactness(boundingboxes) {
    let usedArea = 0;

    let maxTop = Number.POSITIVE_INFINITY;
    let maxBottom = Number.NEGATIVE_INFINITY;
    let maxLeft = Number.POSITIVE_INFINITY;
    let maxRight = Number.NEGATIVE_INFINITY;

    for (let rect of boundingboxes) {
        let rectArea = rect.width * rect.height;
        usedArea += rectArea;

        maxTop = Math.min(maxTop, rect.top);
        maxBottom = Math.max(maxBottom, rect.bottom);
        maxLeft = Math.min(maxLeft, rect.left);
        maxRight = Math.max(maxRight, rect.right);

        
    }

    let totalArea = (maxRight - maxLeft) * (maxBottom - maxTop);

    return usedArea/totalArea;
}

function areRectsTouching(rect1, rect2) {
    let xIntersect = areIntervalsIntersecting(Math.min(rect1.width, rect2.width), rect1.left, rect1.right, rect2.left, rect2.right);
    let yIntersect = areIntervalsIntersecting(Math.min(rect1.height, rect2.height), rect1.top, rect1.bottom, rect2.top, rect2.bottom);
    return xIntersect && yIntersect;
}

function areIntervalsIntersecting(size, left1, right1, left2, right2) {
    if (right1 + size*TOLERANCE <= left2) return false;
    else if (right2 + size*TOLERANCE <= left1) return false;
    else return true;
}