#!/bin/sh

# This script will take an HTML compatible hex colour string and turn it into
# a terminal sequence for true colour and other formats which best approximate
# its colour value.
#
# After that, we can map a few other special named symbols.  The idea is to
# permit shell variables such as `aaffcc bold italic` to indicate what you
# want a format to look like.
#
# The terminal colour and effect language herein implemented is named
# "SGR Name".

debug=0

debug_print()
{
	if [ ${debug} -ne 0 ]
	then
		echo ${*} >&2
	fi
}

# This is the named color map.  Just add new entries and they'll be supported...

COLOR_TABLE=""

map()
{
	if [ $# -eq 2 ]
	then
		COLOR_TABLE="${COLOR_TABLE}:${1}=${2}"
	else
		___right="${COLOR_TABLE##*:${1}=}"
		echo ${___right%%:*}
	fi
}

map white "FFFFFF"
map silver "C0C0C0"
map gray "808080"
map grey "808080"
map black "000000"
map red "FF0000"
map maroon "800000"
map yellow "FFFF00"
map olive "808000"
map lime "00FF00"
map green "008000"
map aqua "00FFFF"
map teal "008080"
map blue "0000FF"
map navy "000080"
map fuchsia "FF00FF"
map purple "800080"

# Some VGA color names can be promoted:
map brown "aa5500"
map cyan "00aaaa"


# The VGA names can be prefixed with "vga-" to explicitly avoid the collisions with HTML names, above...
# Based upon wikipedia's article on ANSI.  See the table on that page for details.
map vga-black "000000"
map vga-red "aa0000"
map vga-green "00aa00"
map vga-brown "aa5500"
map vga-yellow "aa5500"
map vga-blue "0000aa"
map vga-magenta "aa00aa"
map vga-cyan "00aaaa"
map vga-white "aaaaaa"

map vga-brightblack "555555"
map vga-grey "555555"
map vga-gray "555555"
map vga-brightred "ff5555"
map vga-brightgreen "55ff55"
map vga-brightyellow "ffff55"
map vga-brightblue "5555ff"
map vga-brightmagenta "ff55ff"
map vga-brightcyan "55ffff"
map vga-brightwhite "ffffff"

enable_greyscale_cheat=0

nocsi=0
if [ "$1" = "no-csi" ]
then
    nocsi=1
    shift 1
fi

dump_colors()
{
	all_maps=$(echo ${COLOR_TABLE} | sed -e 's/:/ /g' -e 's/=[0-9a-fA-F]\{6\}//g')
	debug_print "Going through all these colors ${all_maps} (${COLOR_TABLE})"
    for key in ${all_maps}
    do
		debug_print "Processing key ${key}"
        case $key in
            *bright*) text_color="000000" ;;
            *black*) text_color="ffffff" ;;
            *) text_color="000000" ;;
        esac
        printf "%-24s$(map ${key} )  |" ${key}
        printf "`$0 reset reverse bg:${text_color} fg:${key}`native-sample`$0 reset`|"
        if [ -z "${CSHENV_TERMINAL_COLORS+x}" ] || [ ${CSHENV_TERMINAL_COLORS} -ge 256 ]
        then
            printf "`env CSHENV_TERMINAL_COLORS=256 $0 reset reverse bg:${text_color} fg:${key}`8-bit sample`$0 reset`|"
        fi
        if [ -z "${CSHENV_TERMINAL_COLORS+x}" ] || [ ${CSHENV_TERMINAL_COLORS} -ge 16 ]
        then
            printf "`env CSHENV_TERMINAL_COLORS=16 $0 reset reverse bg:${text_color} fg:${key}`4-bit sample`$0 reset`|"
        fi
        if [ -z "${CSHENV_TERMINAL_COLORS+x}" ] || [ ${CSHENV_TERMINAL_COLORS} -ge 8 ]
        then
            printf "`env CSHENV_TERMINAL_COLORS=8 $0 reset reverse bg:${text_color} fg:${key}`3-bit sample`$0 reset`|"
        fi
        printf "\n"
    done

    exit 0
}

cube_row()
{
	r_base=${1}
	g=0
	while [ ${g} -lt 6 ]
	do
		u=0
		while [ ${u} -lt 3 ]
		do
			r=$(( r_base + u ))

			b=0
			while [ ${b} -lt 6 ]
			do
				i=$(( 16 + ( r * 36 ) + ( g * 6 ) + ( b ) ))

				txt="" && [ ${g} -lt 3 ] && txt=';37;1'

				printf "[30${txt}m[48;5;${i}m%3i[m " ${i}

				b=$(( b + 1 ))
			done

			printf '  '

			u=$(( u + 1 ))
		done

		printf '\n'

		g=$(( g + 1 ))
	done
	printf '\n'
}

greyscale_chart()
{
	[ ${i} -ge 244 ] && txt=";30"

	local k=$(( i + 12 ))
	while [ ${i} -lt ${k} ]
	do
		printf "[48;5;${i};37;1${txt}m%3i[m " ${i}
		i=$(( i + 1 ))
	done
	printf '\n'
}

color_chart()
{
	i=0

	# Base 16
	while [ ${i} -lt 16 ]
	do
		[ ${i} -gt 0 ] && txt=";30"

		printf "[37;1${txt};48;5;${i}m%3i[m " ${i}
		i=$(( i + 1 ))
	done
	printf '\n\n'

	r=0;g=0;b=0
	cube_row 0
	cube_row 3
	i=$(( 6 * 6 * 6 + 16 ))

	greyscale_chart
	greyscale_chart
}

color_block()
{
	printf "\e[37;1;48;5;${1}m%3i\e[m " ${1}
}

color_swatches()
{
	while [ "$1" != "" ]
	do
		color_block $1
		shift 1
	done
	printf "\n"
}

setup_color_depth()
{
    use_8_bit=0
    use_4_bit=0
    use_3_bit=0

    # Must only be set if terminal colors are reduced...
    if [ "${CSHENV_TERMINAL_COLORS+x}" ]
    then
        if [ ${CSHENV_TERMINAL_COLORS} -eq 256 ]
        then
            use_8_bit=1
        elif [ ${CSHENV_TERMINAL_COLORS} -eq 16 ]
        then
            use_4_bit=1
        elif [ ${CSHENV_TERMINAL_COLORS} -eq 8 ]
        then
            use_3_bit=1
        else
            echo "ERROR!" 1>&2 ; exit 255
        fi
    fi
}

render_color()
{
    command_color=30
    if [ ${use_3_bit} -ne 0 ]
    then
        # Underline colours need 8-bit or more
        if [ ${background} -gt 1 ] ; then return; fi
        basecolor=$(( ${command_color} + ${background}*10))
        next="$(( ${basecolor} + ${legacy_3_bit} ))"
    elif [ ${use_4_bit} -ne 0 ]
    then
        # Underline colours need 8-bit or more
        if [ ${background} -gt 1 ] ; then return; fi
        command_color=$(( ${command_color} + ${intensity_1_bit} * 60 ))
        basecolor=$(( ${command_color} + ${background}*10 ))
        next="$(( ${basecolor} + ${legacy_3_bit} ))"
    elif [ ${use_8_bit} -ne 0 ]
    then
        basecolor=$(( 8 + ${command_color} + ${background}*10 ))
        next="${basecolor};5;${ext_8_bit}"
    else
        basecolor=$(( 8 + ${command_color} + ${background}*10 ))
        next="${basecolor};2;${red_dec};${green_dec};${blue_dec}"
    fi
}



ansi_color()
{
    if [ ${1} -gt 15 ]
    then
        echo "ERROR!" 1>&2 ; exit 255
    elif [ ${1} -ge 8 ]
    then
        use_4_bit=1
        intensity_1_bit=1
        legacy_3_bit=$(( ${1} - 8 ))
    else
        use_3_bit=1
        intensity_1_bit=0
        legacy_3_bit=$(( ${1} ))
    fi
}

ext_grey()
{
    use_8_bit=1
    grey_val=${1}
    if [ ${grey_val} -le 0 - ${grey_val} -gt 23 ]
    then
        echo "ERROR!" 1>&2 ; exit 255
    fi
    ext_8_bit=$(( 232 + ${grey_val} ))
}

ext_rgb()
{
    use_8_bit=1
    red_ext_val="${1%??*}"
    tmp="${1#?}"
    green_ext_val=${tmp%?*}
    blue_ext_val=${tmp#?*}
    ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} ))
}

ext_color()
{
    use_8_bit=1
    if [ ${1} -gt 255 ]
    then
        echo "ERROR!" 1>&2 ; exit 255
    else
        ext_8_bit=${1}
    fi
}

rgb_color()
{
    color=$1
    case $1 in
        [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F])
			tmp="${color}"
			color="${tmp%??*}${tmp%??*}"
			tmp="${tmp#?}"
			color="${color}${tmp%?*}${tmp%?*}${tmp#?}${tmp#?}"
		;;
    esac
    # First split off the red, green, and blue components...
    red_hex="0x${color%????}"
    color="${color#??}"
    green_hex="0x${color%??}"
    blue_hex="0x${color#??}"

    # Convert to decimal...
    red_dec=$((${red_hex}))
    green_dec=$((${green_hex}))
    blue_dec=$((${blue_hex}))

	if [ ${debug} -ne 0 ]
	then
		debug_print "Red: $red_dec"
		debug_print "Green: $green_dec"
		debug_print "Blue: $blue_dec"
    fi

    # Now compute the rest of the stuff...

    # We probably don't have to compute all the unused color states.  We only have to compute
    # the color state we're signed up for by CSHENV_TERMINAL_COLORS... but whatever...

    # We round up by 128 points of colour, so that if we're above a certain intensity, we get the top
    # bit set.

    red_1_bit=$(( ( ( ${red_dec} + 128 )  >> 8 ) & 1 ))
    green_1_bit=$(( ( ( ${green_dec} + 128 )  >> 8 ) & 1 ))
    blue_1_bit=$(( ( ( ${blue_dec} + 128 )  >> 8 ) & 1 ))
    intensity_1_bit=0


    # If we can support an intensity bit, we'll turn that on too...
    # But we're going to stop using bold to set the colour "intense"
    # We'll use the 9x and 10x forms...
    if [ ${red_dec} -ge 192 -o ${green_dec} -ge 192 -o ${blue_dec} -ge 192 ]
    then
        intensity_1_bit=1
    fi

    if [ ${debug} -ne 0 ]
    then
		debug_print "Red bit: " $red_1_bit
		debug_print "Green bit: " $green_1_bit
		debug_print "Blue bit: " $blue_1_bit
		debug_print "Intensity bit: " $intensity_1_bit
    fi


    # Because grey is tricky, I'm going to special case it:
    if [ ${red_dec} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} -a ${red_dec} -lt 128 -a ${red_dec} -ge 80 ]
    then
        red_1_bit=0
        green_1_bit=0
        blue_1_bit=0
        intensity_1_bit=1
    fi

    # Because brown is tricky, I'm going to special case it:
    if [ ${red_dec} -gt 128 -a ${green_dec} -gt 80 -a ${blue_dec} -lt 50 ]
    then
        red_1_bit=1
        green_1_bit=1
        blue_1_bit=0
    fi

    if [ ${debug} -ne 0 ]
    then
		debug_print "Red bit: " $red_1_bit
		debug_print "Green bit: " $green_1_bit
		debug_print "Blue bit: " $blue_1_bit
		debug_print "Intensity bit: " $intensity_1_bit
	fi

    # This lets us combine them for a legacy colour value in the legacy colour space...

    legacy_3_bit=$(( ( ${blue_1_bit} << 2 ) + ( ${green_1_bit} << 1 ) + ( ${red_1_bit} ) ))

    if [ ${debug} -ne 0 ]
    then
        debug_print "Legacy colour: " ${legacy_3_bit} " Intensity: " ${intensity_1_bit}
    fi

    # Now compute an extended colour cube placement (216 colours):

    red_ext_val=$(( ${red_dec} * 6 / 256 ))
    green_ext_val=$(( ${green_dec} * 6 / 256 ))
    blue_ext_val=$(( ${blue_dec} * 6 / 256 ))

    if [ ${debug} -ne 0 ]
    then
        debug_print "Red ext: " ${red_ext_val}
        debug_print "Green ext: " ${green_ext_val}
        debug_print "Blue ext: " ${blue_ext_val}
    fi

    ext_8_bit=$(( 16 + 36 * ${red_ext_val} + 6 * ${green_ext_val} + ${blue_ext_val} ))

    if [ ${debug} -ne 0 ]
    then
        debug_print "Computed 216 color cube: " ${ext_8_bit}
    fi

    # Check for precise greyscale, which would replace the above:
    # (Note that precise greyscale is implied by a user explicitly making ALL of RGB the
    # same value, thus demanding greyscale, rather than a subtle tinting...)

    if [ ${red_dec} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} ]
    then
        ext_8_bit=$(( 232 + ${red_dec} * 24 / 256 ))
    fi

    if [ ${debug} -ne 0 ]
    then
        debug_print "Computed 256 color choice, after applying greyscale scan: " ${ext_8_bit}
    fi

    if [ ${enable_greyscale_cheat} -ne 0 ]
    then
        # Check for SPECIFIC white and black and grey:
        # The half-saturation, no saturation, and full saturation values are mapped
        # to specific points in the legacy set.  (This may be ill-advised as a manually
        # built reverse video terminal setting through colour mapping may make it so that
        # we wind up in la-la land.  Perhaps the better way is to find the head and tail
        # points in the 6x6x6 colour cube.)
        #
        # TODO: Only map the head and tail of the colour cube.
        if [ ${red_dec} -eq ${green_dec} -a ${green_dec} -eq ${blue_dec} ]
        then
            if [ ${red_dec} -eq 0 ]
            then
                ext_8_bit=0
            elif [ ${red_dec} -eq 255 ]
            then
                ext_8_bit=15
            elif [ ${red_dec} -eq 128 ]
            then
                ext_8_bit=8
            elif [ ${red_dec} -eq 192 ]
            then
                ext_8_bit=7
            fi
        fi
    fi

    if [ ${debug} -ne 0 ]
    then
        debug_print "Computed 256 color choice, after applying white and black scan: " ${ext_8_bit}
    fi

    # We do NOT check for the base colours precisely.  Since those 16 can be custom mapped by your terminal emulator, you just pass `ansi:0` thru
    # `ansi:15` to select them by ID.  Or use `ext:0` thru `ext:255` to select a specific extended colour.
}

make_color()
{
    setup_color_depth
    background=$1 # $1 is the background bit
    $2 $3 # $2 is the color function
    render_color
}

named_color()
{
    name=$( echo ${1} | awk '{print tolower($1)}' )
	mapped=$(map ${name})
	debug_print "${name} -> ${mapped}"
    if [ -z "${mapped}" ]
    then
        echo "ERROR!" 1>&2 ; exit 255
    fi

    rgb_color ${mapped}
}

build_sgr_code()
{
    case $1 in
        dump-colors) dump_colors ;;
        color-chart) color_chart ;;

        reset) next=0 ;;
        bold) next=1 ;;
        dim) next=2 ;;
        italic) next=3 ;;
        underline|under) next=4 ;;
        blink) next=5 ;;
        fastblink) next=6 ;;
        reverse) next=7 ;;
        hide|conceal) next=8 ;;
        strike|strikethrough|strikethru) next=9 ;;
        doubleunder) next=21 ;;
        reveal) next=28 ;;

        ansi:*) make_color 0 ansi_color ${1#?????} ;;
        ext:grey*) make_color 0 ext_grey ${1#????????} ;;
        ext:rgb*) make_color 0 ext_rgb ${1#???????} ;;
        ext:*) make_color 0 ext_color ${1#????} ;;
        # 12-bit color also supported
        [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color $1 ;;
        [0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color $1 ;;

        fg:ansi:*) make_color 0 ansi_color ${1#????????} ;;
        fg:ext:grey*) make_color 0 ext_grey ${1#???????????} ;;
        fg:ext:rgb*) make_color 0 ext_rgb ${1#??????????} ;;
        fg:ext:*) make_color 0 ext_color ${1#???????} ;;
        # 12-bit color also supported
        fg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color ${1#???} ;;
        fg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 0 rgb_color ${1#???} ;;

        bg:ansi:*) make_color 1 ansi_color ${1#????????} ;;
        bg:ext:grey*) make_color 1 ext_grey ${1#???????????} ;;
        bg:ext:rgb*) make_color 1 ext_rgb ${1#??????????} ;;
        bg:ext:*) make_color 1 ext_color ${1#???????} ;;
        # 12-bit color also supported
        bg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 1 rgb_color ${1#???} ;;
        bg:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 1 rgb_color ${1#???} ;;

        ul:ext:grey*) make_color 2 ext_grey ${1#???????????} ;;
        ul:ext:rgb*) make_color 2 ext_rgb ${1#??????????} ;;
        ul:ext:*) make_color 2 ext_color ${1#???????} ;;
        # 12-bit color also supported
        ul:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 2 rgb_color ${1#???} ;;
        ul:[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) make_color 2 rgb_color ${1#???} ;;

        fg:*) make_color 0 named_color ${1#???} ;;
        bg:*) make_color 1 named_color ${1#???} ;;
        ul:*) make_color 2 named_color ${1#???} ;;
        *) make_color 0 named_color $1 ;;
    esac
    if [ ! -z ${output} ]
    then
        output="${output};"
    fi
    output="${output}${next}"
}

output=""

if [ "$1" = "color-swatches" ]
then
	shift 1
	color_swatches $*
else
	while [ ! -z "$1" ]
	do
		build_sgr_code $1
		shift 1
	done
fi

if [ ! -z "${output}" ] && [ ${nocsi} -ne 1 ]
then
	#echo "I AM PRINTING WITH CSI! (${nocsi} is nocsi, \`${output}\` is output)">&2
    output="[${output}m"
fi
if [ ! -z "${output}" ]
then
    printf ${output}
fi


exit 0
