// Copyright (c) 2016-2018 VMware, Inc. All Rights Reserved.
// This software is released under MIT license.
// The full license information can be found in LICENSE in the root directory of this project.

// color helper functions that support the user-facing functions in color.clarity.scss

// cOLOR lOOKUPS

@function _clr-getColorFromList($colorIndex, $colors) {
    $default-labels-map: (
        lightest: 1,
        lighter: 2,
        light: 3,
        light-midtone: 4,
        dark-midtone: 5,
        dark: 6,
        darker: 7,
        darkest: 8,
        accessible: 9,
    );

    // gray needs its own special map because design introduced more gradations in tone
    // after the fact.
    $gray-labels-map: (
        lightest: 1,
        lighter: 2,
        light: 3,
        light-midtone: 5,
        dark-midtone: 6,
        dark: 7,
        darker: 8,
        darkest: 9,
    );

    $labels-to-indices-map: if($colors == $clr-gray-list, $gray-labels-map, $default-labels-map);

    // sending an index of 0 or the word 'white' will return white
    @if ($colorIndex == 0 or $colorIndex == white) {
        @return $clr-white;
    }

    // sending the word 'black' will return black
    @if ($colorIndex == black) {
        @return $clr-black;
    }

    @if map-has-key($labels-to-indices-map, $colorIndex) {
        @return nth($colors, map-get($labels-to-indices-map, $colorIndex));
    }

    @return nth($colors, $colorIndex);
}

@function _clr-lookupColorMap($whichOne: grays) {
    $clr-all-colors: (
        base: $clr-base-colors,
        grays: $clr-grays,
        neutrals: $clr-grays,
        action-blues: $clr-action-blues,
        action-purples: $clr-action-purples,
        purples: $clr-action-purples,
        reds: $clr-reds,
        greens: $clr-greens,
        yellows: $clr-yellows,
    );

    @if map-has-key($clr-all-colors, $whichOne) {
        @return map-get($clr-all-colors, $whichOne);
    }

    @warn '_clr-lookupColorMap: #{$whichOne} not found in collection of color-maps';
    @return null;
}

// cOLOR cONTRAST hELPERS

// where $primaryColor is the value of red, green, or blue for a given color
@function _clr-getChromaticity($primaryColor) {
    $color: $primaryColor/255;

    @if $color <= 0.03928 {
        @return $color/12.92;
    }

    $color: ($color + 0.055)/1.055;

    @return pow($color, 2.4);
}

// helper functions for contrast testing...
@function _clr-getLuminance($color: #fff, $rgbOrAll: all) {
    $redMultiplier: 0.2126;
    $greenMultiplier: 0.7152;
    $blueMultiplier: 0.0722;

    $returnLuminance: 0;

    $doAll: ($rgbOrAll == all);
    $justRed: ($rgbOrAll == red or $rgbOrAll == r or $rgbOrAll == R);
    $justGreen: ($rgbOrAll == green or $rgbOrAll == g or $rgbOrAll == G);
    $justBlue: ($rgbOrAll == blue or $rgbOrAll == b or $rgbOrAll == B);

    @if $doAll or $justRed {
        $returnLuminance: $returnLuminance + ($redMultiplier * _clr-getChromaticity(red($color)));

        @if $justRed {
            @return $returnLuminance;
        }
    }

    @if $doAll or $justGreen {
        $returnLuminance: $returnLuminance + ($greenMultiplier * _clr-getChromaticity(green($color)));

        @if $justGreen {
            @return $returnLuminance;
        }
    }

    @if $doAll or $justBlue {
        $returnLuminance: $returnLuminance + ($blueMultiplier * _clr-getChromaticity(blue($color)));

        @if $justBlue {
            @return $returnLuminance;
        }
    }

    @return $returnLuminance;
}

@function _clr-getContrastFailThreshold($weight: normal, $size: normal) {
    $defaultContrast: 4.5;
    $lowContrast: 3;
    $highContrast: 7;
    $superHighContrast: 13;

    @if ($size == big and $weight != thin) or ($weight == bold and $size != small) {
        @return $lowContrast;
    } @else if $size == small and $weight == thin {
        @return $superHighContrast;
    } @else if ($size == small and $weight != bold) or $weight == thin {
        @return $highContrast;
    }

    // normal is the default; this is the fall through;
    @return $defaultContrast;
}

@function _clr-getContrastRatio($highValue, $lowValue) {
    $rawRatio: ($highValue + 0.05)/ ($lowValue + 0.05);
    @return decimal-round($rawRatio, 2);
}

@function _clr-testColorContrast(
    $foreground: clr-getColor(lightest),
    $background: clr-getColor(darkest),
    $failThreshold: 4.5
) {
    $fgLightness: _clr-getLuminance($foreground);
    $bgLightness: _clr-getLuminance($background);
    $hi: $bgLightness;
    $lo: $fgLightness;

    @if ($fgLightness > $bgLightness) {
        $hi: $fgLightness;
        $lo: $bgLightness;
    }

    $ratio: _clr-getContrastRatio($hi, $lo);
    $pass: false;

    @if ($ratio >= $failThreshold) {
        $pass: true;
    }

    // @debug '--------------------------------------';
    // @debug '#{$foreground} #{$background} failThreshold: #{$failThreshold} vs. #{$ratio} :: #{$ratio >= $failThreshold} :: pass = #{$pass}';

    @return if($pass, true, false);
}

@function _clr-isLightOrDark($color: #fff, $failThreshold: 4.5) {
    @if (_clr-testColorContrast(#fff, $color, $failThreshold)) {
        // yes, this is a little unscientific but we want to force the issue on white text over a background
        // so if something passes white, we want it to register as dark. this catches more edge cases than a
        // luminance range.
        @return dark;
    }

    @if (_clr-testColorContrast(#000, $color, $failThreshold)) {
        @return light;
    }

    // rare case. generally indicates something is broken.
    @warn '_clr-isLightOrDark could not determine type for #{$color}. There may be a breakage in the color contrast helpers.';

    @return null;
}

@function _clr-getAchromaticColor($background: #fff, $whiteOrBlack: black, $failThreshold: 4.5) {
    @if (not(($whiteOrBlack == white) or ($whiteOrBlack == black))) {
        @warn '_clr-getAchromaticColor passed a non-black-or-white option to lookup. Expect breakage.';
        @return null;
    }

    $lightOrDark: _clr-isLightOrDark($background, $failThreshold);

    @if ($whiteOrBlack == white and $lightOrDark == dark) {
        @if (_clr-testColorContrast($background, $clr-white, $failThreshold)) {
            @return $clr-white;
        } @else {
            // clr-white might be different than pure white but we know this passes
            // from the lightOrDark test above.
            @return #fff;
        }
    }

    @if ($whiteOrBlack == black and $lightOrDark == light) {
        @if (_clr-testColorContrast($background, $clr-black, $failThreshold)) {
            @return $clr-black;
        } @else {
            // clr-black might be different than pure black but we know this passes
            // from the lightOrDark test above.
            @return #000;
        }
    }

    @warn '_clr-getAchromaticColor asked to get #{$whiteOrBlack} contrast against #{$background} background.';
}

@function _clr-testAndReturnTextColor(
    $background: #fff,
    $textTypeToReturn: darkest,
    $expectedBgLuminance: light,
    $failThreshold: 4.5,
    $preferredColor: grays
) {
    $textColorMap: map-get(_clr-lookupColorMap($preferredColor), text-colors);
    $myColor: #000;

    @if ($textTypeToReturn == darkest or $textTypeToReturn == lightest) {
        $myColor: map-get($textColorMap, $textTypeToReturn);
    } @else {
        // gets lightBg/text|hovered|hoverable|etc.
        $myColor: map-get(map-get($textColorMap, #{$expectedBgLuminance}Bg), $textTypeToReturn);
    }

    $testMyColor: _clr-testColorContrast($background, $myColor, $failThreshold);

    @if ($testMyColor == true) {
        @return $myColor;
    }

    // 80% of the time we don't even get here. usually $testMyColor will pass unless someone is
    // using this incorrectly or there is a bug.
    $bgLuminanceType: _clr-isLightOrDark($background, $failThreshold);
    $textWeight: normal;
    $textSize: normal;

    @if ($textTypeToReturn == darkest or $textTypeToReturn == lightest) {
        // same if dark or light
        @return clr-getTextContrast($background, $textTypeToReturn, $textWeight, $textSize, $preferredColor);
    } @else if ($textTypeToReturn == subtext) {
        // go darker/lighter with text
        @return _clr-testAndReturnTextColor($background, text, $expectedBgLuminance, $failThreshold, $preferredColor);
    } @else if ($textTypeToReturn == hoverable) {
        @return _clr-testAndReturnTextColor(
            $background,
            subtext,
            $expectedBgLuminance,
            $failThreshold,
            $preferredColor
        );
    } @else if ($bgLuminanceType == light) {
        @return clr-getTextContrast($background, darkest, $textWeight, $textSize, $preferredColor);
    }

    @return clr-getTextContrast($background, lightest, $textWeight, $textSize, $preferredColor);
}
