#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of ROBOTGAME
#
# ROBOTGAME 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.
#
# ROBOTGAME 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
# ROBOTGAME.  If not, see <http://www.gnu.org/licenses/>.
#
# ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' '
#
#                         lasermirror.py
#                     --------------------
#       date created : Tue Aug 7 2012
#          copyright : (C) 2012 Niels G. W. Serup
#      maintained by : Niels G. W. Serup <ns@metanohi.name>

"""
Management of lasers in rooms of mirrors and targets.
"""

from __future__ import print_function
import math
import random
import itertools
from robotgame.logic.direction import *
import robotgame.logic.rollingstone as rstone
from robotgame.logic.rollingstone import Blocker
import robotgame.misc as misc

class MirrorLeft(object):
    pass

class MirrorRight(object):
    pass

class Lever(object):
    pass

class Target(object):
    pass

def generate_simple_playfield(nmirrors):
    """
    Generate a completable 16x16 playfield where:
      * there are four laser sources, one in each corner
        + the one in the upper left corner (0, 0) starts in (0, -1) heading down
        + the one in the upper right corner (15, 0) starts in (16, 0), heading left
        + the one in the lower right corner (15, 15) starts in (15, 16), heading up
        + the one in the lower left corner (0, 15) starts in (-1, 15), heading right
      * there are four laser targets
      * there are nmirrors mirrors
      * there are nmirrors levers
      * all levers are at the wall

    Return playfield : {(x, y):
        Target | MirrorLeft | MirrorRight | rstone.Blocker | Lever}
    """
    playfield = {(6, 6): Target,
                 (9, 6): Target,
                 (6, 9): Target,
                 (9, 9): Target,
                 (7, 7): rstone.Blocker,
                 (7, 8): rstone.Blocker,
                 (8, 7): rstone.Blocker,
                 (8, 8): rstone.Blocker,
                 }
    width, height = 16, 16
    
    succs = lambda d: d
    source_direc = Up
    nlevers = nmirrors
    for missing in range(4, 0, -1):
        nm = nmirrors / missing
        nmirrors -= nm
        stone_playfield, _ = rstone.generate_simple_playfield(
            7, 7, nm, 0, False, False)
        for pos, direc in stone_playfield.items():
            playfield[_adjust(source_direc, 16 - 1, 16 - 1, *pos)] \
                = random.choice((MirrorLeft, MirrorRight))
        succs = (lambda s: lambda d: succ(s(d)))(succs)
        source_direc = succ(source_direc)

    occup = set(playfield.keys())

    is_empty = lambda x, y: (x, y) not in occup

    ok_a = lambda y: is_empty(1, y)
    ok_b = lambda y: is_empty(width - 2, y)
    ok_c = lambda x: is_empty(x, 1)
    ok_d = lambda x: is_empty(x, height - 2)
    no_block = lambda x, y: \
        all((ok_a(y) if x == 0 else True,
             ok_b(y) if x == width - 1 else True,
             ok_c(x) if y == 0 else True,
             ok_d(x) if y == height - 1 else True))
    
    emptys = set([(0, y) for y in filter(ok_a, range(height))]
                 + [(width - 1, y) for y in filter(ok_b, range(height))]
                 + [(x, 0) for x in filter(ok_c, range(width))]
                 + [(x, height - 1) for x in filter(ok_d, range(width))]) - occup
    emptys_full = set(itertools.product(range(width), range(height))) - occup
    
    emptys = list(emptys)
    random.shuffle(emptys)
    emptys = set(emptys)
    
    is_empty = lambda x, y: (x, y) in emptys_full

    levers = []
    for _ in range(nlevers):
        while True:
            pos = next(iter(emptys))
            emptys.remove(pos)
            emptys_full.remove(pos)
            if no_block(*pos):
                playfield[pos] = Lever
                if not all(no_block(*pos) for pos in levers):
                    del playfield[pos]
                else:
                    levers.append(pos)
                    break

    return playfield


def _adjust(source_direc, w, h, x, y):
    return {
        Up:    lambda x, y: (x, y),
        Right: lambda x, y: (w - y, x),
        Down:  lambda x, y: (w - x, h - y),
        Left:  lambda x, y: (y, h - x),
        }[source_direc](x, y)

def generate_lasers(playfield):
    width, height = 16, 16
    sources = (((0, -1), Down),
               ((width, 0), Left),
               ((width - 1, height), Up),
               ((-1, height - 1), Right))
    lasers = []
    for start, direc in sources:
        end = start
        while True:
            cur = playfield.get(end)
            if cur is Target:
                lasers.append((start, end))
                break
            if cur is Blocker:
                lasers.append((start, end))
                break
            if cur in (MirrorLeft, MirrorRight):
                if (start, end) in lasers:
                    break
                lasers.append((start, end))
                direc = _mirror_new_direc(cur, direc)
                start = end
            new_end = direc.next_pos(end)
            if new_end[0] < 0 or new_end[1] < 0 or new_end[0] >= width or new_end[1] >= height:
                if (start, end) not in lasers:
                    lasers.append((start, new_end))
                break
            end = new_end
    return lasers

def _mirror_new_direc(mirror_type, old_direc):
    return {Down: (Left, Right),
            Left: (Down, Up),
            Up: (Right, Left),
            Right: (Up, Down)}[old_direc][
        0 if mirror_type is MirrorLeft else 1]
                
def print_playfield(playfield, width, height, hide_directions=False):
    text = [['·' for _ in range(width)] for _ in range(height)]
    for (x, y), val in playfield.items():
        if isDirection(val) and hide_directions:
            continue
        text[y][x] = '%' if val is rstone.Blocker \
            else 'x' if val is Mirror \
            else 'L' if val is Lever \
            else 'T' if val is Target else 'N'
    print('\n'.join(''.join(line) for line in text))

