# -*- coding: utf-8 -*-
#
#  Copyright (C) 2016 by Igor E. Novikov
#
#  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/>.


import os

import wal
from sk1 import _, events
from sk1.app_plugins import RsPlugin
from sk1.resources import get_icon
from uc2 import sk2const

PLG_DIR = __path__[0]
IMG_DIR = os.path.join(PLG_DIR, 'images')


def make_artid(name):
    return os.path.join(IMG_DIR, name + '.png')


def get_plugin(app):
    return AlignPlugin(app)


PLUGIN_ICON = make_artid('icon')

# --- Source object constants

SOURCE_PAGE = 0
SOURCE_SEL = 1
SOURCE_FIRST = 2
SOURCE_LAST = 3
SOURCE_BIGGEST = 4
SOURCE_SMALLEST = 5

SOURCE_NAMES = [
    _('Page'),
    _('Selection'),
    _('First selected'),
    _('Last selected'),
    _('Biggest object'),
    _('Smallest object'),
]

# --- Align constants

ALIGN_BOTTOM = -1.0
ALIGN_LEFT = -1.0
ALIGN_CENTER = 0.0
ALIGN_RIGHT = 1.0
ALIGN_TOP = 1.0

H_ALIGN_MODES = [ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT]

H_ALIGN_MODE_ICONS = {
    ALIGN_LEFT: make_artid('align-left'),
    ALIGN_CENTER: make_artid('align-center-h'),
    ALIGN_RIGHT: make_artid('align-right')
}

H_ALIGN_MODE_NAMES = {
    ALIGN_LEFT: _('Align to left side'),
    ALIGN_CENTER: _('Align to center horizontally'),
    ALIGN_RIGHT: _('Align to right side')
}

V_ALIGN_MODES = [ALIGN_BOTTOM, ALIGN_CENTER, ALIGN_TOP]

V_ALIGN_MODE_ICONS = {
    ALIGN_BOTTOM: make_artid('align-bottom'),
    ALIGN_CENTER: make_artid('align-center-v'),
    ALIGN_TOP: make_artid('align-top')
}

V_ALIGN_MODE_NAMES = {
    ALIGN_BOTTOM: _('Align to bottom'),
    ALIGN_CENTER: _('Align to center vertically'),
    ALIGN_TOP: _('Align to top')
}


class AlignPlugin(RsPlugin):
    pid = 'AlignPlugin'
    name = _('Align and Distribute')
    active_transform = None
    transforms = {}
    apanel = None
    dpanel = None

    def build_ui(self):
        self.icon = get_icon(PLUGIN_ICON)

        self.apanel = AlignPanel(self.panel, self.app)
        self.panel.pack(self.apanel, fill=True, padding_all=5)

        self.dpanel = DistributePanel(self.panel, self.app)
        self.panel.pack(self.dpanel, fill=True, padding_all=5)

        # self.fpanel = FlatteringPanel(self.panel, self.app)
        # self.panel.pack(self.fpanel, fill=True, padding_all=5)

        events.connect(events.DOC_CHANGED, self.update)
        events.connect(events.SELECTION_CHANGED, self.update)
        events.connect(events.DOC_MODIFIED, self.update)
        self.update()

    def update(self, *args):
        self.apanel.update()
        self.dpanel.update()
        # self.fpanel.update()


class AlignPanel(wal.LabeledPanel):
    app = None

    def __init__(self, parent, app):
        self.app = app
        wal.LabeledPanel.__init__(self, parent, _('Align'))

        self.pack((5, 5))
        self.pack(wal.Label(self, _('Relative to:')))
        self.source = wal.Combolist(self, items=SOURCE_NAMES,
                                    onchange=self.update)
        self.source.set_active(0)
        self.pack(self.source, padding_all=5, fill=True)
        self.pack((5, 5))

        self.halign = wal.HToggleKeeper(self, H_ALIGN_MODES,
                                        H_ALIGN_MODE_ICONS, H_ALIGN_MODE_NAMES,
                                        on_change=self.update, allow_none=True)
        self.pack(self.halign)
        self.halign.set_mode(ALIGN_CENTER)

        self.valign = wal.HToggleKeeper(self, V_ALIGN_MODES,
                                        V_ALIGN_MODE_ICONS, V_ALIGN_MODE_NAMES,
                                        on_change=self.update, allow_none=True)
        self.pack(self.valign, padding_all=5)
        self.valign.set_mode(ALIGN_CENTER)

        self.group = wal.Checkbox(self, _('Selection as group'), True,
                                  onclick=self.update)
        self.pack(self.group, padding_all=5)

        self.apply_btn = wal.Button(self, _('Apply'), onclick=self.action)
        self.pack(self.apply_btn, padding_all=5, fill=True)

    def get_sel_count(self):
        doc = self.app.current_doc
        return len(doc.selection.objs)

    def get_selection_bbox(self):
        doc = self.app.current_doc
        return [] + doc.selection.bbox

    def get_selection_size(self):
        bbox = self.get_selection_bbox()
        return bbox[2] - bbox[0], bbox[3] - bbox[1]

    def update(self, *args):
        self.source.set_enable(False)
        self.halign.set_enable(False)
        self.valign.set_enable(False)
        self.group.set_enable(False)
        self.apply_btn.set_enable(False)
        if not self.app.insp.is_selection():
            return
        self.source.set_enable(True)
        if self.source.get_active():
            self.group.set_value(False, False)
            if self.get_sel_count() < 2:
                return
            if self.get_sel_count() == 2 and self.group.get_value():
                return
        self.halign.set_enable(True)
        self.valign.set_enable(True)
        if self.get_sel_count() > 1 and not self.source.get_active():
            self.group.set_enable(True)
        self.apply_btn.set_enable(True)
        if self.valign.get_mode() is None and self.halign.get_mode() is None:
            self.apply_btn.set_enable(False)

    def get_obj_areas(self, objs):
        areas = []
        for obj in objs:
            bbox = obj.cache_bbox
            areas.append((bbox[2] - bbox[0]) * (bbox[3] - bbox[1]))
        return areas

    def get_smallest_obj(self, objs):
        areas = self.get_obj_areas(objs)
        return objs[areas.index(min(areas))]

    def get_biggest_obj(self, objs):
        areas = self.get_obj_areas(objs)
        return objs[areas.index(max(areas))]

    def get_trafo(self, source_bbox, target_bbox):
        sw = source_bbox[2] - source_bbox[0]
        sh = source_bbox[3] - source_bbox[1]
        tw = target_bbox[2] - target_bbox[0]
        th = target_bbox[3] - target_bbox[1]
        trafo = [] + sk2const.NORMAL_TRAFO
        cs = source_bbox[:2]
        cs[0] += sw / 2.0
        cs[1] += sh / 2.0
        ct = target_bbox[:2]
        ct[0] += tw / 2.0
        ct[1] += th / 2.0
        if not self.halign.get_mode() is None:
            trafo[4] += cs[0] + self.halign.get_mode() * sw / 2.0
            trafo[4] -= ct[0] + self.halign.get_mode() * tw / 2.0
        if not self.valign.get_mode() is None:
            trafo[5] += cs[1] + self.valign.get_mode() * sh / 2.0
            trafo[5] -= ct[1] + self.valign.get_mode() * th / 2.0
        return trafo

    def action(self):
        doc = self.app.current_doc
        sel_objs = []
        source_bbox = []
        if self.source.get_active() == SOURCE_PAGE:
            pw, ph = doc.get_page_size()
            source_bbox = [-pw / 2.0, -ph / 2.0, pw / 2.0, ph / 2.0]
            if self.group.get_value():
                trafo = self.get_trafo(source_bbox, self.get_selection_bbox())
                doc.api.transform_selected(trafo)
                return
            else:
                sel_objs += doc.selection.objs
        elif self.source.get_active() == SOURCE_SEL:
            source_bbox = self.get_selection_bbox()
            sel_objs += doc.selection.objs
        elif self.source.get_active() == SOURCE_FIRST:
            source_bbox += doc.selection.objs[0].cache_bbox
            sel_objs = self.app.current_doc.selection.objs[1:]
        elif self.source.get_active() == SOURCE_LAST:
            source_bbox += doc.selection.objs[-1].cache_bbox
            sel_objs = self.app.current_doc.selection.objs[:-1]
        elif self.source.get_active() == SOURCE_SMALLEST:
            sel_objs += doc.selection.objs
            smallest = self.get_smallest_obj(sel_objs)
            source_bbox += smallest.cache_bbox
            sel_objs.remove(smallest)
        elif self.source.get_active() == SOURCE_BIGGEST:
            sel_objs += doc.selection.objs
            biggest = self.get_biggest_obj(sel_objs)
            source_bbox += biggest.cache_bbox
            sel_objs.remove(biggest)

        obj_trafo_list = []
        for item in sel_objs:
            trafo = self.get_trafo(source_bbox, item.cache_bbox)
            obj_trafo_list.append((item, trafo))
        doc.api.trasform_objs(obj_trafo_list)


# --- Distribute constants

DISTRIBUTE_LEFT = 0
DISTRIBUTE_BOTTOM = 1
DISTRIBUTE_HCENTER = 4
DISTRIBUTE_VCENTER = 5
DISTRIBUTE_RIGHT = 2
DISTRIBUTE_TOP = 3
DISTRIBUTE_HGAP = 6
DISTRIBUTE_VGAP = 7

H_DISTRIBUTE_MODES = [DISTRIBUTE_LEFT, DISTRIBUTE_HCENTER,
                      DISTRIBUTE_RIGHT, DISTRIBUTE_HGAP]

H_DISTRIBUTE_MODE_ICONS = {
    DISTRIBUTE_LEFT: make_artid('distribute-h-le'),
    DISTRIBUTE_HCENTER: make_artid('distribute-h-c'),
    DISTRIBUTE_RIGHT: make_artid('distribute-h-re'),
    DISTRIBUTE_HGAP: make_artid('distribute-h-gap')
}

H_DISTRIBUTE_MODE_NAMES = {
    DISTRIBUTE_LEFT: _('Distribute by left side horizontally'),
    DISTRIBUTE_HCENTER: _('Distribute by center horizontally'),
    DISTRIBUTE_RIGHT: _('Distribute by right side horizontally'),
    DISTRIBUTE_HGAP: _('Equal gap horizontally')
}

V_DISTRIBUTE_MODES = [DISTRIBUTE_BOTTOM, DISTRIBUTE_VCENTER,
                      DISTRIBUTE_TOP, DISTRIBUTE_VGAP]

V_DISTRIBUTE_MODE_ICONS = {
    DISTRIBUTE_BOTTOM: make_artid('distribute-v-be'),
    DISTRIBUTE_VCENTER: make_artid('distribute-v-c'),
    DISTRIBUTE_TOP: make_artid('distribute-v-te'),
    DISTRIBUTE_VGAP: make_artid('distribute-v-gap')
}

V_DISTRIBUTE_MODE_NAMES = {
    DISTRIBUTE_BOTTOM: _('Distribute by bottom side vertically'),
    DISTRIBUTE_VCENTER: _('Distribute by center vertically'),
    DISTRIBUTE_TOP: _('Distribute by top side vertically'),
    DISTRIBUTE_VGAP: _('Equal gap vertically')
}


class DistributePanel(wal.LabeledPanel):
    app = None

    def __init__(self, parent, app):
        self.app = app
        wal.LabeledPanel.__init__(self, parent, _('Distribute'))

        self.pack((5, 5))

        self.hdistrib = wal.HToggleKeeper(self, H_DISTRIBUTE_MODES,
                                          H_DISTRIBUTE_MODE_ICONS,
                                          H_DISTRIBUTE_MODE_NAMES,
                                          on_change=self.update,
                                          allow_none=True)
        self.pack(self.hdistrib)
        self.hdistrib.set_mode(DISTRIBUTE_HCENTER)

        self.vdistrib = wal.HToggleKeeper(self, V_DISTRIBUTE_MODES,
                                          V_DISTRIBUTE_MODE_ICONS,
                                          V_DISTRIBUTE_MODE_NAMES,
                                          on_change=self.update,
                                          allow_none=True)
        self.pack(self.vdistrib, padding_all=5)
        self.vdistrib.set_mode(DISTRIBUTE_VCENTER)

        self.apply_btn = wal.Button(self, _('Apply'), onclick=self.action)
        self.pack(self.apply_btn, padding_all=5, fill=True)

    def update(self, *args):
        self.hdistrib.set_enable(False)
        self.vdistrib.set_enable(False)
        self.apply_btn.set_enable(False)
        if not self.app.insp.is_selection():
            return
        if len(self.app.current_doc.selection.objs) < 3:
            return
        self.hdistrib.set_enable(True)
        self.vdistrib.set_enable(True)
        if not self.hdistrib.get_mode() is None or \
                not self.vdistrib.get_mode() is None:
            self.apply_btn.set_enable(True)

    def get_coord(self, obj, index):
        bbox = obj.cache_bbox
        if index < 4:
            return bbox[index]
        elif index == 4:
            return bbox[0] + (bbox[2] - bbox[0]) / 2.0
        else:
            return bbox[1] + (bbox[3] - bbox[1]) / 2.0

    def get_obj_width(self, obj):
        bbox = obj.cache_bbox
        return bbox[2] - bbox[0]

    def get_obj_height(self, obj):
        bbox = obj.cache_bbox
        return bbox[3] - bbox[1]

    def get_coord_list(self, objs, index=0):
        ret = []
        for obj in objs:
            ret.append(self.get_coord(obj, index))
        return ret

    def sort_objs(self, objs, index=0):
        objs = [] + objs
        coords = self.get_coord_list(objs, index)
        new_coords = [] + coords
        new_coords.sort()
        new_objs = []
        for item in new_coords:
            index = coords.index(item)
            obj = objs[index]
            coords.remove(item)
            objs.remove(obj)
            new_objs.append(obj)
        return new_objs, new_coords

    def action(self):
        doc = self.app.current_doc
        objs = [] + doc.selection.objs
        trafo_dict = {}
        for obj in objs:
            trafo_dict[obj] = [] + sk2const.NORMAL_TRAFO

        if not self.hdistrib.get_mode() is None:
            mode = self.hdistrib.get_mode()
            if mode < DISTRIBUTE_HGAP:
                new_objs, coords = self.sort_objs(objs, mode)
                shift = (coords[-1] - coords[0]) / float(len(objs) - 1)
                i = 0.0
                start = self.get_coord(new_objs[0], mode)
                for obj in new_objs:
                    coord = self.get_coord(obj, mode)
                    trafo_dict[obj][4] += start + i * shift - coord
                    i += 1.0
            else:
                new_objs, coords = self.sort_objs(objs, DISTRIBUTE_LEFT)
                total_w = 0
                for obj in new_objs:
                    total_w += self.get_obj_width(obj)
                available_w = self.get_coord(new_objs[-1], DISTRIBUTE_RIGHT)
                available_w -= self.get_coord(new_objs[0], DISTRIBUTE_LEFT)
                shift = (available_w - total_w) / float(len(objs) - 1)
                pos = self.get_coord(new_objs[0], DISTRIBUTE_RIGHT)
                for obj in new_objs[1:-1]:
                    pos += shift
                    coord = self.get_coord(obj, DISTRIBUTE_LEFT)
                    trafo_dict[obj][4] += pos - coord
                    pos += self.get_obj_width(obj)

        if not self.vdistrib.get_mode() is None:
            mode = self.vdistrib.get_mode()
            if mode < DISTRIBUTE_VGAP:
                new_objs, coords = self.sort_objs(objs, mode)
                shift = (coords[-1] - coords[0]) / float(len(objs) - 1)
                i = 0.0
                start = self.get_coord(new_objs[0], mode)
                for obj in new_objs:
                    coord = self.get_coord(obj, mode)
                    trafo_dict[obj][5] += start + i * shift - coord
                    i += 1.0
            else:
                new_objs, coords = self.sort_objs(objs, DISTRIBUTE_BOTTOM)
                total_h = 0
                for obj in new_objs:
                    total_h += self.get_obj_height(obj)
                available_h = self.get_coord(new_objs[-1], DISTRIBUTE_TOP)
                available_h -= self.get_coord(new_objs[0], DISTRIBUTE_BOTTOM)
                shift = (available_h - total_h) / float(len(objs) - 1)
                pos = self.get_coord(new_objs[0], DISTRIBUTE_TOP)
                for obj in new_objs[1:-1]:
                    pos += shift
                    coord = self.get_coord(obj, DISTRIBUTE_BOTTOM)
                    trafo_dict[obj][5] += pos - coord
                    pos += self.get_obj_height(obj)

        obj_trafo_list = []
        for item in objs:
            obj_trafo_list.append((item, trafo_dict[item]))
        doc.api.trasform_objs(obj_trafo_list)


class FlatteringPanel(wal.LabeledPanel):
    app = None

    def __init__(self, parent, app):
        self.app = app
        wal.LabeledPanel.__init__(self, parent, 'Flattering')
        self.apply_btn = wal.Button(self, _('Apply'), onclick=self.action)
        self.pack(self.apply_btn, padding_all=5, fill=True)

    def update(self):
        self.apply_btn.set_enable(False)
        if not self.app.insp.is_selection():
            return
        for obj in self.app.current_doc.selection.objs:
            if not obj.is_curve():
                return
        self.apply_btn.set_enable(True)

    def action(self):
        self.app.current_doc.api.flat_curve_selected()
