import enum
import os
import shared
import sys

from pathlib import Path

parser = shared.create_parser()
parser.add_argument(
    '--minimize-location',
    help='location to minimize windows to in the MDI area',
    default='BottomLeft',
    choices=['TopLeft', 'TopRight', 'BottomLeft', 'BottomRight'],
)
parser.add_argument(
    '--border-width',
    help='width of the subwindow borders',
    type=int,
    choices=range(0, 6),
    default=1,
)
parser.add_argument(
    '--default-window-frame',
    help='use the default title bars',
    action='store_true',
)
parser.add_argument(
    '--status-bar',
    help='use a top-level status bar',
    action='store_true',
)
parser.add_argument(
    '--window-help',
    help='add a top-level context help button',
    action='store_true',
)
parser.add_argument(
    '--window-shade',
    help='add a top-level shade/unshade button',
    action='store_true',
)
parser.add_argument(
    '--wayland-testing',
    help='debug with a custom titlebar on wayland',
    action='store_true',
)
args, unknown = shared.parse_args(parser)
QtCore, QtGui, QtWidgets = shared.import_qt(args)
compat = shared.get_compat_definitions(args)
colors = shared.get_colors(args, compat)
ICON_MAP = shared.get_icon_map(args, compat)
# 100ms between repaints, so we avoid over-repainting.
# Allows us to avoid glitchy motion during drags/
REPAINT_TIMER = 100
TRACK_TIMER = 20
CLICK_TIMER = 20
# Make the titlebar size too large, so we can get the real value with min.
TITLEBAR_HEIGHT = 2**16
# QWIDGETSIZE_MAX isn't exported, which is needed to remove fixedSize constraints.
QWIDGETSIZE_MAX = (1 << 24) - 1

# Determine the Linux display server protocol we're using.
# Use `XDG_SESSION_TYPE`, since we can override it for X11.
IS_WAYLAND = os.environ.get('XDG_SESSION_TYPE') == 'wayland'
IS_XWAYLAND = os.environ.get('XDG_SESSION_TYPE') == 'xwayland'
IS_X11 = os.environ.get('XDG_SESSION_TYPE') == 'x11'
# We can run X11 on Wayland, but this doesn't support certain
# features like mouse grabbing, so we don't use it here.
IS_TRUE_WAYLAND = 'WAYLAND_DISPLAY' in os.environ
USE_WAYLAND_FRAME = IS_WAYLAND and not args.wayland_testing

# Add a warning if we're using Wayland with a custom titlebar.
if not args.default_window_frame and USE_WAYLAND_FRAME:
    print('WARNING: Wayland does not support custom title bars.', file=sys.stderr)
    print('Applications in Wayland cannot set their own position.', file=sys.stderr)
    print('Defaulting to the system title bar instead.', file=sys.stderr)

class MinimizeLocation(enum.IntEnum):
    '''Location where to place minimized widgets.'''

    TopLeft = 0
    TopRight = 1
    BottomLeft = 2
    BottomRight = 3

class WindowEdge(enum.IntEnum):
    '''Enumerations for window edge positions.'''

    NoEdge = 0
    Top = 1
    Bottom = 2
    Left = 3
    Right = 4
    TopLeft = 5
    TopRight = 6
    BottomLeft = 7
    BottomRight = 8

MINIMIZE_LOCATION = getattr(MinimizeLocation, args.minimize_location)
TOP_EDGES = (WindowEdge.Top, WindowEdge.TopLeft, WindowEdge.TopRight)
BOTTOM_EDGES = (WindowEdge.Bottom, WindowEdge.BottomLeft, WindowEdge.BottomRight)
LEFT_EDGES = (WindowEdge.Left, WindowEdge.TopLeft, WindowEdge.BottomLeft)
RIGHT_EDGES = (WindowEdge.Right, WindowEdge.TopRight, WindowEdge.BottomRight)

def standard_icon(widget, icon):
    '''Get a standard icon.'''
    return shared.standard_icon(args, widget, icon, ICON_MAP)

def menu_icon(widget):
    '''Get the menu icon depending on the stylesheet.'''
    return standard_icon(widget, compat.SP_TitleBarMenuButton)

def minimize_icon(widget):
    '''Get the minimize icon depending on the stylesheet.'''
    return standard_icon(widget, compat.SP_TitleBarMinButton)

def maximize_icon(widget):
    '''Get the maximize icon depending on the stylesheet.'''
    return standard_icon(widget, compat.SP_TitleBarMaxButton)

def restore_icon(widget):
    '''Get the restore icon depending on the stylesheet.'''
    return standard_icon(widget, compat.SP_TitleBarNormalButton)

def help_icon(widget):
    '''Get the help icon depending on the stylesheet.'''
    return standard_icon(widget, compat.SP_TitleBarContextHelpButton)

def shade_icon(widget):
    '''Get the shade icon depending on the stylesheet.'''
    return standard_icon(widget, compat.SP_TitleBarShadeButton)

def unshade_icon(widget):
    '''Get the unshade icon depending on the stylesheet.'''
    return standard_icon(widget, compat.SP_TitleBarUnshadeButton)

def close_icon(widget):
    '''Get the close icon depending on the stylesheet.'''
    return standard_icon(widget, compat.SP_TitleBarCloseButton)

def transparent_icon(widget):
    '''Create a transparent icon.'''
    return QtGui.QIcon()

def action(text, parent=None, icon=None, checkable=None):
    '''Create a custom QAction.'''

    value = compat.QAction(text, parent)
    if icon is not None:
        value.setIcon(icon)
    if checkable is not None:
        value.setCheckable(checkable)

    return value

def size_greater(x, y):
    '''Compare 2 sizes, determining if any bounds of x are greater than y.'''
    return x.width() > y.width() or x.height() > y.height()

def size_less(x, y):
    '''Compare 2 sizes, determining if any bounds of x are less than y.'''
    return x.width() < y.width() or x.height() < y.height()

# UI WIDGETS
# These are just to populate the views: these could be anything.

class LargeTable(QtWidgets.QTableWidget):
    '''Table with a large number of elements.'''

    def __init__(self, parent=None):
        super().__init__(parent)

        self.setColumnCount(100)
        self.setRowCount(100)
        for index in range(100):
            row = QtWidgets.QTableWidgetItem(f'Row {index + 1}')
            self.setVerticalHeaderItem(index, row)
            column = QtWidgets.QTableWidgetItem(f'Column {index + 1}')
            self.setHorizontalHeaderItem(index, column)

class SortableTree(QtWidgets.QTreeWidget):
    '''Tree with checkboxes and a sort indicator on the header.'''

    def __init__(self, parent=None):
        super().__init__(parent)

        self.item0 = QtWidgets.QTreeWidgetItem(self)
        self.item1 = QtWidgets.QTreeWidgetItem(self)
        self.item2 = QtWidgets.QTreeWidgetItem(self.item1)
        self.item2.setText(0, 'subitem')
        self.item3 = QtWidgets.QTreeWidgetItem(self.item2, ['Row 2.1'])
        self.item3.setFlags(self.item3.flags() | compat.ItemIsUserCheckable)
        self.item3.setCheckState(0, compat.Unchecked)
        self.item4 = QtWidgets.QTreeWidgetItem(self.item2, ['Row 2.2'])
        self.item5 = QtWidgets.QTreeWidgetItem(self.item4, ['Row 2.2.1'])
        self.item6 = QtWidgets.QTreeWidgetItem(self.item5, ['Row 2.2.1.1'])
        self.item7 = QtWidgets.QTreeWidgetItem(self.item5, ['Row 2.2.1.2'])
        self.item3.setFlags(self.item7.flags() | compat.ItemIsUserCheckable)
        self.item7.setCheckState(0, compat.Checked)
        self.item8 = QtWidgets.QTreeWidgetItem(self.item2, ['Row 2.3'])
        self.item8.setFlags(self.item8.flags() | compat.ItemIsUserTristate)
        self.item8.setCheckState(0, compat.PartiallyChecked)
        self.item9 = QtWidgets.QTreeWidgetItem(self, ['Row 3'])
        self.item10 = QtWidgets.QTreeWidgetItem(self.item9, ['Row 3.1'])
        self.item11 = QtWidgets.QTreeWidgetItem(self, ['Row 4'])

        self.headerItem().setText(0, 'qdz')
        self.setSortingEnabled(False)
        self.topLevelItem(0).setText(0, 'qzd')
        self.topLevelItem(1).setText(0, 'effefe')
        self.setSortingEnabled(True)

class SettingTabs(QtWidgets.QTabWidget):
    '''Sample setting widget with a tab view.'''

    def __init__(self, parent=None):
        super().__init__(parent)

        self.setTabPosition(compat.North)
        self.general = QtWidgets.QWidget()
        self.addTab(self.general, 'General')
        self.addTab(QtWidgets.QWidget(), 'Colors')
        self.general_layout = QtWidgets.QGridLayout(self.general)
        self.general_layout.setColumnStretch(3, 10)
        for row in range(1, 10):
            self.general_layout.setRowStretch(row, 1)
        self.general_layout.setRowStretch(7, 10)

        # Add the data folder hboxlayout
        self.general_layout.addWidget(QtWidgets.QLabel('Data Folder'), 0, 0)
        self.data_folder = QtWidgets.QLineEdit(str(Path.home()))
        self.general_layout.addWidget(self.data_folder, 0, 1, 1, 3)
        self.file_dialog = QtWidgets.QPushButton('...', checkable=False)
        self.general_layout.addWidget(self.file_dialog, 0, 4)
        self.file_dialog.clicked.connect(self.launch_filedialog)

        # Add default font.
        app = QtWidgets.QApplication.instance()
        self.general_layout.addWidget(QtWidgets.QLabel('Default Font'), 1, 0)
        self.font_value = QtWidgets.QLineEdit(app.font().family())
        self.general_layout.addWidget(self.font_value, 1, 1, 1, 3)
        self.font_dialog = QtWidgets.QPushButton('...', checkable=False)
        self.general_layout.addWidget(self.font_dialog, 1, 4)
        self.font_dialog.clicked.connect(lambda _: self.launch_fontdialog(self.font_value))

        # Add item label font
        self.general_layout.addWidget(QtWidgets.QLabel('Item Label Font'), 2, 0)
        self.item_label_value = QtWidgets.QLineEdit(app.font().family())
        self.general_layout.addWidget(self.item_label_value, 2, 1, 1, 3)
        self.item_label_dialog = QtWidgets.QPushButton('...', checkable=False)
        self.general_layout.addWidget(self.item_label_dialog, 2, 4)
        self.item_label_dialog.clicked.connect(lambda _: self.launch_fontdialog(self.item_label_value))

        # Add the "Show Grid" QCheckbox.
        self.grid = QtWidgets.QCheckBox('Show grid', self.general)
        self.general_layout.addWidget(self.grid, 3, 2, 1, 1)

        # Grid square size.
        self.grid_size = QtWidgets.QLabel('Grid Square Size', self.general)
        self.general_layout.addWidget(self.grid_size, 4, 0, 1, 2)
        self.grid_spin = QtWidgets.QSpinBox(self.general)
        self.grid_spin.setValue(16)
        self.general_layout.addWidget(self.grid_spin, 4, 2, 1, 1)

        # Add units of measurement
        self.units = QtWidgets.QLabel('Default length unit of measurement', self.general)
        self.general_layout.addWidget(self.units, 5, 0, 1, 2)
        self.units_combo = QtWidgets.QComboBox()
        self.units_combo.addItem('Inches')
        self.units_combo.addItem('Foot')
        self.units_combo.addItem('Meter')
        self.general_layout.addWidget(self.units_combo, 5, 2, 1, 1)

        # Add the alignment options
        self.align_combo = QtWidgets.QComboBox()
        self.align_combo.addItem('Align Top')
        self.align_combo.addItem('Align Bottom')
        self.align_combo.addItem('Align Left')
        self.align_combo.addItem('Align Right')
        self.align_combo.addItem('Align Center')
        self.general_layout.addWidget(self.align_combo, 6, 0, 1, 2)
        self.word_wrap = QtWidgets.QCheckBox('Word Wrap', self.general)
        self.general_layout.addWidget(self.word_wrap, 6, 2, 1, 1)

    def launch_filedialog(self):
        '''Launch the file dialog and store the folder.'''

        dialog = QtWidgets.QFileDialog()
        dialog.setFileMode(compat.Directory)
        dialog.setOption(compat.FileDontUseNativeDialog)
        dialog.setDirectory(self.data_folder.text())
        if shared.execute(args, dialog):
            self.data_folder.setText(dialog.selectedFiles()[0])

    def launch_fontdialog(self, edit):
        initial = QtGui.QFont()
        initial.setFamily(edit.text())
        font, ok = QtWidgets.QFontDialog.getFont(initial)
        if ok:
            edit.setText(font.family())

# RESIZE HELPERS

def border_size(self):
    '''Get the size of the border, regardless if present.'''
    return QtCore.QSize(2 * self._border, 2 * self._border)

def minimized_content_size(self):
    '''Get the minimum content size of the widget.'''
    return self._titlebar_size

def minimized_size(self):
    '''Get the minimum size of the widget, with the size grips hidden.'''

    size = self.minimized_content_size
    if self._border:
        size = size + self.border_size
    return size

def minimum_size(self):
    '''Get the minimum size for the widget.'''

    size = self.minimized_size
    if getattr(self, '_sizegrip', None) is not None and self._sizegrip.isVisible():
        # Don't modify in place: percolates later.
        size = size + self._sizegrip_size

    if getattr(self, '_statusbar', None) is not None and self._statusbar.isVisible():
        size = size + self._statusbar_size

    return size

def get_larger_size(x, y):
    '''Get the larger of the two sizes, for both the height and width.'''
    return QtCore.QSize(max(x.width(), y.width()), max(x.height(), y.height()))

def set_minimum_size(self):
    '''Sets the minimum size of the window and the titlebar, with clobbering.'''

    self._old_minimum_size = self.minimumSize()
    self._titlebar.set_minimum_size()
    self._titlebar_size = self._titlebar.minimumSize()
    self.setMinimumSize(self.minimum_size)

def set_larger_minimum_size(self):
    '''Sets the minimum size of the window and the titlebar, without clobbering.'''

    if self._old_minimum_size is not None:
        self.setMinimumSize(self._old_minimum_size)
    self._titlebar.set_minimum_size()
    self._titlebar_size = self._titlebar.minimumSize()
    size = get_larger_size(self.minimum_size, self.minimumSize())
    self.setMinimumSize(size)

def move_to(self, position):
    '''Move the window to the desired position'''

    # Also updates the stored previous subwindow position, if applicable.
    # This means shading/unshading uses the new position of the window,
    # but the old sizes, rather than jump the window back.
    # NOTICE: this fails on Wayland. Worse, using `QMainWindow::move` on
    # Wayland causes the cursor position to be incorrect, causing issues
    # with other events.
    if IS_WAYLAND and self.window() == self:
        return

    self.move(position)
    rect = self._titlebar._window_rect
    if rect is not None:
        rect.moveTo(position)

def set_geometry(self, rect):
    '''Set the window geometry.'''

    # See `move_to` for documentation.
    self.resize(rect.size())
    window_rect = self._titlebar._window_rect
    if window_rect is not None:
        window_rect.setSize(rect.size())

    move_to(self, rect.topLeft())

def shade(self, size, grip_type):
    '''Shade the window, hiding the main widget and size grip.'''

    self._widget.hide()
    if getattr(self, f'_{grip_type}') is not None:
        getattr(self, f'_{grip_type}').hide()
    self.set_minimum_size()
    self.resize(size)

def unshade(self, rect, grip_type):
    '''Unshade the window, showing the main widget and size grip.'''

    self._widget.show()
    if getattr(self, f'_{grip_type}') is not None:
        getattr(self, f'_{grip_type}').show()
    self.set_larger_minimum_size()
    self.set_geometry(rect)

def start_drag(self, event, window_type):
    '''Start the window drag state.'''
    setattr(self, f'_{window_type}_drag', event.pos())

def handle_drag(self, event, window, window_type):
    '''Handle the window drag event.'''

    position = event.pos() - getattr(self, f'_{window_type}_drag')
    window.move_to(window.mapToParent(position))

def end_drag(self, window_type):
    '''End the window drag state.'''
    setattr(self, f'_{window_type}_drag', None)

def start_move(self, widget, window_type):
    '''Start the window move state.'''

    setattr(self, f'_{window_type}_move', widget)
    widget.menu_move_to(QtGui.QCursor.pos())

def handle_move(self, position, window_type):
    '''Handle the window move event.'''
    getattr(self, f'_{window_type}_move').menu_move_to(position)

def end_move(self, window_type):
    '''End the window move state.'''
    setattr(self, f'_{window_type}_move', None)

def start_resize(self, window, window_type):
    '''Start the window resize state.'''

    # NOTE: We can't use a rubber band with mouse tracking,
    # since mouse events only occurs if the user is holding
    # down the house. Simulating a mouse click isn't enough,
    # even if it sends a mouse press without a release.
    setattr(self, f'_{window_type}_resize', window)
    self.window().setCursor(compat.SizeFDiagCursor)
    self.menu_size_to(QtGui.QCursor.pos())

    # Grab the mouse so we can intercept the click event,
    # and track hover events outside the app. This doesn't
    # work on Wayland or on macOS.
    #   https://doc.qt.io/qt-5/qwidget.html#grabMouse
    if not IS_TRUE_WAYLAND and not sys.platform == 'darwin':
        self.window().grabMouse()

def handle_resize(self, position):
    '''Handle the window resize event.'''
    self.menu_size_to(position)

def end_resize(self, window_type):
    '''End the window resize state.'''

    window = getattr(self, f'_{window_type}_resize')
    if window is None:
        return

    setattr(self, f'_{window_type}_resize', None)
    window.window().unsetCursor()
    if not IS_TRUE_WAYLAND and not sys.platform == 'darwin':
        self.window().releaseMouse()

def start_frame(self, frame, window_type):
    '''Start the window frame resize state.'''
    setattr(self, f'_{window_type}_frame', frame)

def handle_frame(self, window, event, window_type):
    '''Handle the window frame resize event.'''

    # Check if use size grips, return early.
    frame = getattr(window, '_sizeframe', None)
    if frame is None:
        return
    self.frame_event(event, frame)

    # Store if the frame state is active.
    if frame.is_active and not getattr(self, f'_{window_type}_frame'):
        start_frame(self, frame, window_type)
    elif not frame.is_active and getattr(self, f'_{window_type}_frame'):
        end_frame(self, window_type)

def end_frame(self, window_type):
    '''End the window frame resize state.'''
    setattr(self, f'_{window_type}_frame', None)

# EVENT HANDLES

def window_resize_event(self, event):
    '''Ensure titlebar text elides normally.'''

    # Need to trigger the titlebar title resize. Need to handle it
    # here, since the SizeFrame resizes won't always trigger a
    # Label::resizeEvent, which can cause the text to stay elided.
    title_timer = self._titlebar._title._timer
    title_timer.start(REPAINT_TIMER)

    super(type(self), self).resizeEvent(event)

def window_show_event(self, event, grip_type):
    '''Set the minimum size policies once the widgets are shown.'''

    # Until shown, the size grip has inaccurate sizes.
    # Set the minimum size policy of the widget.
    # The show event occurs just after everything is shown,
    # so the widget sizes (and isVisible) are accurate.
    self._titlebar_size = self._titlebar.minimumSize()
    if getattr(self, f'_{grip_type}') is not None:
        grip_size = getattr(self, f'_{grip_type}').sizeHint()
        setattr(self, f'_{grip_type}_size', QtCore.QSize(0, grip_size.height()))
    size = get_larger_size(self.minimum_size, self.minimumSize())
    self.setMinimumSize(size)

    super(type(self), self).showEvent(event)

def window_mouse_double_click_event(self, event):
    '''Override the mouse double click, and don't call the press event.'''

    # By default, the flowchart for titlebar double clicks is as follows:
    #   1. If minimized, restore
    #   2. If maximized, restore
    #   3. If no state and can shade, shade
    #   4. If no state and cannot shade, maximize
    #   5. If shaded, unshade.
    widget = self._titlebar
    if not widget.underMouse() or event.button() != compat.LeftButton:
        return super(type(self), self).mouseDoubleClickEvent(event)
    if widget._is_shaded:
        widget.unshade()
    elif widget.isMinimized() or widget.isMaximized():
        widget.restore()
    elif widget._has_shade:
        widget.shade()
    else:
        widget.maximize()

def window_mouse_press_event(self, event, window, window_type):
    '''Override a mouse click on the titlebar to allow a move.'''

    widget = self._titlebar
    if widget.underMouse():
        # `self.window()._subwindow_move` cannot be set, since we're inside
        # the global event filter here. We handle conflicts here,
        # so only one of the 4 states can be set. We can't move
        # minimized widgets, so don't try.
        is_left = event.button() == compat.LeftButton
        is_minimized = self.isMinimized() and not widget._is_shaded
        has_frame = getattr(window, f'_{window_type}_frame') is not None
        if is_left and not is_minimized and not has_frame:
            start_drag(self.window(), event, window_type)
        elif event.button() == compat.RightButton:
            position = shared.single_point_global_position(args, event)
            shared.execute(args, widget._main_menu, position)
    return super(type(self), self).mousePressEvent(event)

def window_mouse_move_event(self, event, window, window_type):
    '''Reposition the window on the move event.'''

    if getattr(window, f'_{window_type}_frame') is not None:
        end_drag(window, window_type)
    if getattr(window, f'_{window_type}_drag') is not None:
        handle_drag(window, event, self, window_type)
    return super(type(self), self).mouseMoveEvent(event)

def window_mouse_release_event(self, event, window, window_type):
    '''End the drag event.'''

    end_drag(window, window_type)
    return super(type(self), self).mouseReleaseEvent(event)

# WINDOW WIDGETS

class Label(QtWidgets.QLabel):
    '''Custom QLabel-like class that allows text elision.'''

    def __init__(
        self,
        text='',
        parent=None,
        elide=compat.ElideNone,
        width_cb=None,
    ):
        super().__init__(text, parent)
        self._text = text
        self._elide = elide
        self._width_cb = width_cb
        self._timer = QtCore.QTimer()
        self._timer.setSingleShot(True)
        self._timer.timeout.connect(self.elide)

    def text(self):
        '''Get the internal text for the label.'''
        return self._text

    def setText(self, text):
        '''Override the set text event to store the text internally.'''

        # Need to set the text first, otherwise
        # the `width()` might be too small.
        self._text = text
        super().setText(text)
        self.elide()

    def elideMode(self):
        '''Get the elide mode for the label.'''
        return self._elide

    def setElideMode(self, elide):
        self._elide = elide

    def elide(self):
        '''Elide the text in the QLabel.'''

        # The width estimate might not be valid: check the callback.
        width = self.width()
        if self._width_cb is not None:
            width = self._width_cb()

        metrics = QtGui.QFontMetrics(self.font())
        elided = metrics.elidedText(self._text, self._elide, width)
        super().setText(elided)

class TitleButton(QtWidgets.QToolButton):
    '''An icon-only button, without borders, for the titlebar.'''

    def __init__(self, icon, parent=None):
        super().__init__()
        self.setIcon(icon)
        self.setAutoRaise(True)

class TitleBar(QtWidgets.QFrame):
    '''Custom instance of a QTitlebar'''

    def __init__(self, window, parent=None, flags=None):
        super().__init__(parent)

        # Get and set some properties.
        self.setProperty('isTitlebar', True)
        self._window = window
        self._window_type = 'window'
        if isinstance(self._window, SubWindow):
            self._window_type = 'subwindow'
        self._state = compat.WindowNoState
        self._window_rect = None
        self._has_help = False
        self._has_shade = False
        self._is_shaded = False
        self._has_shown = False
        self._title_column = None
        self._move_timer = QtCore.QTimer()
        self._move_timer.setSingleShot(True)
        self._move_timer.timeout.connect(self.menu_move)
        self._size_timer = QtCore.QTimer()
        self._size_timer.setSingleShot(True)
        self._size_timer.timeout.connect(self.menu_size)
        if flags is not None:
            self._has_help = bool(flags & compat.WindowContextHelpButtonHint)
            self._has_shade = bool(flags & compat.WindowShadeButtonHint)

        # Create our widgets.
        self._layout = QtWidgets.QGridLayout(self)
        self._menu = TitleButton(menu_icon(self))
        self._title = Label('', self, compat.ElideRight, self.title_width)
        self._min = TitleButton(minimize_icon(self))
        self._max = TitleButton(maximize_icon(self))
        self._restore= TitleButton(restore_icon(self))
        self._close = TitleButton(close_icon(self))
        if self._has_help:
            self._help = TitleButton(help_icon(self))
        if self._has_shade:
            self._shade = TitleButton(shade_icon(self))
            self._unshade = TitleButton(unshade_icon(self))

        # Add actions to our menu.
        self._menu.setPopupMode(compat.InstantPopup)
        self._main_menu = menu = QtWidgets.QMenu(self)
        self._restore_action = action('&Restore', self, restore_icon(self))
        self._restore_action.triggered.connect(self.restore)
        self._move_action = action('&Move', self, transparent_icon(self))
        self._move_action.triggered.connect(self.move_timer)
        self._size_action = action('&Size', self, transparent_icon(self))
        self._size_action.triggered.connect(self.size_timer)
        self._min_action = action('Mi&nimize', self, minimize_icon(self))
        self._min_action.triggered.connect(self.minimize)
        self._max_action = action('Ma&ximize', self, maximize_icon(self))
        self._max_action.triggered.connect(self.maximize)
        self._top_action = action('Stay on &Top', self, checkable=True)
        self._top_action.toggled.connect(self.toggle_keep_above)
        self._close_action = action('&Close', self, close_icon(self))
        self._close_action.triggered.connect(self._window.close)
        self._main_menu.addActions([
            self._restore_action,
            self._move_action,
            self._size_action,
            self._min_action,
            self._max_action,
            self._top_action,
        ])
        self._main_menu.addSeparator()
        self._main_menu.addAction(self._close_action)
        self._menu.setMenu(self._main_menu)

        # Customize the enabled items.
        self._restore_action.setEnabled(False)

        # Create our layout.
        col = 0
        self._layout.addWidget(self._menu, 0, col)
        col += 1
        self._layout.addWidget(self._title, 0, col, compat.AlignHCenter)
        self._layout.setColumnStretch(col, 1)
        self._title_column = col
        col += 1
        if self._has_help:
            self._layout.addWidget(self._help, 0, col)
            col += 1
        self._layout.addWidget(self._min, 0, col)
        self._state1_column = col
        col += 1
        self._layout.addWidget(self._max, 0, col)
        self._state2_column = col
        col += 1
        if self._has_shade:
            self._layout.addWidget(self._shade, 0, col)
            col += 1
        self._layout.addWidget(self._close, 0, col)
        self._close_column = col
        self._restore.hide()
        if self._has_shade:
            self._unshade.hide()

        # Add in our event triggers.
        self._min.clicked.connect(self.minimize)
        self._max.clicked.connect(self.maximize)
        self._restore.clicked.connect(self.restore)
        self._close.clicked.connect(self._window.close)
        if self._has_help:
            self._help.clicked.connect(self.help)
        if self._has_shade:
            self._shade.clicked.connect(self.shade)
            self._unshade.clicked.connect(self.unshade)

    # PROPERTIES

    @property
    def minimum_width(self):
        '''Get the height (in pixels) for the minimum title bar width.'''

        app = QtWidgets.QApplication.instance()
        icon_width = self._menu.iconSize().width()
        font_size = app.font().pointSizeF()

        # We can have 4-6 icons, which with padding means we need
        # room for at least 10 characters.
        return 6 * icon_width + int(16 * font_size)

    @property
    def minimum_height(self):
        '''Get the height (in pixels) for the minimum title bar height.'''
        return TITLEBAR_HEIGHT

    @property
    def minimum_size(self):
        '''Get the minimum dimensions for the title bar.'''
        return QtCore.QSize(self.minimum_width, self.minimum_height)

    def title_width(self):
        '''Get the width of the title based on the grid layout.'''
        return self._layout.cellRect(0, self._title_column).width()

    # QT-LIKE PROPERTIES

    def windowTitle(self):
        '''Get the titlebar's window title.'''
        return self._title.text()

    def setWindowTitle(self, title):
        '''Get the titlebar's window title.'''
        self._title.setText(title)

    def isNormal(self):
        '''Get if the titlebar and therefore window has no state.'''
        return self._state == compat.WindowNoState

    def isMinimized(self):
        '''Get if the titlebar and therefore window is minimized.'''
        return self._state == compat.WindowMinimized

    def isMaximized(self):
        '''Get if the titlebar and therefore window is maximized.'''
        return self._state == compat.WindowMaximized

    # QT EVENTS

    def showEvent(self, event):
        '''Set the minimum size policies once the widgets are shown.'''

        global TITLEBAR_HEIGHT
        if not self._has_shown:
            TITLEBAR_HEIGHT = min(self.height(), TITLEBAR_HEIGHT)
            self._has_shown = True

        # Set some size policies.
        self.setMinimumSize(self.minimum_width, self.minimum_height)

        super().showEvent(event)

    # ACTIONS

    def set_minimum_size(self):
        '''Set the minimum size of the titlebar.'''
        self.setMinimumSize(self.minimum_width, self.minimum_height)

    def move_timer(self):
        '''Start timer to invoke menu_move.'''

        # We use a timer since the clicks on the menu can invoke the
        # MousePressEvent, which instantly cancels the move event.
        self._move_timer.start(CLICK_TIMER)

    def menu_move(self):
        '''Start a manually trigger move.'''
        start_move(self.window(), self, self._window_type)

    def menu_move_to(self, global_position):
        '''
        Move the subwindow so that the position is in the center bottom
        of the title bar. The position is given in global coordinates.
        '''

        # Move it so the position is right below the bottom and the center.
        position = self.mapFromGlobal(global_position)
        rect = self.geometry()
        x = position.x() - rect.width() // 2
        y = position.y()
        rect.moveBottomLeft(QtCore.QPoint(x, y))

        window = self._window
        window.move_to(window.mapToParent(rect.topLeft()))

    def size_timer(self):
        '''Start timer to invoke menu_size.'''

        # We use a timer since the clicks on the menu can invoke the
        # MousePressEvent, which instantly cancels the size event.
        self._size_timer.start(CLICK_TIMER)

    def menu_size(self):
        '''Start a manually triggered resize event.'''

        window = self.window()
        start_resize(window, self._window, self._window_type)

    def minimize(self):
        '''Minimize the current window.'''

        if self.isNormal():
            self._window_rect = self._window.geometry()
        self.set_minimized()
        self.set_shaded()

        # Toggle state
        self._state = compat.WindowMinimized
        self._is_shaded = False
        self._window.minimize(self._window.minimized_size)

        # Toggle the menu actions
        # Minimized windows should not be movable, resizable, or minimizable.
        self._restore_action.setEnabled(True)
        self._move_action.setEnabled(False)
        self._size_action.setEnabled(False)
        self._min_action.setEnabled(False)
        self._max_action.setEnabled(True)

    def maximize(self):
        '''Maximize the current window.'''

        if self.isNormal():
            self._window_rect = self._window.geometry()
        elif self.isMinimized() and not self._is_shaded:
            self._window.unminimize()
        size = self._window.maximum_size
        rect = QtCore.QRect(0, 0, size.width(), size.height())
        self.set_maximized()
        self.set_unshaded()

        # Toggle state
        self._state = compat.WindowMaximized
        self._is_shaded = False
        self._window.maximize(rect)

        # Toggle the menu actions
        self._restore_action.setEnabled(True)
        self._move_action.setEnabled(False)
        self._size_action.setEnabled(False)
        self._min_action.setEnabled(True)
        self._max_action.setEnabled(False)

    def restore(self):
        '''Restore the current window (set to no state).'''

        if self.isMinimized() and not self._is_shaded:
            self._window.unminimize()
        self.set_restored()
        self.set_unshaded()

        # Toggle state
        self._state = compat.WindowNoState
        self._is_shaded = False
        self._window.restore(self._window_rect)

        # Toggle the menu actions
        self._restore_action.setEnabled(False)
        self._move_action.setEnabled(True)
        self._size_action.setEnabled(True)
        self._min_action.setEnabled(True)
        self._max_action.setEnabled(True)

    def shade(self):
        '''Shade the current window.'''

        # Shaded windows are treated as if they have minimized state, and
        # if the window is maximized, it sets the previous window rect
        # to the maximized geometry.
        self.set_shaded()
        self.set_minimized()

        # Toggle state
        self._state = compat.WindowMinimized
        self._is_shaded = True
        self._window_rect = self._window.geometry()
        width = self._window.width()
        height = self._window.minimized_size.height()
        self._window.shade(QtCore.QSize(width, height))

        # Toggle the menu actions
        # Shaded windows should be movable, but not resizable or minimizable.
        self._restore_action.setEnabled(True)
        self._move_action.setEnabled(True)
        self._size_action.setEnabled(False)
        self._min_action.setEnabled(False)
        self._max_action.setEnabled(True)

    def unshade(self):
        '''Unshade the current window.'''

        if self.isMinimized() and not self._is_shaded:
            self._window.unminimize()

        # If the window is minimized, it restores to the previous
        # window state and position.
        self.set_unshaded()
        self.set_restored()

        # Toggle state
        self._state = compat.WindowNoState
        self._is_shaded = False
        self._window.unshade(self._window_rect)

        # Toggle the menu actions
        # Unshaded windows have no state: they are restored.
        self._restore_action.setEnabled(False)
        self._move_action.setEnabled(True)
        self._size_action.setEnabled(True)
        self._min_action.setEnabled(True)
        self._max_action.setEnabled(True)

    def toggle_keep_above(self, checked):
        '''Toggle whether to keep the window above others.'''

        # If we have a top-level widget, changing the window
        # flags causes `setParent` to be called, causing the
        # widget to hide and then re-appear. This causes major
        # visual delay, so we just ignore the hide event, then
        # set the flags, re-show the window, and unignore hides.
        # Finally, this can change the geometry of the window,
        # so we need to store the geometry and reset it.
        if self._window.window() == self._window:
            self._window._ignore_hide = True
            rect = self.window().geometry()

        flags = self._window.windowFlags()
        if checked:
            flags |= compat.WindowStaysOnTopHint
        else:
            flags &= ~compat.WindowStaysOnTopHint
        self._window.setWindowFlags(flags)

        if self._window.window() == self._window:
            self._window._ignore_hide = False
            self.window().show()
            self.window().set_geometry(rect)

    def help(self):
        '''Enter what's this mode.'''
        QtWidgets.QWhatsThis.enterWhatsThisMode()

    # VIEW

    def set_minimized(self):
        '''Show the restore and maximize icons.'''

        if self.isMinimized():
            return

        item1 = self._layout.itemAtPosition(0, self._state1_column)
        item2 = self._layout.itemAtPosition(0, self._state2_column)
        self._layout.removeItem(item1)
        self._layout.removeItem(item2)
        self._layout.addWidget(self._restore, 0, self._state1_column)
        self._layout.addWidget(self._max, 0, self._state2_column)
        self._min.hide()
        self._restore.show()
        self._max.show()

    def set_maximized(self):
        '''Show the minimize and restore icons.'''

        if self.isMaximized():
            return

        item1 = self._layout.itemAtPosition(0, self._state1_column)
        item2 = self._layout.itemAtPosition(0, self._state2_column)
        self._layout.removeItem(item1)
        self._layout.removeItem(item2)
        self._layout.addWidget(self._min, 0, self._state1_column)
        self._layout.addWidget(self._restore, 0, self._state2_column)
        self._max.hide()
        self._min.show()
        self._restore.show()

    def set_restored(self):
        '''Show the minimize and maximize icons.'''

        if self.isNormal():
            return

        item1 = self._layout.itemAtPosition(0, self._state1_column)
        item2 = self._layout.itemAtPosition(0, self._state2_column)
        self._layout.removeItem(item1)
        self._layout.removeItem(item2)
        self._layout.addWidget(self._min, 0, self._state1_column)
        self._layout.addWidget(self._max, 0, self._state2_column)
        self._restore.hide()
        self._min.show()
        self._max.show()

    def set_shaded(self):
        '''Show the unshade icon (and hide the shade icon).'''

        if self._has_shade and not (self.isMinimized() or self._is_shaded):
            self._layout.replaceWidget(self._shade, self._unshade)
            self._shade.hide()
            self._unshade.show()

    def set_unshaded(self):
        '''Show the shade icon (and hide the unshade icon).'''

        if self._has_shade and (self.isMinimized() or self._is_shaded):
            self._layout.replaceWidget(self._unshade, self._shade)
            self._unshade.hide()
            self._shade.show()

class SizeFrame(QtCore.QObject):
    '''An invisible frame for resizing events around a window.'''

    def __init__(self, window=None, border_width=3):
        super().__init__(window)

        self._window = window
        self._border_width = border_width
        self._band = QtWidgets.QRubberBand(compat.RubberBandRectangle)

        self._pressed = False
        self._cursor = None
        self._press_edge = WindowEdge.NoEdge
        self._move_edge = WindowEdge.NoEdge

        self._window.setMouseTracking(True)
        self._window.setWindowFlag(compat.FramelessWindowHint, True)
        self._window.setAttribute(compat.WA_Hover)

    @property
    def is_active(self):
        '''Get if the SizeFrame resize event is active.'''
        return self._pressed

    def is_on_top(self, pos, rect):
        '''Determine if the cursor is on the top of the widget.'''
        return (
            pos.x() >= rect.x() + self._border_width and
            pos.x() <= rect.x() + rect.width() - self._border_width and
            pos.y() >= rect.y() and
            pos.y() <= rect.y() + self._border_width
        )

    def is_on_bottom(self, pos, rect):
        '''Determine if the cursor is on the bottom of the widget.'''
        return (
            pos.x() >= rect.x() + self._border_width and
            pos.x() <= rect.x() + rect.width() - self._border_width and
            pos.y() >= rect.y() + rect.height() - self._border_width and
            pos.y() <= rect.y() + rect.height()
        )

    def is_on_left(self, pos, rect):
        '''Determine if the cursor is on the left of the widget.'''
        return (
            pos.x() >= rect.x() - self._border_width and
            pos.x() <= rect.x() + self._border_width and
            pos.y() >= rect.y() + self._border_width and
            pos.y() <= rect.y() + rect.height() - self._border_width
        )

    def is_on_right(self, pos, rect):
        '''Determine if the cursor is on the right of the widget.'''
        return (
            pos.x() >= rect.x() + rect.width() - self._border_width and
            pos.x() <= rect.x() + rect.width() and
            pos.y() >= rect.y() + self._border_width and
            pos.y() <= rect.y() + rect.height() - self._border_width
        )

    def is_on_top_left(self, pos, rect):
        '''Determine if the cursor is on the top left of the widget.'''
        return (
            pos.x() >= rect.x() and
            pos.x() <= rect.x() + self._border_width and
            pos.y() >= rect.y() and
            pos.y() <= rect.y() + self._border_width
        )

    def is_on_top_right(self, pos, rect):
        '''Determine if the cursor is on the top right of the widget.'''
        return (
            pos.x() >= rect.x() + rect.width() - self._border_width and
            pos.x() <= rect.x() + rect.width() and
            pos.y() >= rect.y() and
            pos.y() <= rect.y() + self._border_width
        )

    def is_on_bottom_left(self, pos, rect):
        '''Determine if the cursor is on the bottom left of the widget.'''
        return (
            pos.x() >= rect.x() and
            pos.x() <= rect.x() + self._border_width and
            pos.y() >= rect.y() + rect.height() - self._border_width and
            pos.y() <= rect.y() + rect.height()
        )

    def is_on_bottom_right(self, pos, rect):
        '''Determine if the cursor is on the bottom right of the widget.'''
        return (
            pos.x() >= rect.x() + rect.width() - self._border_width and
            pos.x() <= rect.x() + rect.width() and
            pos.y() >= rect.y() + rect.height() - self._border_width and
            pos.y() <= rect.y() + rect.height()
        )

    def cursor_position(self, pos, rect):
        '''Calculate the cursor position inside the window.'''

        if self.is_on_left(pos, rect):
            return WindowEdge.Left
        elif self.is_on_right(pos, rect):
            return WindowEdge.Right
        elif self.is_on_bottom(pos, rect):
            return WindowEdge.Bottom
        elif self.is_on_top(pos, rect):
            return WindowEdge.Top
        elif self.is_on_bottom_left(pos, rect):
            return WindowEdge.BottomLeft
        elif self.is_on_bottom_right(pos, rect):
            return WindowEdge.BottomRight
        elif self.is_on_top_right(pos, rect):
            return WindowEdge.TopRight
        elif self.is_on_top_left(pos, rect):
            return WindowEdge.TopLeft

        return WindowEdge.NoEdge

    def top_left(self, rect):
        '''Get the top/left position of the window in global coordinates.'''

        # Calculate the top left bounds of our window to get our frame.
        # We want our frame in global coordinates, but our window
        # might be a subwindow. If it has a parent, then it's a subwindow
        # and we need to map our coordinates.
        point = rect.topLeft()
        if self._window.window() != self._window:
            point = self._window.parent().mapToGlobal(point)

        return point

    def frame_geometry(self):
        '''Calculate the frame geometry of our window in global coordinates.'''

        rect = self._window.frameGeometry()
        return QtCore.QRect(self.top_left(rect), self._window.frameSize())

    def geometry(self):
        '''Calculate the geometry of our window in global coordinates.'''

        rect = self._window.geometry()
        return QtCore.QRect(self.top_left(rect), self._window.size())

    def update_cursor(self, position):
        '''Update the cursor shape depending on the cursor position.'''

        if self._window.isMaximized() or self._window.isFullScreen():
            self.unset_cursor()
            return

        if self._pressed:
            return

        rect = self.frame_geometry()
        self._move_edge = self.cursor_position(position, rect)
        if self._move_edge == WindowEdge.NoEdge:
            self.unset_cursor()
            return
        if self._move_edge in (WindowEdge.Top, WindowEdge.Bottom):
            self._cursor = compat.SizeVerCursor
        elif self._move_edge in (WindowEdge.Left, WindowEdge.Right):
            self._cursor = compat.SizeHorCursor
        elif self._move_edge in (WindowEdge.TopLeft, WindowEdge.BottomRight):
            self._cursor = compat.SizeFDiagCursor
        elif self._move_edge in (WindowEdge.TopRight, WindowEdge.BottomLeft):
            self._cursor = compat.SizeBDiagCursor

        self._window.setCursor(self._cursor)

    def resize(self, position, rect):
        '''Resize our window to the adjusted dimensions.'''

        # Get our new frame dimensions.
        if self._press_edge == WindowEdge.NoEdge:
            return
        elif self._press_edge == WindowEdge.Top:
            rect.setTop(position.y())
        elif self._press_edge == WindowEdge.Bottom:
            rect.setBottom(position.y())
        elif self._press_edge == WindowEdge.Left:
            rect.setLeft(position.x())
        elif self._press_edge == WindowEdge.Right:
            rect.setRight(position.x())
        elif self._press_edge == WindowEdge.TopLeft:
            rect.setTopLeft(position)
        elif self._press_edge == WindowEdge.TopRight:
            rect.setTopRight(position)
        elif self._press_edge == WindowEdge.BottomLeft:
            rect.setBottomLeft(position)
        elif self._press_edge == WindowEdge.BottomRight:
            rect.setBottomRight(position)

        # Ensure we don't drag the widgets if we go below min sizes.
        if rect.width() < self._window.minimumWidth():
            if self._press_edge in LEFT_EDGES:
                rect.setLeft(rect.right() - self._window.minimumWidth())
            elif self._press_edge in RIGHT_EDGES:
                rect.setRight(rect.left() + self._window.minimumWidth())
        if rect.height() < self._window.minimumHeight():
            if self._press_edge in TOP_EDGES:
                rect.setTop(rect.bottom() - self._window.minimumHeight())
            elif self._press_edge in BOTTOM_EDGES:
                rect.setBottom(rect.top() + self._window.minimumHeight())

        # Calculate our rect for our widget.
        size = rect.size()
        point = rect.topLeft()
        if self._window.window() != self._window:
            point = self._window.parent().mapFromGlobal(point)
        local_rect = QtCore.QRect(point, size)

        # If we have a subwindow, need to limit to the MDI area rect.
        if self._window.window() != self._window:
            area_rect = self._window.mdiArea().contentsRect()
            # Need to calculate our shifts here.
            dx1 = max(local_rect.left(), area_rect.left()) - local_rect.left()
            dy1 = max(local_rect.top(), area_rect.top()) - local_rect.top()
            dx2 = min(local_rect.right(), area_rect.right()) - local_rect.right()
            dy2 = min(local_rect.bottom(), area_rect.bottom()) - local_rect.bottom()
            rect.adjust(dx1, dy1, dx2, dy2)
            # NOTE: Do not remove this. I have tried everything.
            # This does not work unless you keep it. There's a weird
            # bug where the window (only for QMdiSubWindow) now has
            # a bug where if you click on the title bar, it re-enters
            # a resize mode, which is independent of this. Shifting
            # the position by 1 pixel undoes this. Nothing else works,
            # and I have tried:
            #   - Not due to custom drag/move/resize/frame states.
            #   - Not due to lingering pressed/press_edge/move_edge.
            #   - Not due to a lingering cursor.
            #   - Not due to change event.
            #   - Not due to resize/show event.
            #   - Not due to mouse press/double click/release/move event.
            #   - Not due to the event filter.
            #   - Not due to the QMainWindow-level custom title bar.
            #   - `setFixedSize` on the window on the mouse release
            #       and then undoing on the next resize event eats
            #       the mouse click, but still enters the same mode
            #       (just the window can't be resized).
            #   - Not due to the window-level widgets or margins.
            #   - `setFixedSize` on the title bar just fixes title bar size.
            #   - No previous versions work if we use the local_rect.
            #   - Unsetting the band and use the window directly does nothing.
            #   - Using `setGeometry(rect)` then `setGeometry(local_rect)`.
            #   - Unsetting the band geometry in `mouse_release` event.
            #   - Simulating mouse press+release in `mouse_release`.
            #   - Simulating mouse press+release in `end_frame`.
            #   - Ignoring the subsequent mousePressEvent on the title bar.
            #       - This causes the window to disappear entirely.
            #   - Hide+show inside `mouse_release` causes window to hide.
            #   - Hide+show inside `end_frame` causes window to hide.
            #   - Not due to minimum rect size checks.
            #   - Not due to MDI area limit checks.
            #   - Not related to custom restore/min/max/shade/unshade code.
            #   - Not due to custom hide/setVisible overrides.
            #
            # This is almost certain a bug in QMdiArea, but this is a
            # workaround that produces almost is almost imperceptible,
            # since the widget is being actively resized.
            #
            # I love mess.... but not this.
            if dx1 == 0 and dy1 == 0 and dx2 == 0 and dy2 == 0:
                dx1 += 1
                dy1 += 1
                dx2 += 1
                dy2 += 1
            local_rect.adjust(dx1, dy1, dx2, dy2)

        self._window.set_geometry(local_rect)
        self._band.setGeometry(rect)

    def unset_cursor(self):
        '''Unset the custom cursor.'''

        if self._cursor:
            self._window.unsetCursor()
        self._cursor = None

    def enter(self, event):
        '''Handle the enterEvent of the window.'''

        position = shared.single_point_position(args, event)
        self.update_cursor(self._window.mapToGlobal(position))

    def leave(self, event):
        '''Handle the leaveEvent of the window.'''

        if not self._pressed:
            self.unset_cursor()

    def mouse_move(self, event):
        '''Handle the mouseMoveEvent of the window.'''

        position = shared.single_point_global_position(args, event)
        if not self._pressed:
            self.update_cursor(position)
            return

        self.resize(position, self._band.geometry())

    def mouse_press(self, event):
        '''Handle the mousePressEvent of the window.'''

        if event.button() == compat.LeftButton:
            position = shared.single_point_global_position(args, event)
            rect = self.frame_geometry()
            self._press_edge = self.cursor_position(position, rect)
            # We want to separately handle drags, so only
            # set this if we are pressing on the edge.
            if self._press_edge != WindowEdge.NoEdge:
                self._pressed = True
                self._band.setGeometry(self.geometry())

    def mouse_release(self, event):
        '''Handle the mouseReleaseEvent of the window.'''

        if event.button() == compat.LeftButton and self._pressed:
            self._pressed = False

    def hover_move(self, event):
        '''Handle the hoverMoveEvent of the window.'''

        position = shared.single_point_position(args, event)
        self.update_cursor(self._window.mapToGlobal(position))

class SubWindow(QtWidgets.QMdiSubWindow):
    '''Base subclass for a QMdiSubwindow.'''

    def __init__(self, parent=None, flags=QtCore.Qt.WindowType(0)):
        super().__init__(parent, flags=flags)
        super().setWidget(QtWidgets.QWidget())

class DefaultSubWindow(SubWindow):
    '''Default subwindow with a window frame.'''

    def __init__(
        self,
        parent=None,
        flags=QtCore.Qt.WindowType(0),
        sizegrip=False,
    ):
        super().__init__(parent, flags=flags)

class FramelessSubWindow(SubWindow):
    '''Custom subwindow instance without a window frame.'''

    def __init__(
        self,
        parent=None,
        flags=QtCore.Qt.WindowType(0),
        sizegrip=False,
    ):
        super().__init__(parent, flags=flags | compat.FramelessWindowHint)

        # Create our widgets. Sizeframe and sizegrip are mutually exclusive.
        self._central = QtWidgets.QFrame(super().widget())
        self._central.setLayout(QtWidgets.QVBoxLayout())
        self._titlebar = TitleBar(self, self._central, flags)
        self._widget = QtWidgets.QWidget(self._central)
        self._widget.setLayout(QtWidgets.QVBoxLayout())
        self._sizeframe = None
        self._sizegrip = None
        self._border = args.border_width
        self._titlebar_size = QtCore.QSize()
        self._sizegrip_size = QtCore.QSize()
        self._old_minimum_size = None
        if sizegrip:
            self._sizegrip = QtWidgets.QSizeGrip(self._central)
        else:
            self._sizeframe = SizeFrame(self, border_width=5)

        # Add our titlebar, then our central widgets, etc.
        # Make sure we have our titlebar compacted to fit.
        # The trick here is quite simple: have no spacing
        # on the parent layout (so the titlebar goes on the
        # absolute top), and all 3 widgets, with the main
        # widget expanding to the view, and make it seem like
        # it's the central widget for the layout, as is its layout.

        # Align the size grip to the bottom right, without stretch, so
        # it compacts and has the natural placement. For the titlebar,
        # align it top so when the sizegrip is hidden (as is the widget),
        # it does not have a border/padding on the top.
        bottom_right = compat.AlignBottom | compat.AlignRight
        super().layout().setSpacing(0)
        super().layout().addWidget(self._central, 10)
        self._central.layout().setSpacing(0)
        self._central.layout().addWidget(self._titlebar, 0, compat.AlignTop)
        self._central.layout().addWidget(self._widget, 10)
        if self._sizegrip is not None:
            self._central.layout().addWidget(self._sizegrip, 0, bottom_right)

        # Set the border properties.
        self._central.layout().setContentsMargins(QtCore.QMargins(0, 0, 0, 0))
        self._central.setProperty('isWindow', True)
        if self._border > 0:
            self._central.setProperty('windowFrame', min(self._border, 5))
            self._central.setFrameShape(compat.Box)
            self._central.setFrameShadow(compat.Raised)

        # Ensure our titlebar gets highest priority.
        self._titlebar.raise_()
        self._widget.lower()

    # PROPERTIES

    @property
    def border_size(self):
        '''Get the size of the border, regardless if present.'''
        return border_size(self)

    @property
    def minimized_content_size(self):
        '''Get the minimum content size of the widget.'''
        return minimized_content_size(self)

    @property
    def minimized_size(self):
        '''Get the minimum size of the widget, with the size grips hidden.'''
        return minimized_size(self)

    @property
    def minimum_size(self):
        '''Get the minimum size for the widget.'''
        return minimum_size(self)

    @property
    def maximum_size(self):
        '''Get the maximum size for the widget.'''
        return self.mdiArea().size()

    # RESIZE

    def move_to(self, position):
        '''Move the window to the desired position'''
        move_to(self, position)

    def set_geometry(self, rect):
        '''Set the window geometry.'''
        set_geometry(self, rect)

    def set_minimum_size(self):
        '''Sets the minimum size of the window and the titlebar, with clobbering.'''
        set_minimum_size(self)

    def set_larger_minimum_size(self):
        '''Sets the minimum size of the window and the titlebar, without clobbering.'''
        set_larger_minimum_size(self)

    def minimize(self, size):
        '''Minimize the window, hiding the main widget and size grip.'''

        self._widget.hide()
        if self._sizegrip is not None:
            self._sizegrip.hide()
        self.set_minimum_size()
        self.resize(size)
        self.mdiArea().minimize(self)

    def maximize(self, rect):
        '''Maximize the window, showing the main widget and hiding size grip.'''

        self._widget.show()
        if self._sizegrip is not None:
            self._sizegrip.hide()
        self.set_larger_minimum_size()
        self.set_geometry(rect)

    def restore(self, rect):
        '''Restore the window, showing the main widget and size grip.'''

        self._widget.show()
        if self._sizegrip is not None:
            self._sizegrip.show()
        self.set_larger_minimum_size()
        self.set_geometry(rect)

    def shade(self, size):
        '''Shade the window, hiding the main widget and size grip.'''
        shade(self, size, 'sizegrip')

    def unshade(self, rect):
        '''Unshade the window, showing the main widget and size grip.'''
        unshade(self, rect, 'sizegrip')

    def unminimize(self):
        '''Unminimize a minimized subwindow.'''
        self.mdiArea().unminimize(self)

    # QT EVENTS

    def resizeEvent(self, event):
        '''Handle widget resize events here.'''
        window_resize_event(self, event)

    def showEvent(self, event):
        '''Set the minimum size policies once the widgets are shown.'''
        window_show_event(self, event, 'sizegrip')

    def mouseDoubleClickEvent(self, event):
        '''Override the mouse double click, and don't call the press event.'''
        window_mouse_double_click_event(self, event)

    def mousePressEvent(self, event):
        '''Override a mouse click on the titlebar to allow a move.'''
        return window_mouse_press_event(self, event, self.window(), 'subwindow')

    def mouseMoveEvent(self, event):
        '''Reposition the window on the move event.'''
        return window_mouse_move_event(self, event, self.window(), 'subwindow')

    def mouseReleaseEvent(self, event):
        '''End the drag event.'''
        return window_mouse_release_event(self, event, self.window(), 'subwindow')

    # QT-LIKE PROPERTIES

    def windowTitle(self):
        '''Get the window title from the titlebar.'''
        return self._titlebar.windowTitle()

    def setWindowTitle(self, title):
        '''Get the window title from the titlebar.'''
        self._titlebar.setWindowTitle(title)

    def layout(self):
        '''Get the subwindow layout (mapped to self._widget)'''
        return self._widget.layout()

    def setLayout(self, layout):
        '''Set the subwindow layout (mapped to self._widget)'''
        self._widget.setLayout(layout)

    def widget(self):
        '''Get the subwindow widget (mapped to self._widget)'''
        return self._widget

    def setWidget(self, widget):
        '''Set the subwindow widget (mapped to self._widget)'''

        super().layout().replaceWidget(self._widget, widget)
        self._widget = widget

    def isMinimized(self):
        '''Overload since we use a custom minimized for our subwindow.'''
        return self._titlebar.isMinimized()

    def isMaximized(self):
        '''Overload since we use a custom maximized for our subwindow.'''
        return self._titlebar.isMaximized()

class MdiArea(QtWidgets.QMdiArea):
    '''Override the QMdiArea for window minimization and background color.'''

    def __init__(self, parent=None, location=MINIMIZE_LOCATION):
        super().__init__(parent)
        self._minimized = []
        self._location = location
        self._timer = QtCore.QTimer()
        self._timer.setSingleShot(True)
        self._timer.timeout.connect(self.move_minimized)

        # Set the background color
        background = self.background()
        background.setColor(colors.ViewBackground)
        self.setBackground(background)

    def resizeEvent(self, event):
        '''Handle moving minimized windows without glitchy motion.'''

        self._timer.start(REPAINT_TIMER)
        super().resizeEvent(event)

    def minimize(self, subwindow):
        '''Minimize a subwindow and reposition it.'''

        self._minimized.append(subwindow)
        self.move_minimized()

    def unminimize(self, subwindow):
        '''Unminimize a subwindow.'''

        self._minimized.remove(subwindow)
        self.move_minimized()

    def move_minimized(self):
        '''Move the minimized windows.'''

        # No need to set the geometry of our minimized windows.
        if not self._minimized:
            return

        # Get the geometry of the elements, and calculate the windows per row.
        window = self._minimized[0]
        has_border = any(i._border for i in self._minimized)
        size = window.minimized_content_size
        if has_border:
            size = size + window.border_size
        width = size.width()
        height = size.height()
        width += max(int(0.01 * width), 1)
        height += max(int(0.01 * height), 1)
        total_size = self.size()
        minimized_count = len(self._minimized)
        row_count = max(total_size.width() // width, 1)
        rows = minimized_count // row_count
        if minimized_count % row_count != 0:
            rows += 1

        # Get how we shift our elements. Start our elements so
        # the first iteration will shift them into place.
        # We never want to place elements at a negative index,
        # so our right always starts at least at 1.
        # For our bottom, we want the last element to be placed at (_, 0)
        # if it would overflow to the top, we place it at 0 instead.
        left_x = 0
        right_x = max(total_size.width() - width, 0)
        top_y = 0
        bottom_y = max(total_size.height() - height, (rows - 1) * height)
        if self._location == MinimizeLocation.TopLeft:
            point = QtCore.QPoint(left_x, top_y)
            new_column = lambda p: QtCore.QPoint(left_x, p.y() + height)
            shift_row = lambda p, w: p + QtCore.QPoint(w, 0)
        elif self._location == MinimizeLocation.TopRight:
            point = QtCore.QPoint(right_x, top_y)
            new_column = lambda p: QtCore.QPoint(right_x, p.y() + height)
            shift_row = lambda p, w: p - QtCore.QPoint(w, 0)
        elif self._location == MinimizeLocation.BottomLeft:
            point = QtCore.QPoint(left_x, bottom_y)
            new_column = lambda p: QtCore.QPoint(left_x, p.y() - height)
            shift_row = lambda p, w: p + QtCore.QPoint(w, 0)
        else:
            point = QtCore.QPoint(right_x, bottom_y)
            new_column = lambda p: QtCore.QPoint(right_x, p.y() - height)
            shift_row = lambda p, w: p - QtCore.QPoint(w, 0)

        # Now, need to place them accordingly.
        # Need to handle unshifts, if they occur, due to the
        for index in range(len(self._minimized)):
            # Calculate our new column, only storing if it is a new column.
            window = self._minimized[index]
            is_new_column = index % row_count == 0
            if index != 0 and is_new_column:
                point = new_column(point)

            window.move(point)
            point = shift_row(point, width)

class Window(QtWidgets.QMainWindow):
    '''Base subclass for a QMainWindow.'''

    def __init__(self, parent=None, flags=QtCore.Qt.WindowType(0)):
        super().__init__(parent, flags)

        # Tracking for move and resize events.
        # Click and drag title bar move.
        self._subwindow_drag = None
        # Context menu move.
        self._subwindow_move = None
        # Context menu resize.
        self._subwindow_resize = None
        # SizeFrame resize.
        self._subwindow_frame = None

    def setup(self):
        '''Setup the main UI.'''

        subwindow_class = FramelessSubWindow
        if args.default_window_frame:
            subwindow_class = DefaultSubWindow

        self.resize(1068, 824)
        self.setWindowTitle('Custom SubWindow Style.')

        flags = compat.SubWindow
        self.area = MdiArea(self._widget)
        self.window1 = subwindow_class(flags=flags, sizegrip=True)
        self.window1.setWindowTitle('Short Title')
        self.area.addSubWindow(self.window1)
        self.table = LargeTable(self.window1.widget())
        self.window1.layout().addWidget(self.table)

        flags = compat.SubWindow
        flags |= compat.WindowContextHelpButtonHint
        flags |= compat.WindowShadeButtonHint
        self.window2 = subwindow_class(flags=flags)
        self.window2.setWindowTitle('Example of a very, very long title')
        self.area.addSubWindow(self.window2)
        self.tree = SortableTree(self.window2.widget())
        self.window2.layout().addWidget(self.tree)

        flags = compat.SubWindow
        flags |= compat.WindowShadeButtonHint
        self.window3 = subwindow_class(flags=flags, sizegrip=True)
        self.window3.setWindowTitle('Medium length title')
        self.area.addSubWindow(self.window3)
        self._widget.layout().addWidget(self.area)
        self.tab = SettingTabs(self.window3.widget())
        self.window3.layout().addWidget(self.tab)

    # PROPERTIES

    @property
    def maximum_size(self):
        '''Get the maximum size for the window.'''
        # Unused since we use the window flags anyway.
        return self.maximumSize()

    # ACTIONS

    def menu_size_to(self, point):
        '''
        Size the window so that the position is in the center bottom
        of the title bar. The position is given in global coordinates.
        '''

        window = getattr(self, '_window_resize', None)
        if window is None:
            window = self._subwindow_resize
        rect = window.geometry()
        # We add a trivial amount so we avoid a bug where we are
        # exactly on the sizegrip, which keeps us in resize mode
        # unless we do another button click, we weirdly doesn't
        # work well when simulated.
        point += QtCore.QPoint(2, 2)

        # If we have a subwindow, need to limit to the MDI area rect.
        if window.window() != window:
            point = window.parent().mapFromGlobal(point)
            area_rect = window.mdiArea().contentsRect()
            point.setX(min(point.x(), area_rect.right()))
            point.setY(min(point.y(), area_rect.bottom()))

        # Need to ensure we didn't go past the top left.
        # Don't want to shift to negative values.
        top_left = rect.topLeft()
        point.setX(max(top_left.x(), point.x()))
        point.setY(max(top_left.y(), point.y()))

        # We add a trivial amount to simplify growing the window on Wayland.
        # Wayland cannot track outside of the application.
        if IS_TRUE_WAYLAND and window.window() == window:
            point += QtCore.QPoint(16, 16)
        rect.setBottomRight(point)
        window.set_geometry(rect)

        # Ensure we trigger the elide resize timer.
        titlebar = window._titlebar
        titlebar._title._timer.start(REPAINT_TIMER)

    def resolve_state(self):
        '''Handle theoretically possible conflicts in window state.'''

        # The _drag, _move, _resize, and _frame options are
        # mutually exclusive: only one can be active at a given time.
        # Since we use timers for `_move` and `_resize`, it's **possible**
        # multiple might be active here, but it's unlikely. So, we handle
        # those cases by playing favorites. _frame > _resize > _move > _drag.
        # We deal with the window-level widgets first, then the subwindow-level
        # widgets next. We use `getattr(obj, attr, None)` for the window-level
        # widgets since they might not be present (if using Wayland).

        has_state = False
        if has_state or getattr(self, f'_window_frame', None) is not None:
            end_resize(self, 'window')
            has_state = True
        if has_state or getattr(self, f'_window_resize', None) is not None:
            end_move(self, 'window')
            has_state = True
        if has_state or getattr(self, f'_window_move', None) is not None:
            end_drag(self, 'window')
            has_state = True
        if has_state or getattr(self, f'_window_drag', None) is not None:
            end_frame(self, 'window')
            has_state = True
        if has_state or self._subwindow_frame is not None:
            end_resize(self, 'subwindow')
            has_state = True
        if has_state or self._subwindow_resize is not None:
            end_move(self, 'subwindow')
            has_state = True
        if has_state or self._subwindow_move is not None:
            end_drag(self, 'subwindow')
            has_state = True

    def move_event(self, _, event, window_type):
        '''Handle window move events.'''

        if event.type() == compat.MouseMove:
            position = shared.single_point_global_position(args, event)
            handle_move(self, position, window_type)
        elif event.type() == compat.MouseButtonPress:
            end_move(self, window_type)

    def resize_event(self, obj, event, window_type):
        '''Handle window resize events.'''

        # NOTE: If we're on Wayland, we cant' track hover events outside the
        # main widget, and we can't guess intermittently since if the mouse
        # doesn't move, we won't get an `Enter` or `HoverEnter` event, and
        # `QCursor::pos` will always be the same. What this means is we
        # can't guess where we left the, and resize until we're back
        # in the bounds.
        if event.type() in (compat.MouseMove, compat.HoverMove):
            position = shared.single_point_global_position(args, event)
            handle_resize(self, position)
        elif event.type() == compat.MouseButtonPress:
            end_resize(self, window_type)

    def frame_event(self, event, frame):
        '''Handle size adjustments using the window frame.'''

        # No position for the event: we don't use it.
        if event.type() in (compat.Enter, compat.HoverEnter):
            frame.enter(event)
        elif event.type() in (compat.Leave, compat.HoverLeave):
            frame.leave(event)
        elif event.type() == compat.MouseMove:
            frame.mouse_move(event)
        elif event.type() == compat.MouseButtonPress:
            frame.mouse_press(event)
        elif event.type() == compat.MouseButtonRelease:
            frame.mouse_release(event)
        elif event.type() == compat.HoverMove:
            frame.hover_move(event)

    # QT EVENTS

    def eventFilter(self, obj, event):
        '''Custom event filter to handle move and resize events.'''

        self.resolve_state()
        if getattr(self, '_window_move', None) is not None:
            # Cannot occur while the size frame is active.
            self.move_event(obj, event, 'window')
        elif getattr(self, '_window_resize', None) is not None:
            self.resize_event(obj, event, 'window')
        elif isinstance(obj, Window) and not obj.isMinimized():
            handle_frame(self, obj, event, 'window')
        elif self._subwindow_move is not None:
            # Cannot occur while the size frame is active.
            self.move_event(obj, event, 'subwindow')
        elif self._subwindow_resize is not None:
            self.resize_event(obj, event, 'subwindow')
        elif isinstance(obj, SubWindow) and not obj.isMinimized():
            handle_frame(self, obj, event, 'subwindow')

        return super().eventFilter(obj, event)

class DefaultWindow(Window):
    '''Default main window with a window frame.'''

    def __init__(self, parent=None, flags=QtCore.Qt.WindowType(0)):
        if args.window_help:
            flags |= compat.WindowContextHelpButtonHint
        if args.window_shade:
            flags |= compat.WindowShadeButtonHint
        super().__init__(parent, flags)

        self._central = QtWidgets.QFrame(self)
        self._layout = QtWidgets.QVBoxLayout(self._central)
        self.setCentralWidget(self._central)
        self._widget = QtWidgets.QWidget(self._central)
        self._widget.setLayout(QtWidgets.QVBoxLayout())
        self._central.layout().addWidget(self._widget, 10)

        if args.status_bar:
            self._statusbar = QtWidgets.QStatusBar(self._central)
            self.setStatusBar(self._statusbar)

        self.setup()

class FramelessWindow(Window):
    '''Main window with a custom event filter for all events.'''

    def __init__(self, parent=None, flags=QtCore.Qt.WindowType(0)):
        # On X11, the `WindowStaysOnTopHint` hint supposedly doesn't
        # work unless you bypass the window manager, but this seems
        # to no longer be true. There's major downsides to bypassing
        # the window manager, so it's not worth it anyway.
        flags |= compat.FramelessWindowHint
        if args.window_help:
            flags |= compat.WindowContextHelpButtonHint
        if args.window_shade:
            flags |= compat.WindowShadeButtonHint
        super().__init__(parent, flags)

        # Create our widgets. Sizeframe and sizegrip are mutually exclusive.
        self._central = QtWidgets.QFrame(self)
        self._layout = QtWidgets.QVBoxLayout(self._central)
        self.setCentralWidget(self._central)
        self._titlebar = TitleBar(self, self._central, flags)
        self._widget = QtWidgets.QWidget(self._central)
        self._widget.setLayout(QtWidgets.QVBoxLayout())
        self._sizeframe = None
        self._statusbar = None
        self._border = args.border_width
        self._titlebar_size = QtCore.QSize()
        self._statusbar_size = QtCore.QSize()
        self._old_minimum_size = None
        if args.status_bar:
            self._statusbar = QtWidgets.QStatusBar(self._central)
            self.setStatusBar(self._statusbar)
        else:
            self._sizeframe = SizeFrame(self, border_width=5)

        self._central.layout().setSpacing(0)
        self._central.layout().addWidget(self._titlebar, 0, compat.AlignTop)
        self._central.layout().addWidget(self._widget, 10)

        # Tracking for move and resize events.
        # Click and drag title bar move.
        self._window_drag = None
        # Context menu move.
        self._window_move = None
        # Context menu resize.
        self._window_resize = None
        # SizeFrame resize.
        self._window_frame = None

        # For toggling window flags, which calls `setParent`, hiding the window.
        # Since an immediate show causes an unminimize/re-minimize, this
        # causes a serious visual lag.
        self._ignore_hide = False

        # Set the border properties.
        self._central.layout().setContentsMargins(QtCore.QMargins(0, 0, 0, 0))
        self._central.setProperty('isWindow', True)
        if self._border > 0:
            self._central.setProperty('windowFrame', min(self._border, 5))
            self._central.setFrameShape(compat.Box)
            self._central.setFrameShadow(compat.Raised)

        # Ensure our titlebar gets highest priority.
        self._titlebar.raise_()
        self._widget.lower()

        self.setup()

    # HACKS

    def hide(self):
        '''Override the hide event to ignore it if desired.'''

        if self._ignore_hide:
            return
        super().hide()

    def setVisible(self, value):
        '''Override the hide event to ignore it if desired.'''

        if self._ignore_hide and not value:
            return
        super().setVisible(value)

    # PROPERTIES

    @property
    def border_size(self):
        '''Get the size of the border, regardless if present.'''
        return border_size(self)

    @property
    def minimized_content_size(self):
        '''Get the minimum content size of the widget.'''
        return minimized_content_size(self)

    @property
    def minimized_size(self):
        '''Get the minimum size of the widget, with the size grips hidden.'''
        return minimized_size(self)

    @property
    def minimum_size(self):
        '''Get the minimum size for the widget.'''
        return minimum_size(self)

    # QT-LIKE PROPERTIES

    def windowTitle(self):
        '''Get the window title from the titlebar.'''
        return self._titlebar.windowTitle()

    def setWindowTitle(self, title):
        '''Get the window title from the titlebar.'''
        self._titlebar.setWindowTitle(title)

    # RESIZE

    def move_to(self, position):
        '''Move the window to the desired position'''
        move_to(self, position)

    def set_geometry(self, rect):
        '''Set the window geometry.'''
        set_geometry(self, rect)

    def set_minimum_size(self):
        '''Sets the minimum size of the window and the titlebar, with clobbering.'''
        set_minimum_size(self)

    def set_larger_minimum_size(self):
        '''Sets the minimum size of the window and the titlebar, without clobbering.'''
        set_larger_minimum_size(self)

    def minimize(self, _):
        '''Minimize the window, using the actual OS to handle that.'''
        self.showMinimized()

    def maximize(self, _):
        '''Minimize the window, using the actual OS to handle that.'''
        self.showMaximized()

    def restore(self, _):
        '''Restore the window, showing the main widget and size grip.'''
        self.showNormal()

    def showNormal(self):
        super().showNormal()

    def shade(self, size):
        '''Shade the window, hiding the main widget and size grip.'''
        shade(self, size, 'statusbar')

    def unshade(self, rect):
        '''Unshade the window, showing the main widget and size grip.'''
        unshade(self, rect, 'statusbar')

    def unminimize(self):
        '''Unminimize a minimized window (unimplemented).'''

    # QT EVENTS

    def resizeEvent(self, event):
        '''Handle widget resize events here.'''
        window_resize_event(self, event)

    def showEvent(self, event):
        '''Set the minimum size policies once the widgets are shown.'''
        window_show_event(self, event, 'statusbar')

    def mouseDoubleClickEvent(self, event):
        '''Override the mouse double click, and don't call the press event.'''
        window_mouse_double_click_event(self, event)

    def mousePressEvent(self, event):
        '''Override a mouse click on the titlebar to allow a move.'''
        return window_mouse_press_event(self, event, self, 'window')

    def mouseMoveEvent(self, event):
        '''Reposition the window on the move event.'''
        return window_mouse_move_event(self, event, self, 'window')

    def mouseReleaseEvent(self, event):
        '''End the drag event.'''
        return window_mouse_release_event(self, event, self, 'window')

    def changeEvent(self, event):
        '''Catch state changes from outside our custom titlebar.'''

        super().changeEvent(event)

        # If we're restoring a top-level widget, need to ensure the
        # state is properly restored to the correct icons.
        if event.type() not in (compat.ActivationChange, compat.WindowStateChange):
            return

        # We have 3 states, and we can have combinations of some of them:
        #   - NoState
        #   - Minimized
        #   - Maximized
        #   - Minimized + Maximized (treat as Minimized).
        state = self.windowState()
        if state & compat.WindowMinimized:
            self._titlebar.minimize()
        elif state & compat.WindowMaximized:
            self._titlebar.maximize()
        else:
            self._titlebar.restore()

def main():
    'Application entry point'

    window_class = FramelessWindow
    # Wayland does not allow windows to reposition themselves: therefore,
    # we cannot use the custom titlebar at the application level.
    if args.default_window_frame or USE_WAYLAND_FRAME:
        window_class = DefaultWindow
    app, window = shared.setup_app(args, unknown, compat, window_class=window_class)
    app.installEventFilter(window)

    shared.set_stylesheet(args, app, compat)
    return shared.exec_app(args, app, window, compat)

if __name__ == '__main__':
    sys.exit(main())


from pyspark.sql import SparkSession, SQLContext
import pyspark.sql.functions as F
import src.lib.utils as Utils

if __name__ == "__main__":

    spark = SparkSession.builder.appName("data2insights").getOrCreate()
    sqlContext = SQLContext(spark)

    s3_npi_counts_file_location = "s3a://data2insights/zipcode/parquet/zipcode_from_npi"
    s3_uszips_file_location = "s3a://data2insights/uszips/parquet/uszips"
    s3_medicare_count_file_location = "s3a://data2insights/zipcode/parquet/medicare_count_by_zipcode"

    s3_output_file_location = "s3a://data2insights/zipcode/parquet/combined_neighborhood_master"

    df_npi_counts = sqlContext.read.parquet(s3_npi_counts_file_location)
    df_medicare_counts = sqlContext.read.parquet(s3_medicare_count_file_location)
    df_uszips = sqlContext.read.parquet(s3_uszips_file_location).withColumnRenamed(
        "zip", "zip5"
    )

    df_hcp = df_npi_counts.where(df_npi_counts.entity_type_code == 1).select(
        F.col("zip5"), F.col("count").alias("hcp_count")
    )
    df_hco = df_npi_counts.where(df_npi_counts.entity_type_code == 2).select(
        F.col("zip5"), F.col("count").alias("hco_count")
    )

    df_zip_counts = df_hcp.join(df_hco, ["zip5"], how="full")

    cols = [
        F.col("zip5").alias("zip_code"),
        F.col("state_id").alias("state"),
        F.col("lat").alias("latitude"),
        F.col("lng").alias("longitude"),
        F.col("county_fips").alias("county_fips"),
        F.col("county_name").alias("county_name"),
        F.col("hcp_count").alias("provider_count"),
        F.col("hco_count").alias("institution_count"),
        F.col("zipcode_medicare_count").alias("medicare_count")
    ]

    df_neighborhood = df_zip_counts.join(df_uszips, ["zip5"], how="full").join(df_medicare_counts, ["zip5"], how="full").select(cols)

    Utils.write_df_to_s3(df_neighborhood, s3_output_file_location)

    spark.stop()