# -*- coding: utf-8 -*-
"""
$Id$

Copyright 2010-2011 Lars Kruse <devel@sumpfralle.de>
Copyright 2008-2009 Lode Leroy

This file is part of PyCAM.

PyCAM 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.

PyCAM 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 PyCAM.  If not, see <http://www.gnu.org/licenses/>.
"""

from pycam.PathGenerators import get_max_height_dynamic
from pycam.Utils import ProgressCounter
from pycam.Utils.threading import run_in_parallel
import pycam.Geometry.Model
import pycam.Utils.log

log = pycam.Utils.log.get_logger()


# We need to use a global function here - otherwise it does not work with
# the multiprocessing Pool.
def _process_one_grid_line((positions, minz, maxz, model, cutter, physics)):
    """ This function assumes, that the positions are next to each other.
    Otherwise the dynamic over-sampling (in get_max_height_dynamic) is
    pointless.
    """
    return get_max_height_dynamic(model, cutter, positions, minz, maxz, physics)


class DropCutter(object):

    def __init__(self, path_processor, physics=None):
        self.pa = path_processor
        self.physics = physics

    def GenerateToolPath(self, cutter, models, motion_grid, minz=None, maxz=None, draw_callback=None):
        quit_requested = False
        model = pycam.Geometry.Model.get_combined_model(models)

        # Transfer the grid (a generator) into a list of lists and count the
        # items.
        lines = []
        # usually there is only one layer - but an xy-grid consists of two
        for layer in motion_grid:
            for line in layer:
                lines.append(line)

        num_of_lines = len(lines)
        progress_counter = ProgressCounter(len(lines), draw_callback)
        current_line = 0

        self.pa.new_direction(0)

        args = []
        for one_grid_line in lines:
            # simplify the data (useful for remote processing)
            xy_coords = [(pos.x, pos.y) for pos in one_grid_line]
            args.append((xy_coords, minz, maxz, model, cutter,
                    self.physics))
        for points in run_in_parallel(_process_one_grid_line, args,
                callback=progress_counter.update):
            self.pa.new_scanline()
            if draw_callback and draw_callback(text="DropCutter: processing " \
                        + "line %d/%d" % (current_line + 1, num_of_lines)):
                # cancel requested
                quit_requested = True
                break
            for point in points:
                if point is None:
                    # exceeded maxz - the cutter has to skip this point
                    self.pa.end_scanline()
                    self.pa.new_scanline()
                    continue
                self.pa.append(point)
                # "draw_callback" returns true, if the user requested to quit
                # via the GUI.
                # The progress counter may return True, if cancel was requested.
                if draw_callback and draw_callback(tool_position=point,
                        toolpath=self.pa.paths):
                    quit_requested = True
                    break
            progress_counter.increment()
            self.pa.end_scanline()
            # update progress
            current_line += 1
            if quit_requested:
                break
        self.pa.end_direction()
        self.pa.finish()
        return self.pa.paths

