#!/usr/bin/env python
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

from gimpfu import *
from colorsys import rgb_to_yiq

gettext.install("gimp20-python", gimp.locale_directory, unicode=True)

AVAILABLE_CHANNELS = (_("Red"), _("Green"), _("Blue"),
                      _("Luma (Y)"),
                      _("Hue"), _("Saturation"), _("Value"),
                      _("Saturation (HSL)"), _("Lightness (HSL)"))
GRAIN_UNIT = 256
GRAIN_SIZE = (1.0, 1.0 , 1.0,
              1.0,
              360., 100., 100.,
              100., 100.,
              100., 256., 256.,
              256., 360.)
GRAIN_SIZE = tuple([v / GRAIN_UNIT for v in GRAIN_SIZE])
SELECT_ALL = 0
SELECT_ARRAY = 1
SELECT_SLICE = 2
SELECT_PARTITIONED = 3
SELECTIONS = (SELECT_ALL, SELECT_ARRAY, SELECT_SLICE, SELECT_PARTITIONED)

def noop(v):
    return v
def to_hsv(v):
    return v.to_hsv()
def to_hsl(v):
    return v.to_hsl()
def to_yiq(v):
    return rgb_to_yiq(*v[:-1])
channel_getters = [ (noop, 0), (noop, 1), (noop, 2),
                    (to_yiq, 0),
                    (to_hsv, 0), (to_hsv, 1), (to_hsv, 2),
                    (to_hsl, 1), (to_hsl, 2)]


try:
    from colormath.color_objects import RGBColor, LabColor, LCHabColor
    AVAILABLE_CHANNELS = AVAILABLE_CHANNELS + (_("Lightness (LAB)"), _("A-color"), _("B-color"),
                                               _("Chroma (LCHab)"), _("Hue (LCHab)"))
    to_lab = lambda v: RGBColor(*v[:-1]).convert_to('LAB').get_value_tuple()
    to_lchab = lambda v: RGBColor(*v[:-1]).convert_to('LCHab').get_value_tuple()
    channel_getters.extend([(to_lab, 0), (to_lab, 1), (to_lab, 2),
                            (to_lchab, 1), (to_lchab, 2)])
except ImportError:
    pass

def palette_sort (palette, channel, ascending, granularity, selection,
                  start, length, nrows, partitionchannel, pgranularity):

    granularity = max(1.0, granularity)
    pgranularity = max(1.0, pgranularity)
    if granularity > 1.0:
        grain = max(0.00001, GRAIN_SIZE[channel] * granularity)
    else:
        grain = 0.00001
    if pgranularity > 1.0:
        pgrain = max(0.00001, GRAIN_SIZE[partitionchannel] * pgranularity)
    else:
        pgrain = 0.00001
    #If palette is read only, work on a copy:
    editable = pdb.gimp_palette_is_editable(palette)
    if not editable:
        palette = pdb.gimp_palette_duplicate (palette)

    num_colors = pdb.gimp_palette_get_info (palette)
    if length == -1:
        length = num_colors - start
    length = max(1, length)
    channels_getter, channel_index = channel_getters[channel]

    def get_colors (start, end):
        result = []
        for i in range (start, end):
            entry =  (pdb.gimp_palette_entry_get_name (palette, i),
                      pdb.gimp_palette_entry_get_color (palette, i))
            index = channels_getter(entry[1])[channel_index]
            index = (index - (index % grain))
            result.append((index, entry))
        return result

    if selection == SELECT_ALL:
        entry_list = get_colors(0, num_colors)
        entry_list.sort(key=lambda v:v[0], reverse=not ascending)
        for i in range(num_colors):
            pdb.gimp_palette_entry_set_name (palette, i, entry_list[i][1][0])
            pdb.gimp_palette_entry_set_color (palette, i, entry_list[i][1][1])

    elif selection == SELECT_PARTITIONED:
        partition_spans = [1]
        pchannels_getter, pchannel_index = channel_getters[partitionchannel]
        old_color = pdb.gimp_palette_entry_get_color (palette, 0)
        old_partition = pchannels_getter(old_color)[pchannel_index]
        old_partition = old_partition - (old_partition % pgrain)
        for i in range(start + 1, start + length):
            this_color = pdb.gimp_palette_entry_get_color (palette, i)
            this_partition = pchannels_getter(this_color)[pchannel_index]
            this_partition = this_partition - (this_partition % pgrain)
            if this_partition == old_partition:
                partition_spans[-1] += 1
            else:
                partition_spans.append(1)
                old_partition = this_partition
        base = start
        for size in partition_spans:
            palette_sort(palette, channel, ascending, granularity, SELECT_SLICE, base, size, 0, 0, 1.0)
            base += size
    else:
        if selection != SELECT_ARRAY:
            nrows = 1
        stride = length
        if num_colors < (start + stride * nrows) - 1:
            raise ValueError('Not enough entries in palette to sort complete rows!'
                             'got %d, expected %d' % (num_colors, start + stride * nrows))

        for row_start in range(start, start + stride * nrows, stride):
            sublist = get_colors(row_start, row_start + stride)
            sublist.sort(key=lambda v:v[0], reverse=not ascending)
            for i, entry in zip(range(row_start, row_start + stride), sublist):
                pdb.gimp_palette_entry_set_name (palette, i, entry[1][0])
                pdb.gimp_palette_entry_set_color (palette, i, entry[1][1])

    return palette


register(
    "python-fu-palette-sort",
    N_("Sort the colors in a palette"),
    "palette_merge (palette, model, channel, ascending) -> new_palette",
    "Joao S. O. Bueno Calligaris, Carol Spears, David Gowers",
    "Joao S. O. Bueno Calligaris",
    "2006",
    N_("_Sort Palette..."),
    "",
    [
        (PF_PALETTE, "palette",  _("Palette"), ""),
        (PF_OPTION,   "channel",    _("Channel to _sort"), 3,
                                    AVAILABLE_CHANNELS),
        (PF_BOOL,   "ascending", _("_Ascending"), True),
        (PF_FLOAT,  "granularity", _("_Granularity"), 1.0),
        (PF_OPTION, "selections", _("Se_lections"), SELECT_ALL,
                    (_("All"), _("Array"), _("Slice"), _("Partitioned"))),
        (PF_INT,    "start", _("Selection S_tart"), 0),
        (PF_INT,    "width", _("Selection _Width"), -1),
        (PF_INT,    "nrows", _("Selection _Rows"), 1),
        (PF_OPTION,   "partitionchannel",    _("Channel to _partition by"), 3,
                                            AVAILABLE_CHANNELS),
        (PF_FLOAT,  "pgranularity", _("Partition Gra_nularity"), 1.0),
    ],
    [],
    palette_sort,
    menu="<Palettes>",
    domain=("gimp20-python", gimp.locale_directory)
    )


main ()
