import cloneDeep from 'lodash/cloneDeep';
import isEqual from 'lodash/isEqual';
import omit from 'lodash/omit';
import find from 'lodash/find';
import flatten from 'lodash/flatten';
import get from 'lodash/get';
import { getAggregatedColors } from './Selectors';
import { calculateColorDistance } from '../DesignArea/Selectors';

export const setColorOnOperations = (prevOperations, color) => {
    const operations = cloneDeep(prevOperations);

    for (const colorKey in operations.vectorizeColorsMap) {
        if (!operations.vectorizeColorsMap.hasOwnProperty(colorKey)) {
            continue;
        }

        operations.vectorizeColorsMap[colorKey] = color;
    }

    return operations;
};

export const replaceColorOnOperations = (prevOperations, oldColor, color) => {
    const operations = cloneDeep(prevOperations);

    for (const colorKey in operations.vectorizeColorsMap) {
        if (!operations.vectorizeColorsMap.hasOwnProperty(colorKey)) {
            continue;
        }

        if (operations.vectorizeColorsMap[colorKey].identifier === oldColor.identifier) {
            operations.vectorizeColorsMap[colorKey] = color;
        }
    }

    return operations;
};

export const getEditDataFromOperations = (imageFileName, operations, designProductionMethod) => {
    const {
        clipping = false,
        clippingColor = false,
        vectorize = false,
        vectorizeThreshold = null,
    } = operations;

    const maxColorAmount = +get(designProductionMethod, 'options.maxColorAmount', 0);

    const vectorizedLogoMandatory = get(designProductionMethod, 'options.vectorizedLogoMandatory', false);
    const vectorizeMonochrome = maxColorAmount === 1 && vectorizeThreshold === 1;

    const result = {
        fileName: imageFileName,
        operations: [],
        vectorizedLogoMandatory,
    };

    if (clipping && clippingColor) {
        result.operations.push({
            identifier: 'clipping',
            config: {
                command: clipping,
                backgroundColor: clippingColor,
            },
        });
    }

    if (vectorize) {
        result.operations.push({
            identifier: 'vectorize',
            config: {
                threshold: vectorizeThreshold,
                monochrome: vectorizeMonochrome,
            },
        });
    }

    return result;
};

export const compareOperations = (oldOperations, newOperations) => {
    return isEqual(omit(oldOperations, ['vectorizeColorsMap']), omit(newOperations, ['vectorizeColorsMap']));
};

export const createVectorizeColorsMap = (currentColorsMap, svgColors) => {
    // make sure the original color ordering is kept by creating placeholders for the keys
    const vectorizeColorsMap = svgColors.reduce((obj, { key }) => ({ ...obj, [key]: undefined }), {});

    currentColorsMap &&
        Object.keys(currentColorsMap).forEach(key => {
            let oldColor = currentColorsMap[key];

            if (!find(svgColors, { key })) {
                return;
            }

            vectorizeColorsMap[key] = oldColor;
        });

    for (const { key, color } of svgColors) {
        if (!vectorizeColorsMap[key]) {
            vectorizeColorsMap[key] = color;
        }
    }

    return vectorizeColorsMap;
};

export const isOneColorAboveThreshold = (vectorizeColorsMap, vectorizeOriginalColorsMap) => {
    const threshold = 1900;

    const aggregatedInitialColors = getAggregatedColors(vectorizeOriginalColorsMap || {});
    const aggregatedCurrentColors = getAggregatedColors(vectorizeColorsMap || {});

    if (Object.keys(aggregatedInitialColors).length !== Object.keys(aggregatedCurrentColors).length) {
        return true;
    }

    const numberOfCustomColors = Object.values(aggregatedCurrentColors).filter(
        c => c.identifier && JSON.parse(c.identifier).custom
    ).length;

    const numberOfNotMatchingColors = Object.entries(aggregatedInitialColors).reduce(
        (accumulator, [key, initialColor]) => {
            const currentColor = Object.values(aggregatedCurrentColors).find(
                c => c.value === initialColor.value
            );

            if (currentColor) {
                return accumulator;
            }

            if (!aggregatedCurrentColors[key]) {
                return accumulator + 1;
            }

            const colorDistance = calculateColorDistance(
                initialColor.value,
                aggregatedCurrentColors[key].value
            );

            return colorDistance === null || colorDistance > threshold ? accumulator + 1 : accumulator;
        },
        0
    );

    return numberOfNotMatchingColors > numberOfCustomColors;
};

export const removeVectorizeFromOperations = operations => ({
    ...operations,
    vectorize: false,
    vectorizeThreshold: null,
    vectorizeMaxColorAmount: undefined,
    vectorizeColorsMap: {},
    vectorizeOriginalColorsMap: {},
    vectorizeMonochrome: false,
});

const getColorKeysForIdentifier = (vectorizeColorsMap, identifier) => {
    return Object.entries(vectorizeColorsMap)
        .filter(colorEntry => colorEntry[1].identifier === identifier)
        .map(colorEntry => colorEntry[0]);
};

const findDistinctIdentifiers = (howMany, vectorizeColorsMap) => {
    const distinctIdentifiers = [];

    Object.values(vectorizeColorsMap).forEach(colorEntry => {
        if (!distinctIdentifiers.includes(colorEntry.identifier) && distinctIdentifiers.length < howMany) {
            distinctIdentifiers.push(colorEntry.identifier);
        }
    });

    return distinctIdentifiers;
};

export const reduceNumberOfDistinctColorsBy = (vectorizeColorsMap, numberOfColorsToRemove) => {
    const numberOfUniqueColorValues = Object.values(vectorizeColorsMap)
        .map(c => c.identifier)
        .filter((c, index, array) => array.indexOf(c) === index).length;

    if (numberOfUniqueColorValues <= numberOfColorsToRemove) {
        throw new Error(
            'vectorizeColorsMap must have more unique colors than the number of colors we are reducing it by'
        );
    }

    const identifiers = findDistinctIdentifiers(numberOfColorsToRemove, vectorizeColorsMap);

    const keysForValuesToBeOverWritten = flatten(
        identifiers.map(identifier => getColorKeysForIdentifier(vectorizeColorsMap, identifier))
    );

    const sourceColorValue = Object.entries(vectorizeColorsMap).find(colorEntry => {
        return !keysForValuesToBeOverWritten.includes(colorEntry[0]);
    })[1];

    const resultingVecotirzeColorsMap = {};

    for (let key in vectorizeColorsMap) {
        if (keysForValuesToBeOverWritten.find(k => k === key)) {
            resultingVecotirzeColorsMap[key] = sourceColorValue;
        } else {
            resultingVecotirzeColorsMap[key] = vectorizeColorsMap[key];
        }
    }
    return resultingVecotirzeColorsMap;
};

export const applyProductionMethodChangeToCanvasObjects = (
    newProductionMethod,
    oldProductionMethod,
    objects
) =>
    [updatePositioningAndScaling, updateLockState].reduce(
        (objects, updateFunc) => updateFunc(newProductionMethod, oldProductionMethod, objects),
        objects
    );

const updatePositioningAndScaling = (newProductionMethod, oldProductionMethod, objects) => {
    const { width: widthA, height: heightA } = newProductionMethod;
    const { width: widthB, height: heightB } = oldProductionMethod;
    if (widthA !== widthB || heightA !== heightB) {
        const scaleX = widthA / widthB;
        const scaleY = heightA / heightB;
        return objects.map(object => {
            const { x, y } = object;
            const fixedObject = {
                ...object,
                x: x * scaleX,
                y: y * scaleY,
            };
            if (fixedObject.fontSize) {
                fixedObject.fontSize *= scaleY;
            }
            return fixedObject;
        });
    }
    return objects;
};

const updateLockState = (newProductionMethod, oldProductionMethod, objects) => {
    const { designElementsLocked: lockedA } = newProductionMethod;
    const { designElementsLocked: lockedB } = oldProductionMethod;
    if (lockedA !== lockedB) {
        return objects.map(object => ({
            ...object,
            rotation: 0,
            autoFit: lockedA,
            locked: lockedA,
        }));
    }
    return objects;
};

export const extendCanvasDataWithMetricContentSize = canvasData => {
    const b = { minX: Infinity, maxX: -Infinity, minY: Infinity, maxY: -Infinity };

    const mergeRotated = (cx, cy, hw, hh, deg) => {
        const rad = (deg * Math.PI) / 180;
        const c = Math.cos(rad);
        const s = Math.sin(rad);
        for (const [dx, dy] of [
            [-hw, -hh],
            [hw, -hh],
            [hw, hh],
            [-hw, hh],
        ]) {
            const x = cx + dx * c - dy * s;
            const y = cy + dx * s + dy * c;
            b.minX = Math.min(b.minX, x);
            b.maxX = Math.max(b.maxX, x);
            b.minY = Math.min(b.minY, y);
            b.maxY = Math.max(b.maxY, y);
        }
    };

    const mergeAxisAligned = (cx, cy, hw, hh) => {
        b.minX = Math.min(b.minX, cx - hw);
        b.maxX = Math.max(b.maxX, cx + hw);
        b.minY = Math.min(b.minY, cy - hh);
        b.maxY = Math.max(b.maxY, cy + hh);
    };

    for (const object of canvasData.objects) {
        if (!object.metric) continue;
        const w = object.metric.width || 0;
        const h = object.metric.height || 0;
        const hw = w / 2;
        const hh = h / 2;
        const cx = object.metric.x || 0;
        const cy = object.metric.y || 0;

        if (typeof object.rotation === 'number' && object.rotation !== 0 && w && h) {
            mergeRotated(cx, cy, hw, hh, object.rotation);
        } else {
            mergeAxisAligned(cx, cy, hw, hh);
        }
    }

    if (!Number.isFinite(b.minX)) {
        return canvasData;
    }

    return {
        ...canvasData,
        metricContentSize: {
            width: Math.abs(b.maxX - b.minX),
            height: Math.abs(b.maxY - b.minY),
        },
    };
};
