<gh_stars>0
# -*- coding: utf-8 -*-
"""
Simple algorithm to solve nonograms using left and right overlaps

See details:
http://www.lancaster.ac.uk/~simpsons/nonogram/ls-fast
"""

from notetool.tool.log import logger
from six.moves import range

from ..core.common import BOX, SPACE, UNKNOWN, NonogramError
from .base import BaseLineSolver

_SYMBOL_MAP = {
    UNKNOWN: ' ',
    SPACE: '-',
    BOX: '#',
}


class FastSolver(BaseLineSolver):
    """
    Nonogram line solver that uses left and right overlap algorithm.
    The algorithm gets most of the solution, but sometimes not complete one.
    """

    @classmethod
    def push_left(cls, line, clue):
        """
        Move all the blocks to the left.
        Raise NonogramError if inconsistency detected.
        """

        line_size = len(line)
        clue_size = len(clue)
        res = [0] * clue_size
        solid = [-1] * clue_size

        # initial state
        current_block = 0

        if clue_size > 0:
            res[current_block] = 0

        logger.info('Pushing clue: %s', ', '.join(map(str, clue)))
        logger.info('Pushing line: >%s<', ''.join(
            _SYMBOL_MAP.get(cell, '?') for cell in line))

        while current_block < clue_size:
            # find first/next non-dot:
            # stop if current_block won't fit into remainder of line

            pos = res[current_block]
            block_size = clue[current_block]

            logger.debug('     start %d >%s %d',
                         current_block, pos, block_size)

            while pos < line_size - block_size:
                cell = line[pos]
                if cell != SPACE:
                    break

                pos += 1

            res[current_block] = pos
            logger.debug('     end %d >%s %d', current_block, pos, block_size)

            # no room left
            if (pos + block_size > line_size) or (line[pos] == SPACE):
                raise NonogramError(
                    'No room left: cannot fit %d-th block' % current_block)

            # assume current position doesn't cover a solid
            solid[current_block] = -1

            # check if the current_block fits in before the next dot;
            # monitor for passing over a solid
            i = 0
            while i < block_size:
                cell = line[pos + i]
                if cell == SPACE:
                    break

                if (solid[current_block] < 0) and (cell == BOX):
                    logger.debug('     solid %d >%s#',
                                 current_block, (pos + i))
                    solid[current_block] = i
                i += 1

            # if a dot was encountered...
            if i < block_size:
                logger.debug('     dot %d >%s-', current_block, (pos + i))

                # if a solid is covered, get an earlier current_block to fit
                if solid[current_block] >= 0:
                    # find an earlier current_block that can be moved to cover the solid
                    # covered by the next current_block without uncovering its own
                    while True:
                        if current_block == 0:
                            raise NonogramError(
                                'All previous blocks cover solids')
                        current_block -= 1
                        if (solid[current_block] < 0) or (
                                res[current_block + 1] + solid[current_block + 1] -
                                clue[current_block] + 1 <=
                                res[current_block] + solid[current_block]):
                            break

                    # set the current_block near to the position of the next current_block,
                    # so that it just overlaps the solid, and try again
                    next_pos = res[current_block + 1]
                    next_solid = solid[current_block + 1]
                    res[current_block] = next_pos + \
                        next_solid - clue[current_block] + 1
                    continue

                # otherwise, simply move to the dot, and try again
                res[current_block] += i
                continue

            # now check to see if the end of the current current_block touches an
            # existing solid - if so, the current_block must be shuffled further to
            # ensure it overlaps the solid, but no solid may emerge from the
            # other end
            pos = res[current_block]

            if pos + block_size < line_size:
                end_block_cell = line[pos + block_size]

                if (end_block_cell == BOX) and (solid[current_block] < 0):
                    solid[current_block] = block_size

                while pos + block_size < line_size:
                    cell = line[pos]
                    if cell == BOX:
                        break
                    end_block_cell = line[pos + block_size]
                    if end_block_cell != BOX:
                        break

                    pos += 1
                    solid[current_block] -= 1

            res[current_block] = pos

            logger.debug('     shuffle %s >%s %d',
                         current_block, pos, block_size)

            # if there's still a solid immediately after the current_block, there's
            # an error, so find an earlier current_block to move
            if pos + block_size < line_size:
                end_block_cell = line[pos + block_size]
                if end_block_cell == BOX:
                    logger.debug('     stretched %d >%s#',
                                 current_block, (pos + i))

                    # find an earlier current_block that isn't covering a solid
                    error_block = current_block
                    while True:
                        if current_block == 0:
                            raise NonogramError(
                                'The %d-th block cannot be stretched' % error_block)
                        current_block -= 1
                        if (solid[current_block] < 0) or (
                                res[current_block + 1] + solid[current_block + 1] -
                                clue[current_block] + 1 <=
                                res[current_block] + solid[current_block]):
                            break

                    # set the current_block near to the position of the next current_block,
                    # so that it just overlaps the solid, and try again
                    next_pos = res[current_block + 1]
                    next_solid = solid[current_block + 1]
                    res[current_block] = next_pos + \
                        next_solid - clue[current_block] + 1
                    continue

            # the current_block is in place, so try the next
            pos = res[current_block] + 1 + clue[current_block]
            if current_block + 1 < clue_size:
                current_block += 1
                res[current_block] = pos
            else:
                # no more blocks, so just check for any remaining solids
                while pos < line_size:
                    cell = line[pos]
                    if cell == BOX:
                        break

                    pos += 1

                # if a solid was found...
                if pos < line_size:
                    logger.debug('     trailing >%s#', pos)

                    # move the current_block so it covers it, but check if solid
                    # becomes uncovered
                    if (solid[current_block] >= 0) and (
                            pos - block_size + 1 > res[current_block] + solid[current_block]):
                        #  a solid was uncovered, so look for an earlier current_block

                        # find an earlier current_block that isn't covering a solid
                        while True:
                            if current_block == 0:
                                raise NonogramError(
                                    'All previous blocks cover solids')
                            current_block -= 1
                            if (solid[current_block] < 0) or (
                                    res[current_block + 1] +
                                    solid[current_block + 1] - clue[current_block] + 1 <=
                                    res[current_block] + solid[current_block]):
                                break

                        # set the current_block near to the position of the next current_block,
                        # so that it just overlaps the solid, and try again
                        next_pos = res[current_block + 1]
                        next_solid = solid[current_block + 1]
                        res[current_block] = next_pos + \
                            next_solid - clue[current_block] + 1

                        continue
                    # if (solid[current_block] >= 0) ... }

                    # solid[current_block] -= pos - block_size + 1 - res[current_block]
                    res[current_block] = pos - block_size + 1
                    continue
                # if pos < line_size ... }
                # no solid found
                current_block += 1
            # if current_block + 1 < clue_size: else ... }
        # while current_block < clue_size ... }
        # all okay
        return res

    @classmethod
    def push_right(cls, line, clue):
        """Move all the blocks to the right"""
        clue = clue[::-1]
        line = line[::-1]

        return list(reversed(cls.push_left(line, clue)))

    def _solve(self):
        """
        Solve the given line with given clue (description)
        using left and right overlap algorithm
        """

        line = self.line
        clue = self.description

        line_size = len(line)
        clue_size = len(clue)

        logger.debug('Line range = %s to %s', 0, line_size - 1)

        if (clue_size == 1) and clue[0] == 0:
            clue_size = 0

        left_positions = self.push_left(line, clue)

        left_desc = []
        for block in range(clue_size):
            left_desc.append('(%s + %s)' %
                             (left_positions[block], clue[block]))
        logger.info('Left: %s', ' '.join(left_desc))
        left_desc = []
        for block in range(clue_size):
            size_now = sum(map(len, left_desc))
            block = (
                '-' * (left_positions[block] - size_now)) + ('#' * clue[block])
            left_desc.append(block)

        desc_size = len(''.join(left_desc))
        left_desc.append('-' * (line_size - desc_size))
        logger.info('Left: >%s<', ''.join(left_desc))

        middle_desc = [_SYMBOL_MAP.get(cell, '?') for cell in line]
        logger.info('Middle: >%s<', ''.join(middle_desc))

        logger.info('Line range = %d to %d', 0, line_size - 1)

        right_positions = self.push_right(line, clue)

        right_desc = []
        for block in range(clue_size):
            right_desc.append('(%s + %s)' %
                              (right_positions[block], clue[block]))
        logger.info('Right: %s', ' '.join(right_desc))
        right_desc = []
        for block in range(clue_size):
            size_now = sum(map(len, right_desc))
            dots = '-' * \
                (line_size - right_positions[block] - clue[block] - size_now)
            solids = '#' * (clue[block])
            block = dots + solids
            right_desc.append(block)

        desc_size = len(''.join(right_desc))
        right_desc.append('-' * (line_size - desc_size))
        logger.info('Right: >%s<', ''.join(right_desc))

        work = list(line)

        j = 0
        for i in range(clue_size):
            right_positions[i] = line_size - right_positions[i] - clue[i]

            k = left_positions[i]
            work[j: k] = [SPACE] * (k - j)

            j = right_positions[i]
            k = left_positions[i] + clue[i]

            work[j: k] = [BOX] * (k - j)

            j = right_positions[i] + clue[i]

        k = line_size
        work[j: k] = [SPACE] * (k - j)

        return work

# -*- coding: utf-8 -*-
"""
Simple algorithm to solve nonograms using left and right overlaps

See details:
http://www.lancaster.ac.uk/~simpsons/nonogram/ls-fast
"""

from notetool.tool.log import logger
from six.moves import range

from ..core.common import BOX, SPACE, UNKNOWN, NonogramError
from .base import BaseLineSolver

_SYMBOL_MAP = {
    UNKNOWN: ' ',
    SPACE: '-',
    BOX: '#',
}


class FastSolver(BaseLineSolver):
    """
    Nonogram line solver that uses left and right overlap algorithm.
    The algorithm gets most of the solution, but sometimes not complete one.
    """

    @classmethod
    def push_left(cls, line, clue):
        """
        Move all the blocks to the left.
        Raise NonogramError if inconsistency detected.
        """

        line_size = len(line)
        clue_size = len(clue)
        res = [0] * clue_size
        solid = [-1] * clue_size

        # initial state
        current_block = 0

        if clue_size > 0:
            res[current_block] = 0

        logger.info('Pushing clue: %s', ', '.join(map(str, clue)))
        logger.info('Pushing line: >%s<', ''.join(
            _SYMBOL_MAP.get(cell, '?') for cell in line))

        while current_block < clue_size:
            # find first/next non-dot:
            # stop if current_block won't fit into remainder of line

            pos = res[current_block]
            block_size = clue[current_block]

            logger.debug('     start %d >%s %d',
                         current_block, pos, block_size)

            while pos < line_size - block_size:
                cell = line[pos]
                if cell != SPACE:
                    break

                pos += 1

            res[current_block] = pos
            logger.debug('     end %d >%s %d', current_block, pos, block_size)

            # no room left
            if (pos + block_size > line_size) or (line[pos] == SPACE):
                raise NonogramError(
                    'No room left: cannot fit %d-th block' % current_block)

            # assume current position doesn't cover a solid
            solid[current_block] = -1

            # check if the current_block fits in before the next dot;
            # monitor for passing over a solid
            i = 0
            while i < block_size:
                cell = line[pos + i]
                if cell == SPACE:
                    break

                if (solid[current_block] < 0) and (cell == BOX):
                    logger.debug('     solid %d >%s#',
                                 current_block, (pos + i))
                    solid[current_block] = i
                i += 1

            # if a dot was encountered...
            if i < block_size:
                logger.debug('     dot %d >%s-', current_block, (pos + i))

                # if a solid is covered, get an earlier current_block to fit
                if solid[current_block] >= 0:
                    # find an earlier current_block that can be moved to cover the solid
                    # covered by the next current_block without uncovering its own
                    while True:
                        if current_block == 0:
                            raise NonogramError(
                                'All previous blocks cover solids')
                        current_block -= 1
                        if (solid[current_block] < 0) or (
                                res[current_block + 1] + solid[current_block + 1] -
                                clue[current_block] + 1 <=
                                res[current_block] + solid[current_block]):
                            break

                    # set the current_block near to the position of the next current_block,
                    # so that it just overlaps the solid, and try again
                    next_pos = res[current_block + 1]
                    next_solid = solid[current_block + 1]
                    res[current_block] = next_pos + \
                        next_solid - clue[current_block] + 1
                    continue

                # otherwise, simply move to the dot, and try again
                res[current_block] += i
                continue

            # now check to see if the end of the current current_block touches an
            # existing solid - if so, the current_block must be shuffled further to
            # ensure it overlaps the solid, but no solid may emerge from the
            # other end
            pos = res[current_block]

            if pos + block_size < line_size:
                end_block_cell = line[pos + block_size]

                if (end_block_cell == BOX) and (solid[current_block] < 0):
                    solid[current_block] = block_size

                while pos + block_size < line_size:
                    cell = line[pos]
                    if cell == BOX:
                        break
                    end_block_cell = line[pos + block_size]
                    if end_block_cell != BOX:
                        break

                    pos += 1
                    solid[current_block] -= 1

            res[current_block] = pos

            logger.debug('     shuffle %s >%s %d',
                         current_block, pos, block_size)

            # if there's still a solid immediately after the current_block, there's
            # an error, so find an earlier current_block to move
            if pos + block_size < line_size:
                end_block_cell = line[pos + block_size]
                if end_block_cell == BOX:
                    logger.debug('     stretched %d >%s#',
                                 current_block, (pos + i))

                    # find an earlier current_block that isn't covering a solid
                    error_block = current_block
                    while True:
                        if current_block == 0:
                            raise NonogramError(
                                'The %d-th block cannot be stretched' % error_block)
                        current_block -= 1
                        if (solid[current_block] < 0) or (
                                res[current_block + 1] + solid[current_block + 1] -
                                clue[current_block] + 1 <=
                                res[current_block] + solid[current_block]):
                            break

                    # set the current_block near to the position of the next current_block,
                    # so that it just overlaps the solid, and try again
                    next_pos = res[current_block + 1]
                    next_solid = solid[current_block + 1]
                    res[current_block] = next_pos + \
                        next_solid - clue[current_block] + 1
                    continue

            # the current_block is in place, so try the next
            pos = res[current_block] + 1 + clue[current_block]
            if current_block + 1 < clue_size:
                current_block += 1
                res[current_block] = pos
            else:
                # no more blocks, so just check for any remaining solids
                while pos < line_size:
                    cell = line[pos]
                    if cell == BOX:
                        break

                    pos += 1

                # if a solid was found...
                if pos < line_size:
                    logger.debug('     trailing >%s#', pos)

                    # move the current_block so it covers it, but check if solid
                    # becomes uncovered
                    if (solid[current_block] >= 0) and (
                            pos - block_size + 1 > res[current_block] + solid[current_block]):
                        #  a solid was uncovered, so look for an earlier current_block

                        # find an earlier current_block that isn't covering a solid
                        while True:
                            if current_block == 0:
                                raise NonogramError(
                                    'All previous blocks cover solids')
                            current_block -= 1
                            if (solid[current_block] < 0) or (
                                    res[current_block + 1] +
                                    solid[current_block + 1] - clue[current_block] + 1 <=
                                    res[current_block] + solid[current_block]):
                                break

                        # set the current_block near to the position of the next current_block,
                        # so that it just overlaps the solid, and try again
                        next_pos = res[current_block + 1]
                        next_solid = solid[current_block + 1]
                        res[current_block] = next_pos + \
                            next_solid - clue[current_block] + 1

                        continue
                    # if (solid[current_block] >= 0) ... }

                    # solid[current_block] -= pos - block_size + 1 - res[current_block]
                    res[current_block] = pos - block_size + 1
                    continue
                # if pos < line_size ... }
                # no solid found
                current_block += 1
            # if current_block + 1 < clue_size: else ... }
        # while current_block < clue_size ... }
        # all okay
        return res

    @classmethod
    def push_right(cls, line, clue):
        """Move all the blocks to the right"""
        clue = clue[::-1]
        line = line[::-1]

        return list(reversed(cls.push_left(line, clue)))

    def _solve(self):
        """
        Solve the given line with given clue (description)
        using left and right overlap algorithm
        """

        line = self.line
        clue = self.description

        line_size = len(line)
        clue_size = len(clue)

        logger.debug('Line range = %s to %s', 0, line_size - 1)

        if (clue_size == 1) and clue[0] == 0:
            clue_size = 0

        left_positions = self.push_left(line, clue)

        left_desc = []
        for block in range(clue_size):
            left_desc.append('(%s + %s)' %
                             (left_positions[block], clue[block]))
        logger.info('Left: %s', ' '.join(left_desc))
        left_desc = []
        for block in range(clue_size):
            size_now = sum(map(len, left_desc))
            block = (
                '-' * (left_positions[block] - size_now)) + ('#' * clue[block])
            left_desc.append(block)

        desc_size = len(''.join(left_desc))
        left_desc.append('-' * (line_size - desc_size))
        logger.info('Left: >%s<', ''.join(left_desc))

        middle_desc = [_SYMBOL_MAP.get(cell, '?') for cell in line]
        logger.info('Middle: >%s<', ''.join(middle_desc))

        logger.info('Line range = %d to %d', 0, line_size - 1)

        right_positions = self.push_right(line, clue)

        right_desc = []
        for block in range(clue_size):
            right_desc.append('(%s + %s)' %
                              (right_positions[block], clue[block]))
        logger.info('Right: %s', ' '.join(right_desc))
        right_desc = []
        for block in range(clue_size):
            size_now = sum(map(len, right_desc))
            dots = '-' * \
                (line_size - right_positions[block] - clue[block] - size_now)
            solids = '#' * (clue[block])
            block = dots + solids
            right_desc.append(block)

        desc_size = len(''.join(right_desc))
        right_desc.append('-' * (line_size - desc_size))
        logger.info('Right: >%s<', ''.join(right_desc))

        work = list(line)

        j = 0
        for i in range(clue_size):
            right_positions[i] = line_size - right_positions[i] - clue[i]

            k = left_positions[i]
            work[j: k] = [SPACE] * (k - j)

            j = right_positions[i]
            k = left_positions[i] + clue[i]

            work[j: k] = [BOX] * (k - j)

            j = right_positions[i] + clue[i]

        k = line_size
        work[j: k] = [SPACE] * (k - j)

        return work
# -*- coding: utf-8 -*- Simple algorithm to solve nonograms using left and right overlaps  See details: http://www.lancaster.ac.uk/~simpsons/nonogram/ls-fast Nonogram line solver that uses left and right overlap algorithm.     The algorithm gets most of the solution, but sometimes not complete one. Move all the blocks to the left.         Raise NonogramError if inconsistency detected. # initial state # find first/next non-dot: # stop if current_block won't fit into remainder of line # no room left # assume current position doesn't cover a solid # check if the current_block fits in before the next dot; # monitor for passing over a solid #', # if a dot was encountered... # if a solid is covered, get an earlier current_block to fit # find an earlier current_block that can be moved to cover the solid # covered by the next current_block without uncovering its own # set the current_block near to the position of the next current_block, # so that it just overlaps the solid, and try again # otherwise, simply move to the dot, and try again # now check to see if the end of the current current_block touches an # existing solid - if so, the current_block must be shuffled further to # ensure it overlaps the solid, but no solid may emerge from the # other end # if there's still a solid immediately after the current_block, there's # an error, so find an earlier current_block to move #', # find an earlier current_block that isn't covering a solid # set the current_block near to the position of the next current_block, # so that it just overlaps the solid, and try again # the current_block is in place, so try the next # no more blocks, so just check for any remaining solids # if a solid was found... #', pos) # move the current_block so it covers it, but check if solid # becomes uncovered #  a solid was uncovered, so look for an earlier current_block # find an earlier current_block that isn't covering a solid # set the current_block near to the position of the next current_block, # so that it just overlaps the solid, and try again # if (solid[current_block] >= 0) ... } # solid[current_block] -= pos - block_size + 1 - res[current_block] # if pos < line_size ... } # no solid found # if current_block + 1 < clue_size: else ... } # while current_block < clue_size ... } # all okay Move all the blocks to the right Solve the given line with given clue (description)         using left and right overlap algorithm