// BROTOOLS INSTALLER SCRIPT
// This script installs and loads the BroTools plugin, and configures it to load automatically.
// To run simply Drag and Drop this file into Maya viewport
// Or open the Script Editor and Source the file.

// Dummy procedure used to locate the source script file path
global proc scriptLocator (){}

// Return path to directory containing this script
global proc string scriptLocation ()
{
    string $whatIs=`whatIs scriptLocator`;
    string $fullPath=`substring $whatIs 25 999`;  // Strip the human-readable preamble before the file path
    python("import os");  // Just in case this hasn't been imported already by some other scope
    return encodeString(python("os.path.dirname('" + $fullPath + "')"));
}

// Define path inside python
string $scriptLocation = scriptLocation();
python ("scriptLoc = r'" + $scriptLocation + "'");
python ("print ('scriptLoc: ' + scriptLoc)");

// Install code in python

python ("installer_ver = '20251202'\n\
\n\
def log(*args):\n\
    s = ' '.join(args)\n\
    print('# BroTools Installer | {}'.format(s))\n\
\n\
log('BroTools installer script {} start'.format(installer_ver))\n\
import os\n\
import sys\n\
import platform\n\
from maya import cmds\n\
from random import uniform\n\
\n\
QtWidgets = None\n\
QtCore = None\n\
QtGui = None\n\
\n\
try:\n\
    from PySide2 import QtWidgets, QtCore, QtGui\n\
except Exception:\n\
    try:\n\
        from PySide6 import QtWidgets, QtCore, QtGui\n\
    except Exception:\n\
        QtWidgets = None\n\
        QtCore = None\n\
        QtGui = None\n\
\n\
correct_install_location = True\n\
\n\
def _get_logo_pixmap():\n\
    if QtGui is None:\n\
        return None\n\
    import base64\n\
    logo_data = base64.b64decode(' ')\n\
    pixmap = QtGui.QPixmap()\n\
    pixmap.loadFromData(logo_data)\n\
    return pixmap\n\
\n\
def _installer_stylesheet():\n\
    if QtWidgets is None:\n\
        return ''\n\
    return 'QWidget { background-color: #232323; color: #e0e0e0; font-size: 12px; } QDialog { background-color: #232323; } QPushButton { background-color: rgba(255,255,255,0.07); border: 1px solid #1b1b1b; border-radius: 2px; padding: 6px 12px; font-size: 12px; color: #e0e0e0; } QPushButton:hover { background-color: rgba(255,255,255,0.14); border: 1px solid #3a3a3a; } QPushButton:pressed { background-color: rgba(0,0,0,0.35); border: 1px solid #141414; } QLabel#TitleLabel { font-size: 16px; font-weight: bold; } QLabel#HeaderLabel { font-size: 13px; color: #cccccc; } QLabel#CheckItem { font-size: 12px; color: #999999; } QLabel#CheckItemDone { font-size: 12px; color: #e0e0e0; } QLabel#VersionLabel { font-size: 10px; color: #666666; } QLineEdit { background-color: #1d1d1d; border: 1px solid #404040; border-radius: 3px; padding: 4px 8px; }'\n\
\n\
def _show_message(title, message, icon=None):\n\
    if QtWidgets is not None:\n\
        box = QtWidgets.QMessageBox()\n\
        box.setWindowTitle(title)\n\
        box.setText(message)\n\
        if icon == 'warning':\n\
            box.setIcon(QtWidgets.QMessageBox.Warning)\n\
        elif icon == 'critical':\n\
            box.setIcon(QtWidgets.QMessageBox.Critical)\n\
        box.setStandardButtons(QtWidgets.QMessageBox.Ok)\n\
        box.setStyleSheet(_installer_stylesheet())\n\
        if hasattr(box, 'exec_'):\n\
            box.exec_()\n\
        else:\n\
            box.exec()\n\
    else:\n\
        cmds.confirmDialog(title=title, message=message, button=['OK'], defaultButton='OK', icn='information')\n\
\n\
if QtWidgets is not None:\n\
\n\
    class InstallerDialog(QtWidgets.QDialog):\n\
\n\
        def __init__(self, maya_mod_dir, mod_name, installer_version, is_update, parent=None):\n\
            QtWidgets.QDialog.__init__(self, parent)\n\
            self.mayaModDir = maya_mod_dir\n\
            self.modName = mod_name\n\
            self.installerVersion = installer_version\n\
            self.isUpdate = is_update\n\
            self.currentStep = 0\n\
            action = 'Update Install Location' if is_update else 'Install'\n\
            self.setWindowTitle('BroTools Installer - {}'.format(action))\n\
            self.setMinimumWidth(520)\n\
            self.setMinimumHeight(340)\n\
            self.setStyleSheet(_installer_stylesheet())\n\
            self._build_ui()\n\
\n\
        def _build_ui(self):\n\
            main_layout = QtWidgets.QVBoxLayout(self)\n\
            main_layout.setContentsMargins(0, 10, 0, 0)\n\
            main_layout.setSpacing(0)\n\
\n\
            self.progress_bar = self._create_progress_bar()\n\
            main_layout.addWidget(self.progress_bar)\n\
\n\
            content_widget = QtWidgets.QWidget()\n\
            self.content_layout = QtWidgets.QVBoxLayout(content_widget)\n\
            self.content_layout.setContentsMargins(20, 20, 20, 20)\n\
            main_layout.addWidget(content_widget)\n\
\n\
            self.stacked_widget = QtWidgets.QStackedWidget()\n\
            self.content_layout.addWidget(self.stacked_widget)\n\
\n\
            self.page1 = self._create_install_page()\n\
            self.page2 = self._create_installing_page()\n\
            self.page3 = self._create_complete_page()\n\
\n\
            self.stacked_widget.addWidget(self.page1)\n\
            self.stacked_widget.addWidget(self.page2)\n\
            self.stacked_widget.addWidget(self.page3)\n\
\n\
            self.button_layout = QtWidgets.QHBoxLayout()\n\
            self.button_layout.addStretch(1)\n\
            self.content_layout.addLayout(self.button_layout)\n\
\n\
            self.cancel_button = QtWidgets.QPushButton('Cancel')\n\
            self.cancel_button.clicked.connect(self.reject)\n\
            self.button_layout.addWidget(self.cancel_button)\n\
\n\
            self.button_layout.addSpacing(8)\n\
\n\
            action = 'Update Install Location' if self.isUpdate else 'Install'\n\
            self.next_button = QtWidgets.QPushButton(action)\n\
            self.next_button.clicked.connect(self._on_next)\n\
            self.button_layout.addWidget(self.next_button)\n\
\n\
            self._update_progress()\n\
\n\
        def _create_progress_bar(self):\n\
            bar_widget = QtWidgets.QWidget()\n\
            bar_widget.setFixedHeight(2)\n\
            bar_widget.setStyleSheet('background-color: #141414;')\n\
            bar_layout = QtWidgets.QHBoxLayout(bar_widget)\n\
            bar_layout.setContentsMargins(0, 0, 0, 0)\n\
            bar_layout.setSpacing(2)\n\
\n\
            self.progress_segments = []\n\
            for i in range(3):\n\
                segment = QtWidgets.QWidget()\n\
                segment.setFixedHeight(2)\n\
                segment.setStyleSheet('background-color: #0b0b0b;')\n\
                bar_layout.addWidget(segment)\n\
                self.progress_segments.append(segment)\n\
\n\
            return bar_widget\n\
\n\
        def _update_progress(self):\n\
            for i, segment in enumerate(self.progress_segments):\n\
                if i <= self.currentStep:\n\
                    segment.setStyleSheet('background-color: #faa300;')\n\
                else:\n\
                    segment.setStyleSheet('background-color: #0b0b0b;')\n\
\n\
        def _create_install_page(self):\n\
            page = QtWidgets.QWidget()\n\
            layout = QtWidgets.QVBoxLayout(page)\n\
\n\
            logo_pixmap = _get_logo_pixmap()\n\
            if logo_pixmap:\n\
                logo_label = QtWidgets.QLabel()\n\
                logo_label.setPixmap(logo_pixmap)\n\
                logo_label.setAlignment(QtCore.Qt.AlignLeft)\n\
                layout.addWidget(logo_label)\n\
                layout.addSpacing(8)\n\
\n\
            action = 'Update Install Location' if self.isUpdate else 'Install'\n\
            title_text = 'BroTools Installer - {}'.format(action)\n\
            title_label = QtWidgets.QLabel(title_text)\n\
            title_label.setObjectName('TitleLabel')\n\
            layout.addWidget(title_label)\n\
\n\
            if self.isUpdate:\n\
                header_text = 'Update the installation path for {0}.'.format(self.modName)\n\
            else:\n\
                header_text = 'This script will install {0}.'.format(self.modName)\n\
            header = QtWidgets.QLabel(header_text)\n\
            header.setObjectName('HeaderLabel')\n\
            header.setWordWrap(True)\n\
            layout.addWidget(header)\n\
\n\
            if self.isUpdate:\n\
                details_text = 'This will update the {0}.mod file path in the selected Maya modules folder. You can adjust the Maya modules folder below if needed.'.format(self.modName)\n\
            else:\n\
                details_text = 'This will create a {0}.mod file in the selected Maya modules folder, load {0}.py plugin and set it to autoload. You can adjust the Maya modules folder below if needed.'.format(self.modName)\n\
            details = QtWidgets.QLabel(details_text)\n\
            details.setWordWrap(True)\n\
            layout.addWidget(details)\n\
\n\
            layout.addSpacing(20)\n\
\n\
            path_layout = QtWidgets.QHBoxLayout()\n\
            path_label = QtWidgets.QLabel('Maya modules folder:')\n\
            path_layout.addWidget(path_label)\n\
\n\
            self.path_edit = QtWidgets.QLineEdit(self.mayaModDir)\n\
            self.path_edit.setReadOnly(True)\n\
            path_layout.addWidget(self.path_edit)\n\
\n\
            self.browse_button = QtWidgets.QPushButton('Change Path')\n\
            self.browse_button.clicked.connect(self._on_change_path)\n\
            path_layout.addWidget(self.browse_button)\n\
\n\
            layout.addLayout(path_layout)\n\
            layout.addStretch(1)\n\
\n\
            version_label = QtWidgets.QLabel('Installer version: {}'.format(self.installerVersion))\n\
            version_label.setObjectName('VersionLabel')\n\
            version_label.setAlignment(QtCore.Qt.AlignLeft)\n\
            layout.addWidget(version_label)\n\
\n\
            return page\n\
\n\
        def _create_installing_page(self):\n\
            page = QtWidgets.QWidget()\n\
            layout = QtWidgets.QVBoxLayout(page)\n\
            layout.addStretch(1)\n\
\n\
            action = 'Updating' if self.isUpdate else 'Installing'\n\
            title_label = QtWidgets.QLabel('{}...'.format(action))\n\
            title_label.setObjectName('TitleLabel')\n\
            title_label.setAlignment(QtCore.Qt.AlignCenter)\n\
            layout.addWidget(title_label)\n\
\n\
            layout.addSpacing(20)\n\
\n\
            checklist_layout = QtWidgets.QVBoxLayout()\n\
            checklist_layout.setSpacing(8)\n\
\n\
            self.check_items = []\n\
            check_texts = [\n\
                'Creating module file',\n\
                'Updating environment',\n\
                'Loading plugin'\n\
            ]\n\
            \n\
            for text in check_texts:\n\
                item_layout = QtWidgets.QHBoxLayout()\n\
                item_layout.addStretch(1)\n\
                \n\
                check_label = QtWidgets.QLabel('-')\n\
                check_label.setObjectName('CheckItem')\n\
                item_layout.addWidget(check_label)\n\
                \n\
                text_label = QtWidgets.QLabel(text)\n\
                text_label.setObjectName('CheckItem')\n\
                item_layout.addWidget(text_label)\n\
                \n\
                item_layout.addStretch(1)\n\
                checklist_layout.addLayout(item_layout)\n\
                \n\
                self.check_items.append((check_label, text_label))\n\
\n\
            layout.addLayout(checklist_layout)\n\
            layout.addStretch(1)\n\
\n\
            return page\n\
\n\
        def _create_complete_page(self):\n\
            page = QtWidgets.QWidget()\n\
            layout = QtWidgets.QVBoxLayout(page)\n\
            layout.addStretch(1)\n\
\n\
            title_label = QtWidgets.QLabel('Installation Complete')\n\
            title_label.setObjectName('TitleLabel')\n\
            title_label.setAlignment(QtCore.Qt.AlignCenter)\n\
            layout.addWidget(title_label)\n\
\n\
            if self.isUpdate:\n\
                message_text = 'BroTools installation path has been successfully updated.'\n\
            else:\n\
                message_text = 'BroTools has been successfully installed and will now be loaded for the first time.'\n\
            message = QtWidgets.QLabel(message_text)\n\
            message.setWordWrap(True)\n\
            message.setAlignment(QtCore.Qt.AlignCenter)\n\
            layout.addWidget(message)\n\
\n\
            layout.addStretch(1)\n\
\n\
            return page\n\
\n\
        def _on_change_path(self):\n\
            start_dir = self.mayaModDir\n\
            new_dir = QtWidgets.QFileDialog.getExistingDirectory(\n\
                self,\n\
                'Pick Maya modules directory',\n\
                start_dir\n\
            )\n\
            if new_dir:\n\
                self.mayaModDir = new_dir\n\
                self.path_edit.setText(new_dir)\n\
\n\
        def _on_next(self):\n\
            if self.currentStep == 0:\n\
                self.currentStep = 1\n\
                self._animate_page_transition(1)\n\
                self.cancel_button.setEnabled(False)\n\
                self.next_button.setEnabled(False)\n\
                self.browse_button.setEnabled(False)\n\
                self._update_progress()\n\
                QtWidgets.QApplication.processEvents()\n\
                self._perform_installation()\n\
            elif self.currentStep == 2:\n\
                self.accept()\n\
\n\
        def _animate_page_transition(self, page_index):\n\
            self.stacked_widget.setCurrentIndex(page_index)\n\
\n\
        def _perform_installation(self):\n\
            self.installSuccess = True\n\
            self.installError = None\n\
            \n\
            try:\n\
                import time\n\
                \n\
                self._mark_check_item(0, True)\n\
                QtWidgets.QApplication.processEvents()\n\
                time.sleep(uniform(0.1, 0.3))\n\
                \n\
                try:\n\
                    modDir = os.path.normpath(os.path.relpath(scriptLoc, self.mayaModDir))\n\
                except ValueError:\n\
                    modDir = os.path.normpath(os.path.abspath(scriptLoc))\n\
                \n\
                newModFile = os.path.join(self.mayaModDir, self.modName + '.mod')\n\
                \n\
                if not os.path.exists(self.mayaModDir):\n\
                    cmds.sysFile(self.mayaModDir, md=True)\n\
                \n\
                modfile_contents = '+ BroTools 1.0 ' + modDir\n\
                with open(newModFile, 'w') as f:\n\
                    f.write(modfile_contents)\n\
                \n\
                self._mark_check_item(0, False)\n\
                time.sleep(uniform(0.1, 0.3))\n\
                \n\
                self._mark_check_item(1, True)\n\
                QtWidgets.QApplication.processEvents()\n\
                time.sleep(uniform(0.1, 0.3))\n\
                \n\
                sys.path.append(scriptLoc)\n\
                os.environ['MAYA_PLUG_IN_PATH'] += os.pathsep + os.path.join(scriptLoc, 'plug-ins')\n\
                os.environ['MAYA_PLUG_IN_PATH'] += os.pathsep + scriptLoc\n\
                os.environ['MAYA_SCRIPT_PATH'] += os.pathsep + scriptLoc\n\
                \n\
                self._mark_check_item(1, False)\n\
                time.sleep(uniform(0.1, 0.3))\n\
                \n\
                self._mark_check_item(2, True)\n\
                QtWidgets.QApplication.processEvents()\n\
                time.sleep(uniform(0.1, 0.3))\n\
                \n\
                cmds.loadPlugin('BroTools.py')\n\
                cmds.pluginInfo('BroTools.py', e=True, autoload=True)\n\
                \n\
                self._mark_check_item(2, False)\n\
                time.sleep(uniform(1, 1.5))\n\
                \n\
                self.currentStep = 2\n\
                self._update_progress()\n\
                self._animate_page_transition(2)\n\
                self.next_button.setText('OK')\n\
                self.next_button.setEnabled(True)\n\
                \n\
            except Exception as e:\n\
                self.installSuccess = False\n\
                self.installError = str(e)\n\
                for check_label, text_label in self.check_items:\n\
                    check_label.setText('x')\n\
                    check_label.setStyleSheet('color: #ff4444;')\n\
                self.cancel_button.setEnabled(True)\n\
\n\
        def _mark_check_item(self, index, in_progress):\n\
            if index < len(self.check_items):\n\
                check_label, text_label = self.check_items[index]\n\
                if in_progress:\n\
                    check_label.setText('>')\n\
                    check_label.setStyleSheet('color: #faa300;')\n\
                    text_label.setStyleSheet('color: #faa300;')\n\
                else:\n\
                    check_label.setText('+')\n\
                    check_label.setStyleSheet('color: #4CAF50;')\n\
                    text_label.setObjectName('CheckItemDone')\n\
                    text_label.setStyle(text_label.style())\n\
\n\
# TODO Add check for script folder installation\n\
\n\
# os.environ['MAYA_MODULE_PATH']\n\
# Example output: C:/Program Files/Autodesk/Maya2023/modules;D:/Nextcloud/WindowsDocuments/Mikrowave/maya/2023/modules;D:/Nextcloud/WindowsDocuments/Mikrowave/maya/modules;C:/Program Files/Common Files/Autodesk Shared/Modules/maya/2023\n\
\n\
if platform.system() == 'Windows':\n\
    if 'autodesk' in scriptLoc.lower() and 'maya' in scriptLoc.lower() and 'plug-ins' in scriptLoc.lower():\n\
        correct_install_location = False\n\
\n\
if not correct_install_location:\n\
    _show_message(\n\
        'BroTools Installer',\n\
        'BroTools Installer detected that you are trying to install it under Maya plug-ins folder. Unfortunately, BroTools can not be installed inside Maya plug-ins folder. Please, move it anywhere else and try again.',\n\
        icon='critical'\n\
    )\n\
    sys.exit()\n\
\n\
def win_get_documents_folder():\n\
    try:\n\
        log('Attempt to find documents folder with win_get_documents_folder...')\n\
        import ctypes.wintypes\n\
        CSIDL_PERSONAL= 5 \n\
        SHGFP_TYPE_CURRENT= 0\n\
        buf= ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)\n\
        ctypes.windll.shell32.SHGetFolderPathW(0, CSIDL_PERSONAL, 0, SHGFP_TYPE_CURRENT, buf)\n\
        return buf.value\n\
    except Exception as e:\n\
        log('Failed to use win_get_documents_folder!')\n\
        return None\n\
\n\
mayaModDir = None\n\
# mayaModDirs = os.environ.get('MAYA_MODULE_PATH')\n\
# log('MAYA_MODULE_PATH: {}'.format(mayaModDirs))\n\
\n\
if mayaModDir is None:\n\
       \n\
    if platform.system() == 'Darwin':\n\
        home = os.path.expanduser('~')\n\
        mayaModDir = os.path.join(home, 'Library', 'Preferences', 'Autodesk', 'maya', 'modules')\n\
\n\
    elif platform.system() == 'Windows':\n\
        home = win_get_documents_folder()\n\
        if home is None:\n\
            home = os.path.expanduser('~')\n\
        mayaModDir = os.path.join(home, 'maya', 'modules')\n\
        \n\
    else:\n\
        home = os.path.expanduser('~')\n\
        mayaModDir = os.path.join(home, 'maya', 'modules')\n\
\n\
\n\
log('mayaModDir found: {}'.format(os.path.exists(mayaModDir)))\n\
log('mayaModDir path: {}'.format(mayaModDir))\n\
\n\
modName = 'BroTools'\n\
modFileName = modName + '.mod'\n\
modFile = os.path.join(scriptLoc, modFileName)\n\
newModFile = os.path.join(mayaModDir, modFileName)\n\
\n\
is_update = os.path.exists(newModFile)\n\
if is_update:\n\
    log('Existing installation detected at: {}'.format(newModFile))\n\
else:\n\
    log('No existing installation found, proceeding with fresh install')\n\
\n\
if QtWidgets is None:\n\
    s = cmds.confirmDialog(title='BroTools installer',\n\
                           message='''This script will install {0}.\n\
                           \n\
It will:\n\
 \n\
 1. Create a {0}.mod file at {1} \n\
 2. Load {0}.py plugin \n\
 3. Set it to autoload.\n\
 \n\
Click Install to proceed.\n\
If you suspect that path for Maya modules folder is incorrect you can Change *.mod Path manually.\n\
\n\
[Installer version {2}]'''.format(modName, newModFile, installer_ver),\n\
                           button=['Install', 'Change *.mod Path', 'Cancel'], defaultButton='Install', cancelButton='Cancel', dismissString='Cancel', icn='question')\n\
\n\
    if s == 'Cancel':\n\
        cmds.confirmDialog(title='BroTools',\n\
                           message='Install cancelled.'.format(\n\
                               modName, newModFile),\n\
                           button='OK', defaultButton='OK', icn='information')\n\
        sys.exit()\n\
\n\
    if s == 'Change *.mod Path':\n\
        mayaModDir = cmds.fileDialog2(fileMode=3, cap='Pick Maya modules directory to install {}.mod to'.format(modName))[0]\n\
        newModFile = os.path.join(mayaModDir, modFileName)\n\
    \n\
    try:\n\
        try:\n\
            log('Trying to get relative path...')\n\
            modDir = os.path.normpath(os.path.relpath(scriptLoc, mayaModDir))\n\
        except ValueError:\n\
            log('Failed to get relative path, using absolute path instead.')\n\
            modDir = os.path.normpath(os.path.abspath(scriptLoc))\n\
    except Exception:\n\
        modDir = scriptLoc\n\
\n\
    if not os.path.exists(mayaModDir):\n\
        cmds.sysFile(mayaModDir, md=True)\n\
\n\
    modfile_contents = '+ BroTools 1.0 ' + modDir\n\
\n\
    with open(newModFile, 'w') as f:\n\
        f.write(modfile_contents)\n\
\n\
    sys.path.append(scriptLoc)\n\
    os.environ['MAYA_PLUG_IN_PATH'] += os.pathsep + os.path.join(scriptLoc, 'plug-ins')\n\
    os.environ['MAYA_PLUG_IN_PATH'] += os.pathsep + scriptLoc\n\
    os.environ['MAYA_SCRIPT_PATH'] += os.pathsep + scriptLoc\n\
    log('os.environ[MAYA_SCRIPT_PATH] =', os.environ['MAYA_SCRIPT_PATH'])\n\
\n\
    cmds.loadPlugin('BroTools.py')\n\
    cmds.pluginInfo('BroTools.py', e=True, autoload=True)\n\
\n\
    cmds.confirmDialog(title='BroTools',\n\
                       message='BroTools installed. Now we will attempt to load it for the first time. If you experience any issues now, please, check ScriptEditor for errors',\n\
                       button='OK', defaultButton='OK', icn='information')\n\
else:\n\
    dialog = InstallerDialog(mayaModDir, modName, installer_ver, is_update)\n\
    if hasattr(dialog, 'exec_'):\n\
        result = dialog.exec_()\n\
    else:\n\
        result = dialog.exec()\n\
    if result != QtWidgets.QDialog.Accepted:\n\
        _show_message('BroTools', 'Install cancelled.')\n\
        sys.exit()\n\
");