<reponame>TeamSirius/mapbuilder<filename>scripts/mappointgenerator.py
# This Script:
#   - supports building mapping by mode
# Valid Modes:
#   - Point
#   - Line (horizontal or vertical)
#       - asks for the number of points in the line
#       - Naming Schema:
#           - Vertical Lines:
#               - Top-Bottom = A-Z
#           - Horizontal Lines:
#               - Left-Right = A-Z
#   - Rectangle
#   - Grid
#       - Name Schema:
#           - Columns: 0-N
#           - Rows: A-Z

#################### TODO ###################
# name points before they get sent to the database

import Tkinter
import math
import requests
import json
from PIL import Image, ImageTk
from sys import exit
from db.db import SERVER_URL, DEBUG
import ntpath
import argparse
import sys
from server_interface import ServerInterface

server_interface = None
APP = {}  # will hold global information for TKinter app
DENSITY = 30 # default density of grids

def file_name(file_path):
    head, tail = ntpath.split(file_path)
    return tail or ntpath.basename(head)

def get_floor_information():
    """ Get the name of the building and floor information from the user
        If a non-integer floor number is entered, keep asking"""

    building_name = raw_input("Building: ")
    floor_number = -1
    while True:
        try:
            floor_number = int(raw_input("Floor Number: "))
            break
        except ValueError:
            print >> sys.stderr, "Not a number. Try again"

    return building_name, floor_number


def get_floor_identifier(image_file):
    """ retrieves floor identifier from server """
    global server_interface
    building_name, floor_number = get_floor_information()
    get_payload = {
        'building_name__iexact': building_name.lower(),
        'floor_number': floor_number
    }

    worked, data = server_interface.get_single_item(ServerInterface.FLOOR,
                                                    get_payload)

    if not worked:
        with open(image_file, 'rb') as image:
            post_payload = {
                'floor_number': floor_number,
                'building_name': building_name
            }

            files = {
                'image': image
            }

            data = server_interface.post_with_files(ServerInterface.FLOOR,
                                                    post_payload,
                                                    files)
    return data['resource_uri']

#--------------------------------
# Object code here
#--------------------------------

class Location(object):
    """ used for posting to the server """

    def __init__(self, point, name, verbose):
        self.point = point
        self.name = name
        self.verbose = verbose

    def save(self, floor_identifier):
        global server_interface
        payload = {
            'verbose_name': self.verbose,
            'short_name': self.name,
            'x_coordinate': self.point.x,
            'y_coordinate': self.point.y,
            'floor': floor_identifier
        }

        for i in xrange(4):
            payload['direction'] = i * 90
            server_interface.post(ServerInterface.LOCATION, payload)

class Point(object):
    """ Point Object, for simple (x,y) """
    def __init__(self, x, y):
        self.x = x
        self.y = y

#--------------------------------
# Object code for TKinter drawing
#--------------------------------

class DrawPoint(object):
    """ wrapper for Points when drawing"""
    def __init__(self, point):
        self.point = point

    def points(self):
        """ Returns the point to be drawn for a point """
        return self.point

class Rectangle(object):
    def __init__(self, point_list):
        p1 = point_list[0]
        p2 = point_list[1]

        self.XL = min(p1.x, p2.x)
        self.XR = max(p1.x, p2.x)
        self.YT = min(p1.y, p2.y)
        self.YB = max(p1.y, p2.y)

    def points(self):
        """ Returns the corners of a rectangle """
        return [Point(self.XL, self.YT),
                Point(self.XR, self.YT),
                Point(self.XR, self.YB),
                Point(self.XL, self.YB)]

#--------------------------------
# Draw Functions Below
#--------------------------------

def cornersAndCenter(rect):
    """ Returns the points to be drawn for a rectangle """
    return [Point(rect.XL, rect.YT),
            Point(rect.XR, rect.YT),
            Point(rect.XR, rect.YB),
            Point(rect.XL, rect.YB),
            Point((rect.XL + rect.XR) // 2, (rect.YB + rect.YT) // 2)]

def line(rect):
    """ Returns the points to be drawn for a line """
    num_points = int(raw_input('Number of points in line: '))

    width = rect.XR - rect.XL
    height = rect.YB - rect.YT 

    if width > height: 
        # horizontal line
        x = rect.XL
        y = (rect.YT + rect.YB) // 2
        x_step = width // (num_points - 1)
        y_step = 0
    else: 
        # vertical line
        x = (rect.XL + rect.XR) // 2
        y = rect.YT
        x_step = 0
        y_step = height // (num_points - 1)

    points = []
    for i in range(num_points):
        points.append(Point(x + (i * x_step), y + (i * y_step)))

    return points

def grid(rect):
    """ Returns the points to be drawn for a grid """
    width = rect.XR - rect.XL
    height = rect.YB - rect.YT 

    num_across = max(1, int(math.ceil(width // DENSITY)))
    num_high = max(1, int(math.ceil(height // DENSITY)))
    x_justification = ((width - (DENSITY * (num_across - 1))) / 2) + rect.XL
    y_justification = ((height - (DENSITY * (num_high - 1))) / 2) + rect.YT

    points = []
    for x in range(num_across):
        for y in range(num_high):
            x_coord = x_justification + (DENSITY * x)
            y_coord = y_justification + (DENSITY * y)
            points.append(Point(x_coord, y_coord))
    return points

#--------------------------------
# TKinter Application Code Below
#--------------------------------

def initializeAPP(image_path):
    """ Initializes data for app and binds tkinter buttons """
    global APP

    image = Image.open(image_path)
    width, height = image.size[0], image.size[1]

    APP['window'] = Tkinter.Tk()
    APP['frame'] = Tkinter.Frame(APP['window'])
    image_tk = ImageTk.PhotoImage(image)

    APP['canvas'] = Tkinter.Canvas(APP['frame'], width=width, height=height)
    APP['canvas'].create_image(width // 2, height // 2, image=image_tk)

    APP['dims'] = {'w': width, 'h': height}
    APP['buttons'] = getButtons()
    APP['locations'] = []
    APP['points'] = []
    APP['canvas_list'] = []
    APP['mode'] = {'type': Rectangle, 'vertices': 2}
    APP['draw'] = cornersAndCenter
    packAndRunApp()

def getButtons():
    """ Returns dict of buttons; will be added to APP object """
    global APP
    buttons = {'point_btn': Tkinter.Button(APP['frame'], text="Point", command=point_mode),
               'line_btn': Tkinter.Button(APP['frame'], text="Line", command=line_mode),
               'rectangle_btn': Tkinter.Button(APP['frame'], text="Rectangle", command=rectangle_mode),
               'grid_btn': Tkinter.Button(APP['frame'], text="Grid", command=grid_mode),
               'log_btn': Tkinter.Button(APP['frame'], text="Log", command=log)}
    return buttons

def packAndRunApp():
    """ packs tkinter interface at startup of app """
    global APP

    APP['frame'].pack()
    APP['canvas'].pack()

    APP['buttons']['point_btn'].pack(side='left')
    APP['buttons']['line_btn'].pack(side='left')
    APP['buttons']['rectangle_btn'].pack(side='left')
    APP['buttons']['grid_btn'].pack(side="left")

    APP['canvas'].bind("<Button-1>", handle_click)
    APP['window'].mainloop()

def draw_point(p, color):
    """ draws a point at the coordinates with the specified color """
    global APP

    radius = 5  # point radius
    new_canvas = APP['canvas'].create_oval(
        p.x - radius, p.y - radius, p.x + radius, p.y + radius, fill=color)

    APP['points'].append(p)
    APP['canvas_list'].append(new_canvas)

def draw_rectangle(rectangle, outline_color):
    """ draws a rectangle at the coordinates with the specified color """
    global APP

    corners = rectangle.points()
    p1 = corners[0]
    p2 = corners[2]

    new_canvas = APP['canvas'].create_rectangle(
        p1.x, p1.y, p2.x, p2.y, outline=outline_color, width=2)

    APP['canvas_list'].append(new_canvas)

def handle_click(click):
    """ Adds a point to the canvas; if there are enough points, allows logging """
    global APP
    point = Point(click.x, click.y)

    num_points = len(APP['points']) + 1

    if num_points > APP['mode']['vertices']:
        reset()
        APP['buttons']['log_btn'].pack_forget()
    elif num_points == APP['mode']['vertices']:
        APP['buttons']['log_btn'].pack(side='left')

    draw_point(point, 'blue')

def log():
    """ generates the rest of the shape for drawing; asks for confirmation if input
        is correct.  """
    global APP

    APP['canvas'].unbind("<Button-1>")

    # name = raw_input("Short name: ")
    # verbose = raw_input("Verbose name: ")

    shape = APP['mode']['type'](APP['points'])
    reset()

    if APP['mode']['type'] == Rectangle:
        draw_rectangle(shape, 'green')
        for p in APP['draw'](shape):
            draw_point(p, 'green')
    else:
        draw_point(shape.points, 'red')

    # command = raw_input("Confirm Points? [Y/N]")
    command = 'Y'
    if command.upper() == 'Y':
        reset()

        draw_rectangle(shape, 'red')
        for p in APP['draw'](shape):
            draw_point(p, 'red')

        APP['locations'] += shape.points()
        APP['points'] = []
        APP['canvas_list'] = []
    else:
        reset()

    APP['buttons']['log_btn'].pack_forget()
    APP['canvas'].bind("<Button-1>", handle_click)

def reset():
    """ removes unlogged points from screen """
    global APP

    for cid in APP['canvas_list']:
        APP['canvas'].delete(cid)

    APP['points'] = []
    APP['canvas_list'] = []

def point_mode():
    """ resets points; sets APP's draw function to point mode""" 
    global APP
    APP['mode'] = {'type': DrawPoint, 'vertices': 1}
    APP['draw'] = points
    reset()

def rectangle_mode():
    """ resets points; sets APP's draw function to rectangle mode""" 
    global APP
    APP['mode'] = {'type': Rectangle, 'vertices': 2}
    APP['draw'] = cornersAndCenter
    reset()

def line_mode():
    """ resets points; sets APP's draw function to line mode""" 
    global APP

    APP['mode'] = {'type': Rectangle, 'vertices': 2}
    APP['draw'] = line
    reset()

def grid_mode():
    """ resets points; sets APP's draw function to grid mode""" 
    global APP
    global DENSITY 

    APP['mode'] = {'type': Rectangle, 'vertices': 2}
    APP['draw'] = grid
    if DENSITY == 0:
        DENSITY = raw_input("Density: ")
    reset()

#################################

def main(image_file, debug, username='', password=''):
    """ opens up server interface; finds floor id; initializes and runs app"""
    global server_interface

    if not debug:
        server_interface = ServerInterface(username, password)
        floor_identifier = get_floor_identifier(image_file)

    initializeAPP(image_file)

    if not debug:
        command = raw_input("Save Points? [Y/N] ")
        if command == 'Y':
            for loc in locations:
                loc.save(floor_identifier)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('image_file', help='The image to work with')
    parser.add_argument('username', help='Your username for the server')
    parser.add_argument('password', help='Your password for the server')
    parser.add_argument('-d', '--debug', action='store_true', help='Debug')

    args = parser.parse_args()
    main(args.image_file, args.debug, args.username, args.password)
