# -*- coding: latin-1 -*-

import os
import platform
import logging

from PySide2 import QtWidgets, QtCore, QtGui

from . import constants
from . import utils
from . import core

LOG = logging.getLogger(__name__)

def newIcon():
    mayaWindow = utils.mayaMainWindow()

    dialog = NewPortalIconDialog(mayaWindow)
    dialog.show()

def openIcon():
    mayaWindow = utils.mayaMainWindow()

    dialog = OpenIconDialog(mayaWindow)
    dialog.show()

class LogoWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(LogoWidget, self).__init__(parent=parent)

        mainLayout = QtWidgets.QHBoxLayout(self)
        mainLayout.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)

        self.logo = QtWidgets.QLabel('')
        self.logo.setPixmap(utils.magicLeapLogoIcon().pixmap(296/2, 46/2))
        mainLayout.addWidget(self.logo)
        self.pressPos = None
        self.window = None

    # Support moving the window when clicking and dragging the widget.

    def setWindow(self, window):
        self.window = window

    def mousePressEvent(self, event):
        if not self.window:
            return

        self.pressPos = event.pos()
        self.isMoving = True

    def mouseMoveEvent(self, event):
        if self.window and self.isMoving:
            diff = event.pos() - self.pressPos
            self.window.move(self.window.pos()+diff)

    def mouseReleaseEvent(self, event):
        if not self.window:
            return
        self.isMoving = False

class CopyrightFooterWidget(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(CopyrightFooterWidget, self).__init__(parent=parent)

        spacer = QtWidgets.QWidget()

        self.info = QtWidgets.QLabel(text='Copyright © 2018 - 2020 Magic Leap, Inc.')

        mainLayout = QtWidgets.QVBoxLayout(self)
        mainLayout.setAlignment(QtCore.Qt.AlignRight)
        mainLayout.setContentsMargins(0, 0, 0, 0)
        mainLayout.addWidget(self.info, 0)

class NewPortalIconDialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(NewPortalIconDialog, self).__init__(parent=parent)

        # Configure the QWidget properties.
        self.setObjectName('NewPortalIconDialog')
        self.setWindowTitle('New Portal Icon')
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)

        mainLayout = QtWidgets.QVBoxLayout(self)
        mainLayout.setAlignment(QtCore.Qt.AlignTop)
        mainLayout.setContentsMargins(10, 10, 10, 10)
        mainLayout.setSpacing(5)

        self.logoWidget = LogoWidget()
        self.logoWidget.setWindow(self)
        mainLayout.addWidget(self.logoWidget)

        # Try adding a gif
        self.sampleIconGIF = QtWidgets.QLabel('')
        self.sampleIconGIFMovie = utils.samplePortalIconMovie()
        self.sampleIconGIFMovie.start()
        self.sampleIconGIF.setMovie(self.sampleIconGIFMovie)
        mainLayout.addWidget(self.sampleIconGIF)

        subNewIconLayout = QtWidgets.QHBoxLayout()
        subNewIconLayout.setContentsMargins(0, 0, 0, 0)
        mainLayout.addLayout(subNewIconLayout)

        subNewIconLayout.addStretch(1)
        subNewIconLayout.addWidget(self.sampleIconGIF, 0)

        subTextLayout = QtWidgets.QVBoxLayout()
        subTextLayout.setContentsMargins(0,0,0,0)
        subTextLayout.addStretch(1)

        self.title = QtWidgets.QLabel(text='New Portal Icon')
        self.title.setStyleSheet('font: 18pt;')
        subTextLayout.addWidget(self.title)

        self.info = QtWidgets.QLabel(text="What's a portal icon?")
        self.info.setTextFormat(QtCore.Qt.RichText);
        self.info.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction);
        self.info.setOpenExternalLinks(True)
        subTextLayout.addWidget(self.info)

        subTextLayout.addStretch(1)
        subNewIconLayout.addLayout(subTextLayout, 0)
        subNewIconLayout.addStretch(1)
        mainLayout.addLayout(subNewIconLayout)
        mainLayout.addSpacing(6)

        self.filePathField = FilePathWidget(title='Save Maya File:', displayText='', saveFile=True, getFile=False, getDirectory=False)
        mainLayout.addWidget(self.filePathField)

        self.okButton = QtWidgets.QPushButton('Create')
        self.okButton.setDefault(True);
        self.cancelButton = QtWidgets.QPushButton('Cancel')

        subWidget = QtWidgets.QWidget()
        mainLayout.addWidget(subWidget)
        subLayout = QtWidgets.QHBoxLayout(subWidget)
        subLayout.setContentsMargins(0, 0, 0, 0)

        subLayout.addWidget(self.okButton)
        subLayout.addWidget(self.cancelButton)

        mainLayout.addSpacing(12)

        copyrightWidget = CopyrightFooterWidget()
        mainLayout.addWidget(copyrightWidget)

        self.okButton.clicked.connect(self.onOK)
        self.cancelButton.clicked.connect(self.onCancel)

    def sizeHint(self):
        desktopWidget = QtWidgets.QApplication.desktop()
        geometry = desktopWidget.screenGeometry()
        return QtCore.QSize(geometry.width()/4, geometry.height()/8)

    def onOK(self):
        filePath = self.filePathField.filePath
        scene = utils.newScene(filePath)
        LOG.info(scene)

        LOG.info('Importing Icon template.')

        core.importTemplate()

        panel = PortalIconTool.run()
        panel.setupWidget.setFPS()
        panel.setupWidget.setUnits()

        self.accept()

    def onCancel(self):
        LOG.info('User cancelled creating a new Icon.')
        self.reject()

class OpenIconDialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(OpenIconDialog, self).__init__(parent=parent)

        # Configure the QWidget properties.
        self.setObjectName('OpenIconDialog')
        self.setWindowTitle('Open Icon')
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint)

        mainLayout = QtWidgets.QVBoxLayout(self)
        mainLayout.setAlignment(QtCore.Qt.AlignTop)
        mainLayout.setContentsMargins(5, 5, 5, 5)
        mainLayout.setSpacing(5)

        self.logoWidget = LogoWidget()
        mainLayout.addWidget(self.logoWidget)

        self.title = QtWidgets.QLabel(text='Open Icon')
        self.title.setStyleSheet('font: 24pt;')
        mainLayout.addWidget(self.title)

        self.info = QtWidgets.QLabel(text='Open an existing Icon created by this tool to edit.')
        mainLayout.addWidget(self.info)

        self.filePathField = FilePathWidget(title='Open Maya File:', displayText='', saveFile=False, getFile=True, getDirectory=False)
        mainLayout.addWidget(self.filePathField)

        self.okButton = QtWidgets.QPushButton('Open')
        self.cancelButton = QtWidgets.QPushButton('Cancel')

        subWidget = QtWidgets.QWidget()
        mainLayout.addWidget(subWidget)
        subLayout = QtWidgets.QHBoxLayout(subWidget)

        subLayout.addWidget(self.okButton)
        subLayout.addWidget(self.cancelButton)

        copyrightWidget = CopyrightFooterWidget()
        mainLayout.addWidget(copyrightWidget)

        self.okButton.clicked.connect(self.onOK)
        self.cancelButton.clicked.connect(self.onCancel)

    def sizeHint(self):
        desktopWidget = QtWidgets.QApplication.desktop()
        geometry = desktopWidget.screenGeometry()
        return QtCore.QSize(geometry.width()/4, geometry.height()/8)

    def onOK(self):
        filePath = self.filePathField.filePath

        sceneOpened = utils.openScene(filePath)

        if not sceneOpened:
            return

        PortalIconTool.run()

        self.accept()

    def onCancel(self):
        LOG.info('User cancelled opening an Icon.')
        self.reject()

class PortalIconTool(QtWidgets.QWidget):
    @staticmethod
    def run():
        mayaWindow = utils.mayaMainWindow()

        if not core.iconTemplateExists():
            QtWidgets.QMessageBox.critical(mayaWindow, '', 'Icon Error\nUse the Magic Leap menu to start a new Icon or open an existing one.')
            return

        widget = PortalIconTool(parent=mayaWindow)
        widget.move(
            mayaWindow.frameGeometry().center() - QtCore.QRect(QtCore.QPoint(),
            widget.sizeHint()).center()
        )
        widget.show()

        return widget

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

        # Configure the QWidget properties.
        self.setObjectName('PortalIconToolWidget')

        self.setWindowTitle('Portal Icon')
        self.setWindowFlags(self.windowFlags() | QtCore.Qt.Window)
        if utils.isMacOS():
            # MacOS is special, and the QtCore.Qt.Window flag does not sort the windows properly,
            # so instead QtCore.Qt.Tool is used.
            self.setWindowFlags(self.windowFlags() | QtCore.Qt.Tool)

        logoWidget = LogoWidget()
        self.setupWidget = IconSetupWidget(self)
        self.materialsWidget = IconMaterialsWidget(self)
        self.animationsWidget = AnimationWidget(self)
        self.finalizeWidget = IconFinalizeWidget(self)
        self.previewWidget = IconPreviewWidget(self)
        copyrightWidget = CopyrightFooterWidget()

        # Setup root QLayout object, all children widgets will be children of this QLayout.
        main_layout = QtWidgets.QVBoxLayout(self)
        main_layout.setAlignment(QtCore.Qt.AlignTop)
        main_layout.setContentsMargins(5, 5, 5, 5)
        main_layout.setSpacing(5)

        main_layout.addWidget(logoWidget)
        main_layout.addWidget(self.setupWidget)
        main_layout.addWidget(self.materialsWidget)
        main_layout.addWidget(self.animationsWidget)
        main_layout.addWidget(self.finalizeWidget)
        main_layout.addWidget(self.previewWidget)
        main_layout.addStretch(1)
        main_layout.addWidget(copyrightWidget)

    def sizeHint(self):
        return QtCore.QSize(350, 500)

class FilePathWidget(QtWidgets.QWidget):
    filePathChanged = QtCore.Signal(str)

    def __init__(self, title, displayText, getDirectory=True, getFile=False, saveFile=False, parent=None):
        super(FilePathWidget, self).__init__(parent=parent)

        self._getDirectory = getDirectory
        self._getFile = getFile
        self._saveFile = saveFile
        self._title = title

        main_layout = QtWidgets.QVBoxLayout(self)
        main_layout.setAlignment(QtCore.Qt.AlignTop)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(1)

        main_layout.addWidget(QtWidgets.QLabel(title))

        sub_layout = QtWidgets.QHBoxLayout()
        sub_layout.setContentsMargins(0, 0, 0, 0)
        sub_layout.setSpacing(5)

        main_layout.addLayout(sub_layout)

        self.outputField = QtWidgets.QLineEdit()
        self.outputField.setMinimumHeight(24)
        self.outputField.setPlaceholderText(displayText)
        sub_layout.addWidget(self.outputField)

        self.openFileBrowserButton = ActionButton('...')
        self.openFileBrowserButton.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
        sub_layout.addWidget(self.openFileBrowserButton)

        self.openFileBrowserButton.clicked.connect(self.openOutputDialog)
        self.outputField.textChanged.connect(self.filePathChanged.emit)

    def openOutputDialog(self):
        if self._getDirectory:
            result = QtWidgets.QFileDialog.getExistingDirectory(self, self._title, utils.getProjectDirectory())
        elif self._getFile:
            result = QtWidgets.QFileDialog.getOpenFileName(self, self._title, utils.getProjectDirectory())[0]
        elif self._saveFile:
            result = QtWidgets.QFileDialog.getSaveFileName(self, self._title, utils.getProjectDirectory(), 'Maya Ascii (*.ma)')[0]

        if not result:
            return

        self.outputField.setText(result)
        self.filePathChanged.emit(self.outputField.text())

    @property
    def filePath(self):
        return self.outputField.text()

    @filePath.setter
    def filePath(self, text):
        self.outputField.setText(text)

class AnimationWidget(QtWidgets.QWidget):
    clipChanged = QtCore.Signal(object)
    statusChecked = QtCore.Signal()

    defaultStepData = dict(message='Set the animation clips', status=False, required=False)

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

        self._stepData = IconStepObject()
        self._stepData.statusChanged.connect(self.updateProgress)
        self._stepData['callback'] = self.check
        for k, v in self.defaultStepData.items():
            self._stepData[k] = v

        self.data = dict()

        main_layout = QtWidgets.QVBoxLayout(self)
        main_layout.setAlignment(QtCore.Qt.AlignTop)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(1)

        contentLayout = QtWidgets.QVBoxLayout()

        # Add editable animation clips.
        clipOffset = 1
        animData = core.getAnimationData()

        for clipName, duration in constants.ICON_ANIMATION_CLIPS:
            newClipWidget = AnimationClipWidget(clipName, clipOffset, (clipOffset + duration - 1))
            newClipWidget.playClipToggled.connect(self.stopOtherClips)
            self.data[clipName] = newClipWidget
            contentLayout.addWidget(newClipWidget)

            if clipName in animData:
                newClipWidget.clipEnabled = True
                newClipWidget.startFrame = animData[clipName]['startFrame']
                newClipWidget.endFrame = animData[clipName]['endFrame']

            clipOffset += duration + 1

        self.frameWidget = CollapseWidget(title='Setup Animations (Optional)', stepData=self._stepData.serialized, parent=parent)
        self.frameWidget.setContentLayout(contentLayout)
        main_layout.addWidget(self.frameWidget)

        for clipWidget in self.data.values():
            clipWidget.clipChanged.connect(self.updateClipData)

    def showEvent(self, event):
        self._stepData.checkStepStatus.emit()
        super(AnimationWidget, self).showEvent(event)

    def enterEvent(self, event):
        self._stepData.checkStepStatus.emit()
        super(AnimationWidget, self).enterEvent(event)

    @property
    def stepData(self):
        return self._stepData

    def stopOtherClips(self):
        for clipWidget in self.data.values():
            if clipWidget._isPlaying:
                clipWidget.playClip()

    def updateClipData(self, payload):
        core.updateClipData(payload)

        self._stepData.checkStepStatus.emit()

    def updateProgress(self, checked):
        self.frameWidget.completed.emit(checked)
        self.statusChecked.emit()

    def check(self):
        checked = False

        for clipWidget in self.data.values():
            if not clipWidget.clipEnabled:
                continue

            checked = True

            currentRange = clipWidget.endFrame - clipWidget.startFrame
            if currentRange > clipWidget.maxClipSize:
                LOG.exception('%s take requires (%s), current (%s)', clipWidget.clipName, clipWidget.maxClipSize, currentRange)
                checked = False

            clipChecked = core.checkTakeForAnimation(clipWidget.startFrame, clipWidget.endFrame)
            if not clipChecked:
                LOG.exception('%s take missing keyframe at start or end.', clipWidget.clipName)
                checked = False

        return checked

class AnimationClipWidget(QtWidgets.QWidget):
    setRangeIcon = utils.setRangeIcon()
    playIcon = utils.playIcon()
    pauseIcon = utils.pauseIcon()

    minClipFrame = 0
    maxClipFrame = 10000

    clipChanged = QtCore.Signal(object)
    playClipToggled = QtCore.Signal()

    def __init__(self, clipName, startFrame, endFrame, parent=None):
        super(AnimationClipWidget, self).__init__(parent=parent)

        self._isPlaying = False
        self._maxClipSize = endFrame - startFrame + 1

        main_layout = QtWidgets.QHBoxLayout(self)
        main_layout.setAlignment(QtCore.Qt.AlignLeft)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(2)

        self.enableClip = QtWidgets.QCheckBox('')
        self.enableClip.setToolTip('Toggle clip export.')
        main_layout.addWidget(self.enableClip)

        self.title = QtWidgets.QLineEdit('')
        self.title.setToolTip('Clip name (Not Editable). Max number of frames - {0}.'.format(self.maxClipSize))
        self.title.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
        self.title.setReadOnly(True)
        main_layout.addWidget(self.title)

        self.startField = QtWidgets.QSpinBox()
        self.startField.setToolTip('First frame of clip.')
        self.startField.setMinimum(self.minClipFrame)
        self.startField.setMaximum(self.maxClipFrame)
        self.startField.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
        self.startField.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
        main_layout.addWidget(self.startField)

        self.endField = QtWidgets.QSpinBox()
        self.endField.setToolTip('Last frame of clip.')
        self.endField.setMinimum(self.minClipFrame)
        self.endField.setMaximum(self.maxClipFrame)
        self.endField.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
        self.endField.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
        main_layout.addWidget(self.endField)

        self.playButton = QtWidgets.QPushButton(self.playIcon, '')
        self.playButton.setIconSize(QtCore.QSize(10, 10))
        self.playButton.setToolTip('Play the clip.')
        main_layout.addWidget(self.playButton)

        self.setRangeButton = QtWidgets.QPushButton()
        self.setRangeButton.setIcon(self.setRangeIcon)
        self.setRangeButton.setIconSize(QtCore.QSize(10, 10))
        self.setRangeButton.setToolTip('Set timeline to clip start/end frames.')
        main_layout.addWidget(self.setRangeButton)

        # Set default values.
        self.title.setText(clipName)
        self.startField.setValue(startFrame)
        self.endField.setValue(endFrame)

        # Hook up signals.
        self.enableClip.stateChanged.connect(self.updateClipData)
        self.startField.valueChanged.connect(self.updateClipData)
        self.endField.valueChanged.connect(self.updateClipData)
        self.setRangeButton.clicked.connect(self.setActiveClip)
        self.playButton.clicked.connect(self.playClip)

    def paintEvent(self, event):
        if self.clipSize > self.maxClipSize:
            widgetPalette = self.title.palette()
            widgetPalette.setColor(QtGui.QPalette.Base, QtCore.Qt.red)
            self.title.setPalette(widgetPalette)
        else:
            widgetPalette = self.title.palette()
            widgetPalette.setColor(QtGui.QPalette.Base, QtCore.Qt.transparent)
            self.title.setPalette(widgetPalette)

        super(AnimationClipWidget, self).paintEvent(event)

    @property
    def clipName(self):
        return self.title.text()

    @property
    def maxClipSize(self):
        return self._maxClipSize

    @property
    def clipSize(self):
        return self.endFrame - self.startFrame + 1

    @property
    def clipEnabled(self):
        return self.enableClip.isChecked()

    @clipEnabled.setter
    def clipEnabled(self, checked):
        self.enableClip.setChecked(checked)

    @property
    def startFrame(self):
        return self.startField.value()

    @startFrame.setter
    def startFrame(self, value):
        self.startField.setValue(value)

    @property
    def endFrame(self):
        return self.endField.value()

    @endFrame.setter
    def endFrame(self, value):
        self.endField.setValue(value)

    @property
    def payload(self):
        return {
            'name': self.clipName,
            'enabled': self.clipEnabled,
            'startFrame': self.startFrame,
            'endFrame': self.endFrame
        }

    def updateClipData(self, value):
        self.clipChanged.emit(self.payload)

    def playClip(self):
        if not self._isPlaying:
            self.playClipToggled.emit()

        self._isPlaying = not(self._isPlaying)

        utils.playAnimation(self.startFrame, self.endFrame, self._isPlaying)

        if self._isPlaying:
            self.playButton.setIcon(self.pauseIcon)
        else:
            self.playButton.setIcon(self.playIcon)

    def setActiveClip(self):
        utils.setAnimationTimeline(self.startFrame, self.endFrame)

class IconStepObject(QtCore.QObject):
    checkStepStatus = QtCore.Signal()
    statusChanged = QtCore.Signal(bool)

    defaultStepData = dict(message='Templated message', status=False, required=False, callback=None)

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

        self._stepData = self.defaultStepData.copy()
        self.checkStepStatus.connect(self.check)

    def __getitem__(self, key):
        return self._stepData[key]

    def __setitem__(self, key, value):
        self._stepData[key] = value

    @property
    def stepData(self):
        return self._stepData

    @property
    def serialized(self):
        return self._stepData

    def check(self):
        if self._stepData['callback'] is not None:
            checked = self._stepData['callback']()
            self._stepData['status'] = checked
            self.statusChanged.emit(checked)

class FieldWidget(QtWidgets.QWidget):
    text = 0
    dropdown = 1
    number = 2

    showError = QtCore.Signal()
    hideError = QtCore.Signal()
    toggleError = QtCore.Signal(bool)
    toolClicked = QtCore.Signal()

    def __init__(self, title, fieldType, required=False, parent=None):
        super(FieldWidget, self).__init__(parent=parent)

        self._title = title
        self._required = required
        self._fieldType = fieldType

        mainLayout = QtWidgets.QVBoxLayout(self)
        mainLayout.setAlignment(QtCore.Qt.AlignTop)
        mainLayout.setContentsMargins(0, 0, 0, 0)
        mainLayout.setSpacing(0)

        labelText = title
        if self._required:
            labelText += ' (required)'
        self.fieldLabel = QtWidgets.QLabel(labelText)
        mainLayout.addWidget(self.fieldLabel)

        subWidget = QtWidgets.QWidget()
        mainLayout.addWidget(subWidget)
        subLayout = QtWidgets.QHBoxLayout(subWidget)
        subLayout.setAlignment(QtCore.Qt.AlignLeft)
        subLayout.setContentsMargins(0, 0, 0, 0)
        subLayout.setSpacing(5)

        self.field = QtWidgets.QLineEdit()
        subLayout.addWidget(self.field, 1)

        self.toolButton = QtWidgets.QPushButton('')
        self.toolButton.setStyleSheet('QPushButton { padding: 0;margin: 0 }')
        self.toolButton.setIcon(utils.wrenchIcon())
        subLayout.addWidget(self.toolButton, 0)

        self.fieldErrorMessage = QtWidgets.QLabel()
        mainLayout.addWidget(self.fieldErrorMessage)
        # self.fieldErrorMessage.hide()

        self.toolButton.clicked.connect(self.toolClicked.emit)
        self.toggleError.connect(self.onToggleError)
        self.showError.connect(self.onShowError)
        self.hideError.connect(self.onHideError)

        self._setErrorMessageColor()

    def _setErrorMessageColor(self):
        palette = self.fieldErrorMessage.palette()
        palette.setColor(self.fieldErrorMessage.foregroundRole(), QtGui.QColor(241, 39, 66))
        self.fieldErrorMessage.setPalette(palette)

    def onToggleError(self, errored):
        if errored:
            self.showError.emit()
        else:
            self.hideError.emit()

    def onShowError(self):
        self.fieldErrorMessage.show()

    def onHideError(self):
        self.fieldErrorMessage.hide()

    def setErrorMessage(self, text):
        self.fieldErrorMessage.setText(text)

    def setDisplayOnly(self, value):
        self.field.setReadOnly(value)

class IconSetupWidget(QtWidgets.QWidget):
    statusChecked = QtCore.Signal()

    defaultStepData = dict(message='Using the Icon Template', status=False, required=True)

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

        self._stepData = IconStepObject()
        self._stepData.statusChanged.connect(self.updateProgress)
        self._stepData['callback'] = self.check
        for k, v in self.defaultStepData.items():
            self._stepData[k] = v

        main_layout = QtWidgets.QHBoxLayout(self)
        main_layout.setAlignment(QtCore.Qt.AlignTop)
        main_layout.setContentsMargins(1, 1, 1, 1)
        main_layout.setSpacing(0)

        self.unitsField = FieldWidget('Units', FieldWidget.text, required=True)
        self.unitsField.setDisplayOnly(True)
        self.unitsField.setErrorMessage('Scene units not set to Centimeters(CM)')

        self.fpsField = FieldWidget('Time', FieldWidget.text, required=True)
        self.fpsField.setDisplayOnly(True)
        self.fpsField.setErrorMessage('Scene FPS not set to 60')

        contentLayout = QtWidgets.QVBoxLayout()
        contentLayout.addWidget(self.unitsField)
        contentLayout.addWidget(self.fpsField)

        self.frameWidget = CollapseWidget(title='Configure Scene', stepData=self._stepData.serialized, parent=parent)
        self.frameWidget.setContentLayout(contentLayout)
        main_layout.addWidget(self.frameWidget)

        self.unitsField.toolClicked.connect(self.setUnits)
        self.fpsField.toolClicked.connect(self.setFPS)

    def showEvent(self, event):
        self.unitsField.field.setText(core.getUnitsValue())
        self.fpsField.field.setText(core.getFPSValue())

        self._stepData.checkStepStatus.emit()

        super(IconSetupWidget, self).showEvent(event)

    def enterEvent(self, event):
        sceneChecks = core.checkSceneSettings()

        self.unitsField.toggleError.emit(not sceneChecks['units'])

        self.fpsField.toggleError.emit(not sceneChecks['fps'])

        super(IconSetupWidget, self).enterEvent(event)

    def setUnits(self):
        core.setSceneSettings(units=True, fps=False)

        self.unitsField.field.setText(core.getUnitsValue())

        sceneChecks = core.checkSceneSettings()
        self.unitsField.toggleError.emit(not sceneChecks['units'])

        self._stepData.checkStepStatus.emit()

    def setFPS(self):
        core.setSceneSettings(units=False, fps=True)

        self.fpsField.field.setText(core.getFPSValue())

        sceneChecks = core.checkSceneSettings()
        self.fpsField.toggleError.emit(not sceneChecks['fps'])

        self._stepData.checkStepStatus.emit()

    @property
    def stepData(self):
        return self._stepData

    def updateProgress(self, checked):
        self.frameWidget.completed.emit(checked)
        self.statusChecked.emit()

    def check(self):
        sceneChecks = core.checkSceneSettings()

        return all([x for x in sceneChecks.values()])

class IconMaterialsWidget(QtWidgets.QWidget):
    statusChecked = QtCore.Signal()

    defaultStepData = dict(message='Use up to 5 materials to use across your Icon.', status=False, required=True)

    maxMaterialsForIcon = 5
    portalSkySphereMaterialCount = 1

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

        self._stepData = IconStepObject()
        self._stepData.statusChanged.connect(self.updateProgress)
        self._stepData['callback'] = self.check
        for k, v in self.defaultStepData.items():
            self._stepData[k] = v

        self._materials = []
        self._unsupportedMaterials = []
        self._data = []

        self.errorMessage = QtWidgets.QLabel('')
        self.errorMessage.hide()

        self.refreshButton = ActionButton('Refresh Material List')

        contentLayout = QtWidgets.QVBoxLayout(self)

        contentLayout.addWidget(self.refreshButton)
        contentLayout.addWidget(self.errorMessage)

        for index in range(self.maxMaterialsForIcon + self.portalSkySphereMaterialCount):
            widget = MaterialSelectorWidget()
            widget.materialUpdated.connect(self._stepData.checkStepStatus.emit)
            contentLayout.addWidget(widget)
            self._data.append(widget)

        self.frameWidget = CollapseWidget(title='Assign Materials', stepData=self._stepData.serialized, parent=parent)
        self.frameWidget.setContentLayout(contentLayout)
        self.frameWidget.expand()

        main_layout = QtWidgets.QVBoxLayout(self)
        main_layout.setAlignment(QtCore.Qt.AlignTop)
        main_layout.setContentsMargins(1, 1, 1, 1)
        main_layout.setSpacing(0)

        main_layout.addWidget(self.frameWidget)

        self.loadMaterials()
        self.populateMaterials()

        self.refreshButton.clicked.connect(self.refresh)

    def showEvent(self, event):
        self._stepData.checkStepStatus.emit()
        super(IconMaterialsWidget, self).showEvent(event)

    @property
    def stepData(self):
        return self._stepData

    @property
    def materialCount(self):
        return len(self._materials)

    @property
    def materialCountMax(self):
        return self.maxMaterialsForIcon + self.portalSkySphereMaterialCount

    def updateProgress(self, checked):
        self.frameWidget.completed.emit(checked)
        self.statusChecked.emit()

    def check(self):
        checked = True

        if self.materialCount >= self.materialCountMax or self.materialCount == 0:
            checked = False

        textures = core.getTexturesFromMaterials(self._materials)
        if len(textures) != len(self._materials):
            checked = False

        for texture in textures:
            if not core.getFilePathFromTexture(texture):
                checked = False
                break

        if self._unsupportedMaterials:
            self._setErrorMessage()
            checked = False
        else:
            self.errorMessage.hide()

        return checked

    def refresh(self):
        self.loadMaterials()

        self.populateMaterials()

        self._stepData.checkStepStatus.emit()

    def _setErrorMessage(self):
        self.errorMessage.show()
        err = 'Found unsupported material types on some meshes:'
        for mat in self._unsupportedMaterials:
            err += '\n -{0}'.format(mat)
        err += '\n Lambert, Phong, and Blinn materials are only supported.'
        self.errorMessage.setText(err)
        palette = self.errorMessage.palette()
        palette.setColor(self.errorMessage.foregroundRole(), QtGui.QColor(241, 39, 66))
        self.errorMessage.setPalette(palette)
        self.errorMessage.setMargin(5)

    def loadMaterials(self):
        modelNode = core.getModelComponentNode()
        portalNode = core.getPortalComponentNode()
        componentNodes = [modelNode, portalNode]

        if modelNode is None or portalNode is None:
            return

        materials = []
        unsupportedMaterials = []
        for node in componentNodes:
            meshes = core.getMeshesRecursive(node=node)
            mats, unsupported = core.getMaterialsForMeshes(meshes)
            materials.extend(mats)
            unsupportedMaterials.extend(unsupported)

        self._materials = list(set(materials))
        self._unsupportedMaterials = list(set(unsupportedMaterials))

        LOG.info('unsupported mats on load: {0}'.format(self._unsupportedMaterials))

    def populateMaterials(self):
        self.clear()

        LOG.info('Loading materials %s', self._materials)

        for n, mat in enumerate(self._materials):
            self._data[n].loadMaterial(mat)

    def clear(self):
        for index in reversed(range(self.maxMaterialsForIcon + self.portalSkySphereMaterialCount)):
            widget = self._data[index]
            widget.materialOption.setText('Unused Material')
            widget.openMaterialButton.clearMaterial()
            widget.setEnabled(False)

class MaterialSelectorWidget(QtWidgets.QWidget):
    materialUpdated = QtCore.Signal()

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

        self._materialNodePath = None
        self.setEnabled(True)

        self.materialOption = QtWidgets.QPushButton(materialName)
        self.materialOption.setFlat(True)

        self.openMaterialButton = MaterialButton()

        main_layout = QtWidgets.QHBoxLayout(self)
        main_layout.setAlignment(QtCore.Qt.AlignRight)
        main_layout.setContentsMargins(1, 1, 1, 1)
        main_layout.setSpacing(5)

        main_layout.addWidget(self.materialOption, 1)
        main_layout.addWidget(self.openMaterialButton, 0)

        self.materialOption.clicked.connect(self.selectMaterial)
        self.openMaterialButton.materialChanged.connect(self.setMaterial)

    def setMaterial(self, textureFilePath):
        core.setColorTexture(self._materialNodePath, textureFilePath)
        self.materialUpdated.emit()
        self.materialOption.setToolTip('Material: {0}\n Texture: {1}'.format(self._materialNodePath, textureFilePath))

    def selectMaterial(self):
        core.selectNodes(self._materialNodePath)
        utils.openAttributeEditor()

    def loadMaterial(self, nodePath):
        self.setEnabled(True)
        self._materialNodePath = nodePath
        # This length value is hardcoded so it fits the default size of the widget.
        if len(nodePath) > 30:
            self.materialOption.setText(nodePath[:30] + '...')
        else:
            self.materialOption.setText(nodePath)

        texture = core.getTexture(self._materialNodePath)
        if texture:
            textureFilePath = core.getFilePathFromTexture(texture)
            if textureFilePath:
                self.openMaterialButton.setMaterial(textureFilePath)

class MaterialButton(QtWidgets.QPushButton):
    materialChanged = QtCore.Signal(str)

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

        self.setToolTip('Click to set the material color texture.')
        self.clearMaterial()

        self.clicked.connect(self.addNewTexture)

    def sizeHint(self):
        return QtCore.QSize(35, 35)

    def setMaterial(self, filePath):
        icon = QtGui.QIcon(filePath)
        self.setIcon(icon)
        self.setIconSize(self.sizeHint())

        self.materialChanged.emit(filePath)

    def clearMaterial(self):
        icon = utils.materialEmptyIcon()
        self.setIcon(icon)
        self.setIconSize(self.sizeHint())

    def addNewTexture(self):
        result = QtWidgets.QFileDialog.getOpenFileName(
            self,
            'Add Texture',
            utils.getSceneDirectory(),
            'PNG (*.png)'
        )

        if result[0]:
            self.setMaterial(result[0])

class IconPreviewWidget(QtWidgets.QWidget):
    statusChecked = QtCore.Signal()

    defaultStepData = dict(message='Preview on the ML1', status=False, required=False)

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

        self._previewed = False

        self._stepData = IconStepObject()
        self._stepData.statusChanged.connect(self.updateProgress)
        self._stepData['callback'] = self.check
        for k, v in self.defaultStepData.items():
            self._stepData[k] = v

        main_layout = QtWidgets.QHBoxLayout(self)
        main_layout.setAlignment(QtCore.Qt.AlignTop)
        main_layout.setContentsMargins(1, 1, 1, 1)
        main_layout.setSpacing(0)

        self.sdkPathWidget = FilePathWidget(title='MLSDK Path', displayText='/path/to/mlsdk')
        self.sdkPathWidget.setToolTip('Where your MLSDK version folder resides.')

        self.previewButton = QtWidgets.QPushButton('Upload to ML Device')
        self.previewButton.setToolTip('Load the Icon you created onto your Magic Leap device.')

        contentLayout = QtWidgets.QVBoxLayout()
        contentLayout.addWidget(self.sdkPathWidget)
        contentLayout.addWidget(self.previewButton)

        self.frameWidget = CollapseWidget(title='Preview On Device', stepData=self._stepData.serialized, parent=parent)
        self.frameWidget.setContentLayout(contentLayout)
        main_layout.addWidget(self.frameWidget)

        self.sdkPathWidget.filePathChanged.connect(self.saveSDKPath)
        self.previewButton.clicked.connect(self.showPreview)

    def showEvent(self, event):
        self.loadData()
        self._stepData.checkStepStatus.emit()
        super(IconPreviewWidget, self).showEvent(event)

    @property
    def stepData(self):
        return self._stepData

    def showPreview(self):
        LOG.info('Start preview Icon on device.')

        settingsData = core.getIconSettings()

        sdkPath = settingsData.get('sdkPath', None)
        if sdkPath is None:
            QtWidgets.QMessageBox.critical(self, '', 'Preview Error\nSet the `MLSDK Path` field before previewing.')
            return

        outputPath = settingsData.get('outputPath', None)
        if outputPath is None:
            QtWidgets.QMessageBox.critical(self, '', 'Preview Error\nSet the `Output Folder` field before previewing.')
            return

        core.buildIcon(validate=True, export=True, cleanup=False)

        iconZipPath = core.createIconZip()

        result = core.openIconPreviewer(iconZipPath, settingsData['sdkPath'])

        if not result:
            self._previewed = True
        else:
            self._previewed = False
            QtWidgets.QMessageBox.critical(self, 'Preview Error', result)

        self._stepData.checkStepStatus.emit()

    def saveSDKPath(self, text):
        core.saveSDKPath(text)
        self._stepData.checkStepStatus.emit()
        self.statusChecked.emit()

    def loadData(self):
        settingsData = core.getIconSettings()
        if 'sdkPath' in settingsData:
            self.sdkPathWidget.filePath = settingsData['sdkPath']

    def updateProgress(self, checked):
        self.frameWidget.completed.emit(checked)
        self.statusChecked.emit()

    def check(self):
        hasMLSDKPath = False

        if self.sdkPathWidget.filePath:
            hasMLSDKPath = True

        return all([x for x in [hasMLSDKPath, self._previewed]])

class IconFinalizeWidget(QtWidgets.QWidget):
    statusChecked = QtCore.Signal()

    defaultStepData = dict(message='Validate the Icon', status=False, required=True)

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

        self._validated = False

        self._stepData = IconStepObject()
        self._stepData.statusChanged.connect(self.updateProgress)
        self._stepData['callback'] = self.check
        for k, v in self.defaultStepData.items():
            self._stepData[k] = v

        main_layout = QtWidgets.QHBoxLayout(self)
        main_layout.setAlignment(QtCore.Qt.AlignTop)
        main_layout.setContentsMargins(1, 1, 1, 1)
        main_layout.setSpacing(0)

        self.outputWidget = FilePathWidget(title='Output Folder', displayText='/path/to/icon')
        self.outputWidget.setToolTip('Which directory the Icon will be exported to.')

        self.validateButton = ActionButton('Validate')
        self.validateButton.setToolTip('Check that the Icon you made is publish ready.')

        self.exportButton = ActionButton('Export')
        self.exportButton.setToolTip('Export the Icon to the set Output Folder.')

        contentLayout = QtWidgets.QVBoxLayout()
        contentLayout.addWidget(self.outputWidget)
        contentLayout.addWidget(self.validateButton)
        contentLayout.addWidget(self.exportButton)

        self.frameWidget = CollapseWidget(title='Validate And Export', stepData=self._stepData.serialized, parent=parent)
        self.frameWidget.setContentLayout(contentLayout)
        self.frameWidget.expand()
        main_layout.addWidget(self.frameWidget)

        self.outputWidget.filePathChanged.connect(self.saveOutputPath)
        self.validateButton.clicked.connect(self.validate)
        self.exportButton.clicked.connect(self.export)

    def showEvent(self, event):
        self.loadData()
        self._stepData.checkStepStatus.emit()
        super(IconFinalizeWidget, self).showEvent(event)

    @property
    def stepData(self):
        return self._stepData

    def saveOutputPath(self, text):
        core.saveOutputPath(text)
        self._stepData.checkStepStatus.emit()
        self.statusChecked.emit()

    def loadData(self):
        settingsData = core.getIconSettings()
        if 'outputPath' in settingsData:
            self.outputWidget.filePath = settingsData['outputPath']

    def updateProgress(self, checked):
        self.frameWidget.completed.emit(checked)
        self.statusChecked.emit()

    def check(self):
        hasOutputPath = False
        rootPathExists = False

        if self.outputWidget.filePath:
            hasOutputPath = True

        if os.path.exists(os.path.dirname(self.outputWidget.filePath)):
            rootPathExists = True

        return all([x for x in [hasOutputPath, rootPathExists, self._validated]])

    def validate(self):
        LOG.info('Start Icon validation.')

        result = core.buildIcon(validate=True, cleanup=True)
        self._validated = result

        self._stepData.checkStepStatus.emit()

        settingsData = core.getIconSettings()

        if not result:
            QtWidgets.QMessageBox.critical(self, '', 'Icon Validation Failed\nCheck the script editor for details.')
            LOG.info('Icon failed validation - {0}'.format(settingsData['outputPath']))
            return

        LOG.info('Icon passed validation - {0}'.format(settingsData['outputPath']))
        QtWidgets.QMessageBox.information(self, '', 'Icon Validation Passed\nThe Icon is ready for submission!')

    def export(self):
        if not utils.isSceneSaved():
            QtWidgets.QMessageBox.critical(self, '', 'File Not Saved\nSave your file first, and click Export again.')
            return

        LOG.info('Start Icon export.')
        result = core.buildIcon(validate=True, export=True, cleanup=False)
        self._validated = result

        self._stepData.checkStepStatus.emit()

        if not result:
            settingsData = core.getIconSettings()
            LOG.info('Icon failed to export - {0}'.format(settingsData['outputPath']))
            QtWidgets.QMessageBox.critical(self, '', 'Export Failed\nSomething went wrong!')
            return

        zipFile = core.createIconZip()
        LOG.info('Icon export Completed: %s', zipFile)
        QtWidgets.QMessageBox.information(self, '', 'Icon Export Completed\n{0}'.format(zipFile))

class CollapseWidget(QtWidgets.QWidget):
    completed = QtCore.Signal(bool)

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

        self._stepData = stepData
        self.stepStatusWidget = IconStatusStepWidget(stepData)

        self.toggleButton = QtWidgets.QToolButton(
            text=title, checkable=True, checked=False
        )
        self.toggleButton.setStyleSheet("QToolButton { border: none; }")
        self.toggleButton.setToolButtonStyle(
            QtCore.Qt.ToolButtonTextBesideIcon
        )
        self.toggleButton.setArrowType(QtCore.Qt.RightArrow)
        self.toggleButton.setChecked(False)
        self.toggleButton.released.connect(self.onReleased)

        self.contentArea = QtWidgets.QScrollArea(
            maximumHeight=0, minimumHeight=0
        )
        self.contentArea.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
        self.contentArea.setWidgetResizable(True)
        self.contentArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.contentArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.contentArea.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)
        self.contentArea.setFrameShape(QtWidgets.QFrame.Box)

        headerLayout = QtWidgets.QHBoxLayout()
        headerLayout.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter)
        headerLayout.setSpacing(0)
        headerLayout.setContentsMargins(0, 0, 0, 0)
        headerLayout.addWidget(self.stepStatusWidget)
        headerLayout.addWidget(self.toggleButton)

        lay = QtWidgets.QVBoxLayout(self)
        lay.setSpacing(0)
        lay.setContentsMargins(0, 0, 0, 0)
        lay.addLayout(headerLayout)
        lay.addWidget(self.contentArea)

        self.completed.connect(self.stepStatusWidget.setStatus)

    def expand(self):
        self.toggleButton.setChecked(True)
        self.onReleased()

    def collapse(self):
        self.toggleButton.setChecked(False)
        self.onReleased()

    def onReleased(self):
        checked = self.toggleButton.isChecked()

        if not checked:
            self.toggleButton.setArrowType(QtCore.Qt.RightArrow)
            self.contentArea.setMaximumHeight(self.collapsedHeight)
            self.setMaximumHeight(self.collapsedHeight)
            self.contentArea.setFrameShape(QtWidgets.QFrame.NoFrame)
            self.contentArea.hide()
        else:
            self.toggleButton.setArrowType(QtCore.Qt.DownArrow)
            self.contentArea.setMaximumHeight(self.contentHeight + self.collapsedHeight)
            self.setMaximumHeight(self.contentHeight + self.collapsedHeight)
            self.contentArea.setFrameShape(QtWidgets.QFrame.Box)
            self.contentArea.show()

    @property
    def collapsedHeight(self):
        return self._collapsedHeight

    @collapsedHeight.setter
    def collapsedHeight(self, value):
        self._collapsedHeight = value

    @property
    def contentHeight(self):
        return self._contentHeight

    @contentHeight.setter
    def contentHeight(self, value):
        self._contentHeight = value

    def setContentLayout(self, layout):
        oldWidget = self.contentArea.widget()
        if  oldWidget is not None:
            oldWidget.deleteLater()

        widget = QtWidgets.QWidget()
        widget.setLayout(layout)

        self.contentArea.setWidget(widget)

        self.collapsedHeight = self.sizeHint().height() - self.contentArea.maximumHeight()
        self.contentHeight = layout.sizeHint().height()

class IconStatusStepWidget(QtWidgets.QWidget):
    def __init__(self, stepData, parent=None):
        super(IconStatusStepWidget, self).__init__(parent=parent)

        self.setToolTip(stepData.get('message', ''))

        self._required = stepData.get('required', False)

        layout = QtWidgets.QHBoxLayout(self)
        layout.setAlignment(QtCore.Qt.AlignLeft)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(5)

        self.icon = QtWidgets.QLabel('')
        self.setStatus(stepData.get('status', False))
        layout.addWidget(self.icon)

    def setStatus(self, status):
        if self._required:
            if status:
                self.setStatusComplete()
            else:
                self.setStatusIncomplete()
        else:
            if status:
                self.setStatusComplete()
            else:
                self.setStatusOk()

    def setStatusComplete(self):
        iconPath = utils.mergePaths(os.path.dirname(__file__), 'resources', 'statusCompleted_icon.png')
        pixmap = QtGui.QPixmap(iconPath)
        pixmap = pixmap.scaled(16, 16, QtCore.Qt.KeepAspectRatio)
        self.icon.setPixmap(pixmap)

    def setStatusIncomplete(self):
        iconPath = utils.mergePaths(os.path.dirname(__file__), 'resources', 'statusIncompleted_icon.png')
        pixmap = QtGui.QPixmap(iconPath)
        pixmap = pixmap.scaled(16, 16, QtCore.Qt.KeepAspectRatio)
        self.icon.setPixmap(pixmap)

    def setStatusOk(self):
        iconPath = utils.mergePaths(os.path.dirname(__file__), 'resources', 'statusOk_icon.png')
        pixmap = QtGui.QPixmap(iconPath)
        pixmap = pixmap.scaled(16, 16, QtCore.Qt.KeepAspectRatio)
        self.icon.setPixmap(pixmap)

class ActionButton(QtWidgets.QPushButton):
    def __init__(self, title, parent=None):
        super(ActionButton, self).__init__(title, parent=parent)

        self.setMinimumHeight(24)
        self.setMaximumHeight(24)
        self.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed)

    def sizeHint(self):
        return QtCore.QSize(24, 24)


import logging
from contextlib import contextmanager
from typing import List

from sqlalchemy import create_engine, func, inspect, or_
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.ext.declarative.api import DeclarativeMeta
from sqlalchemy.orm import Session, scoped_session, sessionmaker
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.schema import MetaData
from sqlalchemy.sql.schema import Table

from .errors import TableAlreadyExists, TableNameNotFound, TableNotInMetadata
from .models import AnnoMetadata, Base, SegmentationMetadata, AnalysisView
from .schema import DynamicSchemaClient

class DynamicAnnotationDB:
    def __init__(self, sql_url: str, pool_size=5, max_overflow=5) -> None:

        self._cached_session = None
        self._cached_tables = {}
        self._engine = create_engine(
            sql_url, pool_recycle=3600, pool_size=pool_size, max_overflow=max_overflow
        )
        self.base = Base
        self.base.metadata.bind = self._engine
        self.base.metadata.create_all(
            tables=[AnnoMetadata.__table__, SegmentationMetadata.__table__],
            checkfirst=True,
        )

        self.session = scoped_session(
            sessionmaker(bind=self.engine, autocommit=False, autoflush=False)
        )
        self.schema_client = DynamicSchemaClient()

        self._inspector = inspect(self.engine)

        self._cached_session = None
        self._cached_tables = {}

    @property
    def inspector(self):
        return self._inspector

    @property
    def engine(self):
        return self._engine

    @property
    def cached_session(self) -> Session:
        if self._cached_session is None:
            self._cached_session = self.session()
        return self._cached_session

    @contextmanager
    def session_scope(self):
        try:
            yield self.cached_session
        except Exception as e:
            self.cached_session.rollback()
            logging.exception(f"SQL Error: {e}")
            raise e
        finally:
            self.cached_session.close()
        self._cached_session = None

    def commit_session(self):
        try:
            self.cached_session.commit()
        except Exception as e:
            self.cached_session.rollback()
            logging.exception(f"SQL Error: {e}")
            raise e
        finally:
            self.cached_session.close()
        self._cached_session = None

    def get_table_sql_metadata(self, table_name: str):
        self.base.metadata.reflect(bind=self.engine)
        return self.base.metadata.tables[table_name]

    def get_unique_string_values(self, table_name: str):
        """Get unique string values for a given table

        Parameters
        ----------
        table_name : str
            name of table contained within the aligned_volume database

        Returns
        -------
        dict
            dictionary of column names and unique values
        """
        model = self.cached_table(table_name)

        unique_values = {}
        with self.session_scope() as session:
            for column_name in model.__table__.columns.keys():

                # if the column is a string
                try:
                    python_type = model.__table__.columns[column_name].type.python_type
                except NotImplementedError:
                    python_type = None
                if python_type == str:
                    query = session.query(getattr(model, column_name)).distinct()
                    unique_values[column_name] = [row[0] for row in query.all()]
        return unique_values

    def get_views(self, datastack_name: str):
        with self.session_scope() as session:
            query = session.query(AnalysisView).filter(
                AnalysisView.datastack_name == datastack_name
            )
            return query.all()

    def get_view_metadata(self, datastack_name: str, view_name: str):
        with self.session_scope() as session:
            query = (
                session.query(AnalysisView)
                .filter(AnalysisView.table_name == view_name)
                .filter(AnalysisView.datastack_name == datastack_name)
            )
            result = query.one()
            if hasattr(result, "__dict__"):
                return self.get_automap_items(result)
            else:
                return result[0]

    def get_table_metadata(self, table_name: str, filter_col: str = None):
        data = getattr(AnnoMetadata, filter_col) if filter_col else AnnoMetadata
        with self.session_scope() as session:
            if filter_col and data:

                query = session.query(data).filter(
                    AnnoMetadata.table_name == table_name
                )
                result = query.one()

                if hasattr(result, "__dict__"):
                    return self.get_automap_items(result)
                else:
                    return result[0]
            else:
                metadata = (
                    session.query(data, SegmentationMetadata)
                    .outerjoin(
                        SegmentationMetadata,
                        AnnoMetadata.table_name
                        == SegmentationMetadata.annotation_table,
                    )
                    .filter(
                        or_(
                            AnnoMetadata.table_name == table_name,
                            SegmentationMetadata.table_name == table_name,
                        )
                    )
                    .all()
                )
                try:
                    if metadata:
                        flatted_metadata = self.flatten_join(metadata)
                        return flatted_metadata[0]
                except NoResultFound:
                    return None

    def get_table_schema(self, table_name: str) -> str:
        table_metadata = self.get_table_metadata(table_name)
        return table_metadata.get("schema_type")

    def get_valid_table_names(self) -> List[str]:
        with self.session_scope() as session:
            metadata = session.query(AnnoMetadata).all()
            return [m.table_name for m in metadata if m.valid == True]

    def get_annotation_table_size(self, table_name: str) -> int:
        """Get the number of annotations in a table

        Parameters
        ----------
        table_name : str
            name of table contained within the aligned_volume database

        Returns
        -------
        int
            number of annotations
        """
        Model = self.cached_table(table_name)
        with self.session_scope() as session:
            return session.query(Model).count()

    def get_max_id_value(self, table_name: str) -> int:
        model = self.cached_table(table_name)
        with self.session_scope() as session:
            return session.query(func.max(model.id)).scalar()

    def get_min_id_value(self, table_name: str) -> int:
        model = self.cached_table(table_name)
        with self.session_scope() as session:
            return session.query(func.min(model.id)).scalar()

    def get_table_row_count(
        self, table_name: str, filter_valid: bool = False, filter_timestamp: str = None
    ) -> int:
        """Get row counts. Optionally can filter by row validity and
        by timestamp.

        Args:
            table_name (str): Name of table
            filter_valid (bool, optional): Filter only valid rows. Defaults to False.
            filter_timestamp (None, optional): Filter rows up to timestamp . Defaults to False.

        Returns:
            int: number of rows
        """
        model = self.cached_table(table_name)
        with self.session_scope() as session:
            sql_query = session.query(func.count(model.id))
            if filter_valid:
                sql_query = sql_query.filter(model.valid == True)
            if filter_timestamp and hasattr(model, "created"):
                sql_query = sql_query.filter(model.created <= filter_timestamp)
            return sql_query.scalar()

    @staticmethod
    def get_automap_items(result):
        return {k: v for (k, v) in result.__dict__.items() if k != "_sa_instance_state"}

    def obj_to_dict(self, obj):
        if obj:
            return {
                column.key: getattr(obj, column.key)
                for column in inspect(obj).mapper.column_attrs
            }
        else:
            return {}

    def flatten_join(self, _list: List):
        return [{**self.obj_to_dict(a), **self.obj_to_dict(b)} for a, b in _list]

    def drop_table(self, table_name: str) -> bool:
        """Drop a table, actually removes it from the database
        along with segmentation tables associated with it

        Parameters
        ----------
        table_name : str
            name of table to drop

        Returns
        -------
        bool
            whether drop was successful
        """
        table = self.base.metadata.tables.get(table_name)
        if table:
            logging.info(f"Deleting {table_name} table")
            self.base.metadata.drop_all(self._engine, [table], checkfirst=True)
            if self._is_cached(table):
                del self._cached_tables[table]
            return True
        return False

    def _check_table_is_unique(self, table_name):
        existing_tables = self._get_existing_table_names()
        if table_name in existing_tables:
            raise TableAlreadyExists(
                f"Table creation failed: {table_name} already exists"
            )
        return existing_tables

    def _get_existing_table_names(self, filter_valid: bool = False) -> List[str]:
        """Collects table_names keys of existing tables

        Returns
        -------
        list
            List of table_names
        """
        with self.session_scope() as session:
            stmt = session.query(AnnoMetadata)
            if filter_valid:
                stmt = stmt.filter(AnnoMetadata.valid == True)
            metadata = stmt.all()
            return [m.table_name for m in metadata]

    def _get_model_from_table_name(self, table_name: str) -> DeclarativeMeta:
        metadata = self.get_table_metadata(table_name)

        if metadata:
            if metadata["reference_table"]:
                return self.schema_client.create_reference_annotation_model(
                    table_name,
                    metadata["schema_type"],
                    metadata["reference_table"],
                )
            elif metadata.get("annotation_table") and table_name != metadata.get(
                "annotation_table"
            ):
                return self.schema_client.create_segmentation_model(
                    metadata["annotation_table"],
                    metadata["schema_type"],
                    metadata["pcg_table_name"],
                )

            else:
                return self.schema_client.create_annotation_model(
                    table_name, metadata["schema_type"]
                )

        else:
            raise TableNotInMetadata

    def _get_model_columns(self, table_name: str) -> List[tuple]:
        """Return list of column names and types of a given table

        Parameters
        ----------
        table_name : str
            Table name in database

        Returns
        -------
        list
            column names and types
        """
        db_columns = self.inspector.get_columns(table_name)
        if not db_columns:
            raise TableNameNotFound(table_name)
        return [(column["name"], column["type"]) for column in db_columns]

    def get_view_table(self, view_name: str) -> Table:
        """Return the sqlalchemy table object for a view"""
        if self._is_cached(view_name):
            return self._cached_tables[view_name]
        else:
            meta = MetaData(self._engine)
            meta.reflect(views=True, only=[view_name])
            table = meta.tables[view_name]
            self._cached_tables[view_name] = table
            return table

    def cached_table(self, table_name: str) -> DeclarativeMeta:
        """Returns cached table 'DeclarativeMeta' callable for querying.

        Parameters
        ----------
        table_name : str
            Table name in database
        Returns
        -------
        DeclarativeMeta
            SQLAlchemy callable.
        """
        try:
            self._load_table(table_name)
            return self._cached_tables[table_name]
        except KeyError as error:
            raise TableNameNotFound(table_name) from error

    def _load_table(self, table_name: str):
        """Load existing table into cached lookup dict instance

        Parameters
        ----------
        table_name : str
            Table name to be loaded from existing database tables

        Returns
        -------
        bool
            Returns True if table exists and is loaded into cached table dict.
        """
        if self._is_cached(table_name):
            return True

        try:
            self._cached_tables[table_name] = self._get_model_from_table_name(
                table_name
            )
            return True
        except TableNotInMetadata:
            # cant find the table so lets try the slow reflection before giving up
            self.mapped_base = automap_base()
            self.mapped_base.prepare(self._engine, reflect=True)
            try:
                model = self.mapped_base.classes[table_name]
                self._cached_tables[table_name] = model
            except KeyError as table_error:
                logging.error(f"Could not load table: {table_error}")
                return False

        except Exception as table_error:
            logging.error(f"Could not load table: {table_error}")
            return False

    def _is_cached(self, table_name: str) -> bool:
        """Check if table is loaded into cached instance dict of tables

        Parameters
        ----------
        table_name : str
            Name of table to check if loaded

        Returns
        -------
        bool
            True if table is loaded else False.
        """

        return table_name in self._cached_tables


from django.test import TestCase

from faker import Faker

from api.users.models import User

from ...models import Context

class ContextModelTests(TestCase):
    def setUp(self):
        self.faker = Faker()
        self.user = User.objects.create(username=self.faker.name())

    def test_str(self):
        name = self.faker.name()
        context = Context.objects.create(
            owner=self.user,
            name=name,
        )
        self.assertEqual(str(context), name)


"""
Given an array of integers arr, return true if the number of occurrences of each value in the array
is unique, or false otherwise.

Example 1:
Input: arr = [1,2,2,1,1,3]
Output: true
Explanation: The value 1 has 3 occurrences, 2 has 2 and 3 has 1.
             No two values have the same number of occurrences.

Constraints:
1 <= arr.length <= 1000
-1000 <= arr[i] <= 1000
"""

import collections
from typing import List

class Solution:
    """
    Runtime: 40 ms, faster than 71.91% of Python3
    Memory Usage: 14.1 MB, less than 69.98% of Python3

    Time/Space complexity: O(n)
    """
    def uniqueOccurrences(self, arr: List[int]) -> bool:
        ctr = collections.Counter(arr)
        freq = ctr.values()
        # if there're non-unique counters, they will be omitted by set
        return len(freq) == len(set(freq))

if __name__ == '__main__':
    sol = Solution()
    test_cases = (
        ([1,2,2,1,1,3], True),
        ([1,2], False),
        ([-3,0,1,-3,1,1,1,-3,10,0], True))
    for inp_arr, exp_res in test_cases:
        assert sol.uniqueOccurrences(inp_arr) is exp_res


from django.shortcuts import render

def columns_view(request):
    column1_content = "Column 1 content goes here."
    column2_content = "Column 2 content goes here."
    context = {
        'column1_content': column1_content,
        'column2_content': column2_content,
    }
    return render(request, 'horizontal_columns.html', context)


import os

from flask import Blueprint, render_template, request, send_from_directory, current_app

from app.models import Post

main = Blueprint('main', __name__)

@main.route('/')
@main.route('/home')
def home():
    page = request.args.get('page', default=1, type=int)
    posts = Post.query.order_by(Post.date_posted.desc()).paginate(page=page, per_page=5)
    return render_template('home.html', posts=posts)

@main.route('/favicon.ico')
def favicon():
    return send_from_directory(os.path.join(current_app.root_path, 'static'),
                               'favicon.ico',
                               mimetype='image/vnd.microsoft.icon')


# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np

# กระดองเต่า
plt.figure(figsize=[8,4])
for j in [0,1]:
    ax = plt.axes([0.5*j,0,0.5,1],xlim=[-50,50],ylim=[-50,50],aspect=1)
    for i in range(11):
        plt.plot([-50,50],[i*20-150,i*20-50],'#a46529',lw=5,zorder=0)
        plt.plot([-50,50],[i*20-50,i*20-150],'#a46529',lw=5,zorder=0)
        plt.plot([-50,50],[i*20-150,i*20-50],'#7e4b1b',lw=2,zorder=1)
        plt.plot([-50,50],[i*20-50,i*20-150],'#7e4b1b',lw=2,zorder=1)
    plt.axis('off')
plt.savefig('langtao.png',dpi=128,facecolor='k')
plt.close()

# ใบหน้าเต่า
plt.figure(figsize=[4,4])
ax = plt.axes([1./3,1./3,1./3,1./3],xlim=[-5,5],ylim=[-5,5],aspect=1)
ax.add_patch(mpl.patches.Ellipse([-2,1.8],0.4,0.9,color='#ddeeee'))
ax.add_patch(mpl.patches.Ellipse([2,1.8],0.4,0.9,color='#ddeeee'))
theta = np.radians(np.linspace(-60,60,21))
x = np.sin(theta)*2
y = -np.cos(theta)*1.5
plt.plot(x,y,'#ddeeee',lw=3)
plt.axis('off')
plt.savefig('natao.png',dpi=128,facecolor='k')
plt.close()


import pandas as pd
from sklearn import linear_model

df = pd.read_csv("/Users/qozeemodeniran/Projects/python/w3schools/ml/data.csv")

X = df[['Weight', 'Volume']]
y = df['CO2']

regr = linear_model.LinearRegression()
regr.fit(X, y)

print(regr.coef_)


from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql:///hatchways'
app.config['SQLALCHEMY_ECHO'] = True
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
migrate = Migrate(app, db)
db.init_app(app)

@app.route("/")
def hello():
    return {"yo": "world"}

class Student(db.Model):
    __tablename__ = 'students'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String())

    marks = db.relationship('Mark', backref='students')

    def __repr__(self):
        return f"<Student {self.name}>"

class Course(db.Model):
    __tablename__ = 'courses'

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String())
    teacher = db.Column(db.String())

    tests = db.relationship('Test', backref='courses')

    def __repr__(self):
        return f"<Course {self.name}>"

class Test(db.Model):
    __tablename__ = 'tests'

    id = db.Column(db.Integer, primary_key=True)
    weight = db.Column(db.Integer())
    course_id = db.Column(db.Integer(), db.ForeignKey('courses.id'))

    marks = db.relationship('Mark', backref='tests')

class Mark(db.Model):
    __tablename__ = 'marks'

    id = db.Column(db.Integer, primary_key=True)
    mark = db.Column(db.Integer())
    test_id = db.Column(db.Integer(), db.ForeignKey('tests.id'))
    student_id = db.Column(db.Integer(), db.ForeignKey('students.id'))

if __name__ == "__main__":
    app.run(debug=True)


import pickle
import numpy as np
import matplotlib.pyplot as plt
from scipy.io import loadmat
from seawater.eos80 import ptmp
from MyNumpyTools import minmax
from MySysUtils import preall
from MyMapFunctions import haversines
from MyFunctions import central_diff_gradient
from MyInterp import smooth1d_with_holes as smooth

def cast_number_to_filename(cast_number):
    data_loc = '/home/hugke729/PhD/Data/Shipboard/CTD/avg/'
    file_name_base = '1503'
    file_in = data_loc + file_name_base + str(cast_number).zfill(3) + 'avg.cnv'
    return file_in

def read_cnv(cast_number):
    """Take cnv file for specified cast and return only the outputs I want

    Output
    ------
    Pressure (db)
    Temperature (deg C)
    Salinity (PSU)
    sigma-t (kg/m^3)"""
    file_in = cast_number_to_filename(cast_number)

    # Data always starts on line 295
    p, T, S, sigma, lat, lon = np.genfromtxt(
        file_in, skip_header=294, unpack=True, usecols=[0, 1, 2, 13, 15, 16])

    return p, T, S, sigma

def get_xyt(cast_numbers):
    """Get position, location, and depth information from summary

    Returns lat, lon, utc_time (maybe not), and maximum pressure without
    having to read all the data in"""

    data_loc = '/home/hugke729/PhD/Data/Shipboard/CTD/processed/'
    xyt_file = data_loc + 'xyt_summary.csv'

    casts, lats, lons, utc_time, depths = np.genfromtxt(
        xyt_file, skip_header=1, unpack=True, delimiter=',')
    D = {cast: (lat, lon, depth)
         for cast, lat, lon, depth in zip(casts, lats, lons, depths)}
    # Return tuple or list of types depending on what cast_numbers is
    try:
        return D[cast_numbers]
    except TypeError:
        return [D[i] for i in cast_numbers]

def ctd_map():
    m = pickle.load(open('/home/hugke729/PhD/Maps/penny_strait.pickle', 'rb'))
    m.fillcontinents(color='grey')
    casts = np.r_[92:123]
    xyts = get_xyt(casts)
    for i, (lat, lon, _, _) in enumerate(xyts):
        plt.annotate(xy=m(lon, lat), s=str(casts[i] % 100).zfill(2),
                     ha='center', fontsize=9)

def p_to_z(p, sigma):
    """Make the minor change from pressure (dbar) to depth (m)"""
    dbar_to_pa = 10000
    z = np.cumsum(1/((sigma+1000)*9.81)*central_diff_gradient(p))*dbar_to_pa
    # ...avg.cnv files all start at p = 2 db
    # for reasons I can't think how to explain, need to add 1 to z
    # To do with constant of integration in z = int dp/ρg
    z = z + 1
    return z

def T_to_theta(S, T, p):
    """Make the minor change from in situ temperature to potential temp"""
    return ptmp(S, T, p)

def get_theta_S_sigma_z(cast_number):
    """Read .cnv file, and convert p to z and T to theta"""
    p, T, S, sigma = read_cnv(cast_number)
    z = p_to_z(p, sigma)
    theta = T_to_theta(S, T, p)
    return theta, S, sigma, z

def calc_N2(sigma, z):
    """Get buoyancy frequency squared from sigma"""
    N2 = (1000+sigma)/np.mean(sigma)*central_diff_gradient(sigma, z)
    N2 = smooth(N2, 6)
    return N2

def grid_section(cast_list):
    """Put all casts in cast_list into grids for each quantity"""
    lats, lons, pmaxs = preall(len(cast_list), copies=3)
    # Create vector of positions and convert to distance
    for i, (lat, lon, _, pmax) in enumerate(get_xyt(cast_list)):
        lats[i], lons[i], pmaxs[i] = lat, lon, pmax

    # Maximum depth among all casts
    pmax_all = np.ceil(max(pmaxs)).astype(int)

    # Create 2d depth array
    dist = haversines(lons, lats)[0]
    dist = np.outer(np.ones(pmax_all), dist)

    # g for grid
    pg, Tg, Sg, sigmag = preall((pmax_all, len(cast_list)),
                                copies=4, initial_value=np.nan)

    for i, cast_number in enumerate(cast_list):
        p, T, S, sigma = read_cnv(cast_number)
        x = len(p)
        pg[:x, i], Tg[:x, i], Sg[:x, i], sigmag[:x, i] = p, T, S, sigma

    return pg, Tg, Sg, sigmag, dist

def get_velocity_profile(cast_number):
    """Read in preprocessed (I believe) lowered-ADCP data"""
    fname = ('/home/hugke729/PhD/Data/Shipboard/CTD/LADCP/Traitement/stn' +
             str(cast_number).zfill(3) + '/processed/stn' +
             str(cast_number).zfill(3) + '.mat')
    D = loadmat(fname)
    # Not sure why the indexing or the key being dr actually means,
    # but these values correspond to the plots in the CTD folder
    u = D['dr']['u'][0][0][:, 0]
    v = D['dr']['v'][0][0][:, 0]
    z = D['dr']['z'][0][0][:, 0]
    return u, v, z

def plot_cross_section(cast_list):
    pg, Tg, Sg, sigmag, dist = grid_section(cast_list)
    fig, ax = plt.subplots()
    cax = plt.contourf(dist, pg, sigmag, levels=np.arange(24.5, 27, 0.25),
                       cmap='afmhot_r')
    cbar = plt.colorbar(cax)
    cbar.ax.invert_yaxis()
    ax.set(xlim=(-1, np.max(dist) + 1), ylim=minmax(pg)[::-1])
    for d in dist[0, :]:
        ax.plot((d,  d), (2, np.nanmax(pg)), color='grey', zorder=5)
        plt.draw()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# automatic haze removal as described in
#
# Kaiming He, Jian Sun, and Xiaoou Tang, »Single Image Haze Removal
# Using Dark Channel Prior« in IEEE Transactions on Pattern Analysis
# and Machine Intelligence, vol. 33, no. 12, 2341--2353 (2010)
# DOI:  10.1109/TPAMI.2010.168
#
# He K., Sun J., Tang X. (2010) Guided Image Filtering. In: Daniilidis K.,
# Maragos P., Paragios N. (eds) Computer Vision – ECCV 2010. ECCV 2010.
# Lecture Notes in Computer Science, vol 6311. Springer, Berlin, Heidelberg

import os
import numpy as np
from pylab import *
from skimage import data, io, exposure, img_as_float
#
# first compile dehaze module with
# python3 setup.py build_ext --inplace
import dehaze

image='images/forrest.jpg'
image='images/city.jpg'
image='images/landscape.jpg'

# parameters

# window size (positive integer) for determing the dark channel and the transition map
w=4
# window size (positive integer) for guided filter
w2=3*w
# strength of the dahazing effect 0 <= stength <= 1 (is 0.95 in the original paper)
strength=0.99

# gamma correction, algorithm should work in linear RGB space
gamma=2.2

close('all')

I=img_as_float(io.imread(image))

io.imshow(I)
title('original image')
xticks([])
yticks([])
tight_layout()
show(False)

I=I**gamma

dark=dehaze.dark_channel(I, w)
figure()
imshow(dark, cmap='Greys')
title('dark channel')
xticks([])
yticks([])
tight_layout()
show(False)

haze_pixel=dark>=percentile(dark, 98)
In=sum(I, axis=2)/3
# figure()
# io.imshow(haze_pixel*In)
# title('most hazy regions')
# xticks([])
# yticks([])
# tight_layout()
# show(False)

#k0, k1=np.unravel_index(argmax(haze_pixel*In), In.shape)
#A0=I[k0, k1, :]
bright_pixel=In>=percentile(In[haze_pixel], 98)
A0=mean(I[logical_and(haze_pixel, bright_pixel), :], axis=0)

t=dehaze.transition_map(I, A0, w, strength)
figure()
imshow(t, cmap='Greys')
title('transition map')
xticks([])
yticks([])
tight_layout()
show(False)

t=dehaze.box_min(t, w)
t=dehaze.guidedfilter(I, t, w2, 0.001)
t[t<0.025]=0.025
figure()
imshow(t, cmap='Greys')
title('refined transition map')
xticks([])
yticks([])
tight_layout()
show(False)

J=I/t[:, :, np.newaxis] - A0[np.newaxis, np.newaxis, :]/t[:, :, np.newaxis] + A0
# J=empty_like(I)
# J[:, :, 0]=(I[:, :, 0]/t-A0[0]/t+A0[0])
# J[:, :, 1]=(I[:, :, 1]/t-A0[1]/t+A0[1])
# J[:, :, 2]=(I[:, :, 2]/t-A0[2]/t+A0[2])
J[J<0]=0
J[J>1]=1

J=J**(1/gamma)

figure()
imshow(J)
title('haze-free image')
xticks([])
yticks([])
tight_layout()
show(False)

name, ext=os.path.splitext(image)
io.imsave(name+'_haze_free'+ext, J)


import settings

import pandas as pd
import numpy as np

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.metrics import accuracy_score

def get_data(df):
    y = df.iloc[:, -1:]
    x = df.iloc[:, 0:1536]

    return x, y

def evaluate_cross_validation(num_trees, x_train, y_train, x_test, y_test, num_folds=10):
    x_train = x_train.reshape(-1, 1536)
    x_test = x_test.reshape(-1, 1536)

    y_train = np.where(y_train == 1)
    y_train = y_train[1]

    y_test = np.where(y_test == 1)
    y_test = y_test[1]
    forest_model = RandomForestClassifier(n_jobs=-1, n_estimators=num_trees)
    forest_model.fit(x_train, y_train)

    score = np.mean(cross_val_score(forest_model, x_test,
                                    y_test, cv=num_folds, n_jobs=-1))

    return score

def evaluate_train_test(df_train, df_test):
    x_train, y_train = get_data(df_train)

    forest_model = RandomForestClassifier(n_jobs=-1)
    forest_model.fit(x_train, y_train.values.ravel())

    x_test, y_test = get_data(df_test)

    y_pred_test = forest_model.predict(x_test)

    score = accuracy_score(y_test, y_pred_test)

    print("Evaluation score: " + str(score))


import streamlit as st
import numpy as np
import pandas as pd

st.title('Start Streamlit')
st.markdown('''
0. Progress Bar
1. write + text
2. write + DataFrame
3. No write + DataFrame
4. line chart
5. Display Map
6. show data with checkbox
7. show data with selectbox
8. show data with sidebar
9. slider with sidebar
10. show text when clicked
11. show text list when clicked
''')
st.markdown("---")

## progress bar
st.subheader('0. Progress Bar')
import time
st.info('Downloading data from server...')

### Add a placeholder
latest_iteration = st.empty()
bar = st.progress(0)

for i in range(100):
    ### Update the progress bar with each iteration.
    latest_iteration.text(f'Iteration {i+1}')
    bar.progress(i+1)
    time.sleep(0.1)

st.success('All data has been loaded!')

## write + text
st.subheader('1. write + text')
st.write('This is text')
st.code('''
st.write('This is text')
''')

## write + DataFrame
st.subheader('2. write + DataFrame')
st.write({
    'fruit' : ['apple', 'banana', 'kiwi'],
    'sugar' : [26, 66, 17]
})
st.code('''
st.write({
    'fruit' : ['apple', 'banana', 'kiwi'],
    'sugar' : [26, 66, 17]
})
''')

## No write + DataFrame
st.subheader('3. No write + DataFrame')
df = pd.DataFrame({
    'fruit' : ['apple', 'banana', 'kiwi'],
    'sugar' : [26, 66, 17]
})
df
st.code('''
df = pd.DataFrame({
    'fruit' : ['apple', 'banana', 'kiwi'],
    'sugar' : [26, 66, 17]
})
df
''')

## line chart
st.subheader('4. line chart')
chart_data = pd.DataFrame(
    columns = ['apple', 'banana', 'kiwi'],
    data=
    [[25,65,16],
     [26,66,17],
     [27,67,17],
     [27,65,17],
     [27,66,17],
     [27,66,18],
     [27,65,17],
     [28,66,17],
     [26,66,16],]
)
st.line_chart(chart_data)
st.code('''
chart_data = pd.DataFrame(
    columns = ['apple', 'banana', 'kiwi'],
    data=
    [[25,65,16],
     [26,66,17],
     [27,67,17],
     [27,65,17],
     [27,66,17],
     [27,66,18],
     [27,65,17],
     [28,66,17],
     [26,66,16],]
)
st.line_chart(chart_data)
''')

## Display Map
st.subheader('5. Display Map')
map_data = pd.DataFrame(
    columns = ['lat', 'lon'],
    data = np.random.randn(1000, 2) / [50, 50] + [37.76, -122.4]
    )
st.map(map_data)
st.code('''
map_data = pd.DataFrame(
    columns = ['lat', 'lon'],
    data = np.random.randn(1000, 2) / [50, 50] + [37.76, -122.4]
    )
st.map(map_data)
''')

## show data with checkbox
st.subheader('6. show data with checkbox')
if st.checkbox('Show DataFrame'):
    chart_data = pd.DataFrame(
        columns = ['apple', 'banana', 'kiwi'],
        data = [[25,65,16],
                [26,66,17],
                [27,67,17],
                [27,65,17],
                [27,66,17],
                [27,66,18],
                [27,65,17],
                [28,66,17],
                [26,66,16]]
        )
    chart_data
st.code('''
if st.checkbox('Show DataFrame'):
    chart_data = pd.DataFrame(
        columns = ['apple', 'banana', 'kiwi'],
        data = [[25,65,16],
                [26,66,17],
                [27,67,17],
                [27,65,17],
                [27,66,17],
                [27,66,18],
                [27,65,17],
                [28,66,17],
                [26,66,16]]
        )
    chart_data
''')

## show data with selectbox
st.subheader('7. show data with selectbox')
option = st.selectbox(
    'Choose fruit', df['fruit']
    # df = pd.DataFrame({
    #       'fruit' : ['apple', 'banana', 'kiwi'],
    #       'sugar' : [26, 66, 17]
    # })
)
st.info('Do you like "{}" ?'.format(option))
st.code('''
option = st.selectbox(
    'Choose fruit', df['fruit']
    # df = pd.DataFrame({
    #       'fruit' : ['apple', 'banana', 'kiwi'],
    #       'sugar' : [26, 66, 17]
    # })
)
st.info('Do you like "{}" ?'.format(option))
''')

## show data with sidebar
st.subheader('8. show data with sidebar')
sidebar_option = st.sidebar.selectbox(
    'sidebar_selectbox', df['fruit'], key='<sidebar>'
)
st.info('<sidebar> Do you like "{}" ?'.format(sidebar_option))
st.code('''
sidebar_option = st.sidebar.selectbox(
    'Choose fruit', df['fruit'], key='<sidebar>'
)
st.info('<sidebar> Do you like "{}" ?'.format(sidebar_option))
''')

## slider with sidebar
st.subheader('9. slider with sidebar')
select_number = st.sidebar.slider(
    'sidebar_slider', min_value=0, max_value=500
    )
st.info('<sidebar> You select number "{}" in sidebar_slider'.format(select_number))

## show text when clicked
st.subheader('10. show text when clicked')
left_columns, right_columns = st.beta_columns(2)
clicked = left_columns.button('click')
if clicked:
    right_columns.write("Hi! Streamlit! I'm right column")
    left_columns.write("hello! I'm left column")
st.code('''
left_columns, right_columns = st.beta_columns(2)
clicked = left_columns.button('click')
if clicked:
    right_columns.write("Hi! Streamlit! I'm right column")
    left_columns.write("hello! I'm left column")
''')

## show text list when clicked
st.subheader('11. show text list when clicked')
expander = st.beta_expander("FAQ")
expander.write('1th')
expander.write('2th')
expander.write('3th')
st.code('''
expander = st.beta_expander("FAQ")
expander.write('1th')
expander.write('2th')
expander.write('3th')
''')


import uuid
from dataclasses import dataclass, field
from models.item import Item
from models.model import Model
from models.user import User
from common.database import Database
from libs.mailgun import Mailgun

@dataclass(eq=False)
class Alert(Model):
	collection: str = field(init=False, default="alert")
	name: str
	item_id: str
	price_limit: float
	user_email: str
	price: float = field(default="alert")
	_id: str = field(default_factory=lambda: uuid.uuid4().hex)

	def __post_init__(self):
		self.item = Item.get_by_id(self.item_id)
		self.user = User.find_by_email(self.user_email)

	def json(self):

		return {
			"_id": self._id,
			"name": self.name,
			"price": self.item.load_price(),
			"price_limit": self.price_limit,
			"user_email": self.user_email,
			"item_id": self.item_id
		}

	def load_item_price(self):
		return self.item.load_price()

	def notify_if_price_reached(self):
		if self.load_item_price() < self.price_limit:
			return f"Item has reached a price under {self.price_limit} latest price {self.item.load_price()}"
			# message = f"Item has reached a price under {self.price_limit} latest price {self.item.load_price()}"
			# return Mailgun(message).send_simple_message()


from pydantic import BaseModel

class Phone(BaseModel):
    city: str
    country: str
    number: str

class Contacts(BaseModel):
    email: str
    name: str
    phone: Phone

class Coordinates(BaseModel):
    latitude: float
    longitude: float

class Experience(BaseModel):
    id: str

class SalaryRange(BaseModel):
    vars()["from"]: int = 0
    vars()["to"]: int = 0

class Schedule(BaseModel):
    vars()["id"]: str = ""

class Data(BaseModel):
    address: str
    allow_messages: bool
    billing_type: str
    business_area: int
    contacts: Contacts
    coordinates: Coordinates
    description: str
    experience: Experience
    html_tags: bool
    image_url: str
    name: str
    salary: int
    salary_range: SalaryRange
    schedule: Schedule

def converting(data):
    d = {
    "address": data["address"]["value"],
    "allow_messages": True,
    "billing_type": "packageOrSingle",
    "business_area": 1,
    "contacts": {
        "email": data["contacts"]["email"],
        "name": data["contacts"]["fullName"],
        "phone": {
            "city":  data["contacts"]["phone"][-10:-7],
            "country": data["contacts"]["phone"][:-10],
            "number": data["contacts"]["phone"][-7:-4] + "-" + data["contacts"]["phone"][-4:-2] + "-" + data["contacts"]["phone"][-2:]
            }
        },
    "coordinates": {
        "latitude": data["address"]["lat"],
        "longitude": data["address"]["lng"]
        },
    "description": data["description"],
    "experience": {
        "id": "noMatter"
    },
    "html_tags": True,
    "image_url": "https://img.hhcdn.ru/employer-logo/3410666.jpeg",
    "name": data["name"],
    "salary": data["salary"]["to"],
    "salary_range": {
        "from": data["salary"]["from"],
        "to": data["salary"]["to"]
        },
    "schedule": {
        "id": data["employment"]
        }
    }
    return Data(**d)


#necessary imports
import numpy as np
import pandas as pd
import csv
import lucene as ls
from java.io import File

#index imports
from org.apache.lucene.analysis.standard import StandardAnalyzer
from org.apache.lucene.analysis.en import EnglishAnalyzer
from org.apache.lucene.index import IndexWriter, IndexWriterConfig
from org.apache.lucene.store import SimpleFSDirectory, FSDirectory
import org.apache.lucene.document as document

# importing beautiful soup for html scrapping
import os
from bs4 import BeautifulSoup
import requests

# default paths
path = 'adhoc.fire.en/en.docs.2011/'
indexPath='index/'

# taking input from command line
import sys

n=len(sys.argv)
# path in en directory
if sys.argv[1]!='':
	path=sys.argv[1]

# path to index directory path
if sys.argv[2]!='':
	indexPath=sys.argv[2]

#inistialise the VM
ls.initVM()

indexPath=File(indexPath).toPath()
indexDir=FSDirectory.open(indexPath)

# we are using Standard analyser
# stop words removal from english analyzer
writerConfig=IndexWriterConfig(StandardAnalyzer(EnglishAnalyzer.ENGLISH_STOP_WORDS_SET))
writer=IndexWriter(indexDir,writerConfig)

# indexing wrt docno,title and text data
def indexNews(docno,title,text):
    doc=document.Document()
    doc.add(document.Field("DOCNO",docno,document.TextField.TYPE_STORED))
    doc.add(document.Field("TITLE",title,document.TextField.TYPE_STORED))
    doc.add(document.Field("TEXT",text,document.TextField.TYPE_STORED))
    writer.addDocument(doc)

# close the writer
def closeWriter():
    writer.close()

# FINDING THE FILE PATH
# Traverse from the root and find out if if there is any file in that folder store that path

# MAIN FUNCTION

if __name__=="__main__":
	# it goes to each file and parse the content of each tag in the file
	for root, directories, files in os.walk(path, topdown=False):
	    for name in files:
	    	# read the content of the file
	        file=open(os.path.join(root, name),'r').read()
	        # initialise the beauitful soup object
	        soup = BeautifulSoup(file, "lxml")

	        # find titles of news
	        titles=soup.find_all('title')
	        title_text=' '
	        # if some title if found
	        if len(titles)>0:
	            for title in titles:
	                title_text=title.text

	        # find docno of news
	        docnos=soup.find_all('docno')
	        docno_text=' '
	        # if some docno is found
	        if len(docnos)>0:
	            for docs in docnos:
	                docno_text=docs.text

	        # find the text of the news
	        texts=soup.find_all('text')
	        text_text=' '
	        # if some news if found
	        if len(texts)>0:
	            for txt in texts:
	                text_text=txt.text

	        # do the indexing
	        indexNews(docno_text,title_text,text_text.strip('\n').strip())
	# close the writer
	closeWriter()