<commit_before>ndpane                        = require 'ndpane'
_                             = require 'underscore'
kd                            = require 'kd'
$                             = require 'jquery'
KDBlockingModalView           = kd.BlockingModalView
KDCustomHTMLView              = kd.CustomHTMLView
KDModalView                   = kd.ModalView
KDNotificationView            = kd.NotificationView
KDSplitView                   = kd.SplitView
KDSplitViewPanel              = kd.SplitViewPanel
remote                        = require('app/remote').getInstance()
globals                       = require 'globals'
nick                          = require 'app/util/nick'
showError                     = require 'app/util/showError'
whoami                        = require 'app/util/whoami'
Machine                       = require 'app/providers/machine'
KodingKontrol                 = require 'app/kite/kodingkontrol'
FSHelper                      = require 'app/util/fs/fshelper'
AppController                 = require 'app/appcontroller'
CollaborationController       = require './collaborationcontroller'
IDEContentSearch              = require './views/contentsearch/idecontentsearch'
IDEEditorPane                 = require './workspace/panes/ideeditorpane'
IDEFileFinder                 = require './views/filefinder/idefilefinder'
IDEFilesTabView               = require './views/tabview/idefilestabview'
IDEShortcutsView              = require './views/shortcutsview/ideshortcutsview'
IDEStatusBar                  = require './views/statusbar/idestatusbar'
IDEStatusBarMenu              = require './views/statusbar/idestatusbarmenu'
IDETerminalPane               = require './workspace/panes/ideterminalpane'
IDEView                       = require './views/tabview/ideview'
IDEWorkspace                  = require './workspace/ideworkspace'
splashMarkups                 = require './util/splashmarkups'
IDEApplicationTabView         = require './views/tabview/ideapplicationtabview'
AceFindAndReplaceView         = require 'ace/acefindandreplaceview'
EnvironmentsMachineStateModal = require 'app/providers/environmentsmachinestatemodal'
environmentDataProvider       = require 'app/userenvironmentdataprovider'

require('./routes')()


module.exports =

class IDEAppController extends AppController

  _.extend @prototype, CollaborationController

  {
    Stopped, Running, NotInitialized, Terminated, Unknown, Pending,
    Starting, Building, Stopping, Rebooting, Terminating, Updating
  } = Machine.State

  {noop, warn} = kd

  @options = require './ideappcontrolleroptions'

  constructor: (options = {}, data) ->
    options.appInfo =
      type          : 'application'
      name          : 'IDE'

    super options, data

    layoutOptions     =
      splitOptions    :
        direction     : 'vertical'
        name          : 'BaseSplit'
        sizes         : [ 250, null ]
        maximums      : [ 400, null ]
        views         : [
          {
            type      : 'custom'
            name      : 'filesPane'
            paneClass : IDEFilesTabView
          },
          {
            type      : 'custom'
            name      : 'editorPane'
            paneClass : IDEView
          }
        ]

    $('body').addClass 'dark' # for theming

    @workspace = new IDEWorkspace { layoutOptions }
    @ideViews  = []

    # todo:
    # - following two should be abstracted out into a separate api
    @layout = ndpane(16)
    @layoutMap = new Array(16*16)

    {windowController} = kd.singletons
    windowController.addFocusListener @bound 'setActivePaneFocus'

    @workspace.once 'ready', => @getView().addSubView @workspace.getView()

    kd.singletons.appManager.on 'AppIsBeingShown', (app) =>

      return  unless app instanceof IDEAppController

      @setActivePaneFocus on

      # Temporary fix for IDE is not shown after
      # opening pages which uses old SplitView.
      # TODO: This needs to be fixed. ~Umut
      kd.singletons.windowController.notifyWindowResizeListeners()

      @resizeActiveTerminalPane()

    @localStorageController = kd.getSingleton('localStorageController').storage 'IDE'


  prepareIDE: ->

    panel     = @workspace.getView()
    appView   = @getView()
    ideView   = panel.getPaneByName 'editorPane'

    @setActiveTabView ideView.tabView
    @registerIDEView  ideView

    splitViewPanel = ideView.parent.parent
    @createStatusBar splitViewPanel
    @createFindAndReplaceView splitViewPanel

    appView.emit 'KeyViewIsSet'

    @createInitialView()
    @bindCollapseEvents()

    {@finderPane, @settingsPane} = @workspace.panel.getPaneByName 'filesPane'

    @bindRouteHandler()
    @initiateAutoSave()
    @emit 'ready'


  bindRouteHandler: ->

    {router, mainView} = kd.singletons

    router.on 'RouteInfoHandled', (routeInfo) =>
      if routeInfo.path.indexOf('/IDE') is -1
        if mainView.isSidebarCollapsed
          mainView.toggleSidebar()


  bindCollapseEvents: ->

    { panel } = @workspace

    filesPane = @workspace.panel.getPaneByName 'filesPane'

    # We want double click to work
    # if only the sidebar is collapsed. ~Umut
    expand = (event) =>
      kd.utils.stopDOMEvent event  if event?
      @toggleSidebar()  if @isSidebarCollapsed

    filesPane.on 'TabHandleMousedown', expand

    baseSplit = panel.layout.getSplitViewByName 'BaseSplit'
    baseSplit.resizer.on 'dblclick', @bound 'toggleSidebar'


  setActiveTabView: (tabView) ->

    return  if tabView is @activeTabView
    @setActivePaneFocus off
    @activeTabView = tabView
    @setActivePaneFocus on


  setActivePaneFocus: (state) ->

    return  unless pane = @getActivePaneView()
    return  if pane is @activePaneView

    @activePaneView = pane

    kd.utils.defer -> pane.setFocus? state


  splitTabView: (type = 'vertical', ideViewOptions) ->

    ideView        = @activeTabView.parent
    ideParent      = ideView.parent
    newIDEView     = new IDEView ideViewOptions

    splitViewPanel = @activeTabView.parent.parent
    if splitViewPanel instanceof KDSplitViewPanel
    then layout = splitViewPanel._layout
    else layout = @layout

    @activeTabView = null

    ideView.detach()

    splitView   = new KDSplitView
      type      : type
      views     : [ null, newIDEView ]

    layout.split(type is 'vertical')
    splitView._layout = layout

    @registerIDEView newIDEView

    splitView.once 'viewAppended', =>
      splitView.panels.first.attach ideView
      splitView.panels[0] = ideView.parent
      splitView.options.views[0] = ideView
      splitView.panels.forEach (panel, i) =>
        leaf = layout.leafs[i]
        panel._layout = leaf
        @layoutMap[leaf.data.offset] = panel

    ideParent.addSubView splitView
    @setActiveTabView newIDEView.tabView

    splitView.on 'ResizeDidStop', kd.utils.throttle 500, @bound 'doResize'


  mergeSplitView: ->

    panel     = @activeTabView.parent.parent
    splitView = panel.parent
    {parent}  = splitView

    return  unless panel instanceof KDSplitViewPanel

    if parent instanceof KDSplitViewPanel
      parentSplitView    = parent.parent
      panelIndexInParent = parentSplitView.panels.indexOf parent

    splitView.once 'SplitIsBeingMerged', (views) =>
      for view in views
        index = @ideViews.indexOf view
        @ideViews.splice index, 1

      @layoutMap[splitView._layout.data.offset] = parent

      @handleSplitMerge views, parent, parentSplitView, panelIndexInParent
      @doResize()

    splitView._layout.leafs.forEach (leaf) =>
      @layoutMap[leaf.data.offset] = null
    splitView._layout.merge()

    splitView.merge()


  handleSplitMerge: (views, container, parentSplitView, panelIndexInParent) ->

    ideView = new IDEView createNewEditor: no
    panes   = []

    for view in views
      {tabView} = view

      for p in tabView.panes by -1
        {pane} = tabView.removePane p, yes, (yes if tabView instanceof IDEApplicationTabView)
        panes.push pane

      view.destroy()

    container.addSubView ideView

    for pane in panes
      ideView.tabView.addPane pane

    @setActiveTabView ideView.tabView
    @registerIDEView ideView

    if parentSplitView and panelIndexInParent
      parentSplitView.options.views[panelIndexInParent] = ideView
      parentSplitView.panels[panelIndexInParent]        = ideView.parent


  openFile: (file, contents, callback = noop, emitChange) ->

    @activeTabView.emit 'FileNeedsToBeOpened', file, contents, callback, emitChange


  openMachineTerminal: (machineData) ->

    @activeTabView.emit 'MachineTerminalRequested', machineData


  openMachineWebPage: (machineData) ->

    @activeTabView.emit 'MachineWebPageRequested', machineData


  mountMachine: (machineData) ->

    # interrupt if workspace was changed
    return if machineData.uid isnt @workspaceData.machineUId

    panel        = @workspace.getView()
    filesPane    = panel.getPaneByName 'filesPane'

    path = @workspaceData?.rootPath

    path ?= if owner = machineData.getOwner()
    then "/home/#{owner}"
    else '/'

    filesPane.emit 'MachineMountRequested', machineData, path


  unmountMachine: (machineData) ->

    panel     = @workspace.getView()
    filesPane = panel.getPaneByName 'filesPane'

    filesPane.emit 'MachineUnmountRequested', machineData


  isMachineRunning: ->

    return @mountedMachine.status.state is Running


  createInitialView: ->

    kd.utils.defer =>
      @splitTabView 'horizontal', createNewEditor: no
      @getMountedMachine (err, machine) =>

        return unless machine

        machine = new Machine { machine }  unless machine instanceof Machine

        for ideView in @ideViews
          ideView.mountedMachine = @mountedMachine

        unless @isMachineRunning()
          nickname     = nick()
          machineLabel = machine.slug or machine.label
          splashes     = splashMarkups

          @fakeEditor       = @ideViews.first.createEditor()
          @fakeTabView      = @activeTabView
          fakeTerminalView  = new KDCustomHTMLView partial: splashes.getTerminal nickname
          @fakeTerminalPane = @fakeTabView.parent.createPane_ fakeTerminalView, { name: 'Terminal' }
          @fakeFinderView   = new KDCustomHTMLView partial: splashes.getFileTree nickname, machineLabel

          @finderPane.addSubView @fakeFinderView, '.nfinder .jtreeview-wrapper'
          @fakeEditor.once 'EditorIsReady', => kd.utils.defer => @fakeEditor.setFocus no

        else
          snapshot = @localStorageController.getValue @getWorkspaceSnapshotName()

          if snapshot
            @resurrectLocalSnapshot snapshot
          else
            @ideViews.first.createEditor()
            @ideViews.last.createTerminal { machine }
            @setActiveTabView @ideViews.first.tabView
            @forEachSubViewInIDEViews_ (pane) ->
              pane.isInitial = yes


  setMountedMachine: (machine) ->

    @mountedMachine = machine
    @emit 'MachineDidMount', machine, @workspaceData


  whenMachineReady: (callback) ->

    if @mountedMachine
    then callback @mountedMachine, @workspaceData
    else @once 'MachineDidMount', callback


  getMountedMachine: (callback = noop) ->

    return callback()  unless @mountedMachineUId

    kd.utils.defer =>
      environmentDataProvider.fetchMachineByUId @mountedMachineUId, (machine, ws) =>
        machine = new Machine { machine }  unless machine instanceof Machine
        @setMountedMachine machine

        callback null, machine


  mountMachineByMachineUId: (machineUId) ->

    computeController = kd.getSingleton 'computeController'
    container         = @getView()

    environmentDataProvider.fetchMachineByUId machineUId, (machineItem) =>

      unless machineItem
        return @createMachineStateModal { state: 'NotFound', container }

      unless machineItem instanceof Machine
        machineItem = new Machine machine: machineItem

      @setMountedMachine machineItem

      @prepareIDE()

      callback = =>

        if machineItem
          {state}         = machineItem.status
          machineId       = machineItem._id
          baseMachineKite = machineItem.getBaseKite()
          isKiteConnected = baseMachineKite._state is 1


          if state is Running and isKiteConnected
            @mountMachine machineItem
            baseMachineKite.fetchTerminalSessions()

          else
            unless @machineStateModal

              @createMachineStateModal {
                state, container, machineItem, initial: yes
              }

              if state is NotInitialized
                @machineStateModal.once 'MachineTurnOnStarted', =>
                  kd.getSingleton('mainView').activitySidebar.initiateFakeCounter()

          @prepareCollaboration()
          @bindMachineEvents machineItem

        else
          @createMachineStateModal { state: 'NotFound', container }


      @appStorage = kd.getSingleton('appStorageController').storage 'IDE', '1.0.0'
      @appStorage.fetchStorage =>

        isOnboardingModalShown = @appStorage.getValue 'isOnboardingModalShown'

        callback()


  bindMachineEvents: (machineItem) ->

    actionRequiredStates = [Pending, Stopping, Stopped, Terminating, Terminated]

    kd.getSingleton 'computeController'

      .on "public-#{machineItem._id}", (event) =>

        if event.status in actionRequiredStates
          @showStateMachineModal machineItem, event

      .on "reinit-#{machineItem._id}", @bound 'handleMachineReinit'


  handleMachineReinit: ({status}) ->

    switch status
      when 'Building'
        environmentDataProvider.ensureDefaultWorkspace kd.noop
      when 'Running'
        @quit()


  showStateMachineModal: (machineItem, event) ->

    KodingKontrol.dcNotification?.destroy()
    KodingKontrol.dcNotification = null

    machineItem.getBaseKite( no ).disconnect()

    if @machineStateModal
      @machineStateModal.updateStatus event
    else
      {state}   = machineItem.status
      container = @getView()
      @createMachineStateModal { state, container, machineItem }


  createMachineStateModal: (options = {}) ->

    { mainView } = kd.singletons
    mainView.toggleSidebar()  if mainView.isSidebarCollapsed

    { state, container, machineItem, initial } = options

    container   ?= @getView()
    modalOptions = { state, container, initial }
    @machineStateModal = new EnvironmentsMachineStateModal modalOptions, machineItem

    @machineStateModal.once 'KDObjectWillBeDestroyed', => @machineStateModal = null
    @machineStateModal.once 'IDEBecameReady',          => @handleIDEBecameReady machineItem


  collapseSidebar: ->

    panel        = @workspace.getView()
    splitView    = panel.layout.getSplitViewByName 'BaseSplit'
    floatedPanel = splitView.panels.first
    filesPane    = panel.getPaneByName 'filesPane'
    {tabView}    = filesPane
    desiredSize  = 250

    splitView.resizePanel 39, 0
    @getView().setClass 'sidebar-collapsed'
    floatedPanel.setClass 'floating'
    @activeFilesPaneName = tabView.activePane.name
    tabView.showPaneByName 'Dummy'

    @isSidebarCollapsed = yes

    tabView.on 'PaneDidShow', (pane) ->
      return if pane.options.name is 'Dummy'
      @expandSidebar()  if @isSidebarCollapsed


  expandSidebar: ->

    panel        = @workspace.getView()
    splitView    = panel.layout.getSplitViewByName 'BaseSplit'
    floatedPanel = splitView.panels.first
    filesPane    = panel.getPaneByName 'filesPane'

    splitView.resizePanel 250, 0
    @getView().unsetClass 'sidebar-collapsed'
    floatedPanel.unsetClass 'floating'
    @isSidebarCollapsed = no
    filesPane.tabView.showPaneByName @activeFilesPaneName


  toggleSidebar: ->

    if @isSidebarCollapsed then @expandSidebar() else @collapseSidebar()


  splitVertically: ->

    @splitTabView 'vertical'


  splitHorizontally: ->

    @splitTabView 'horizontal'

  createNewFile: do ->
    newFileSeed = 1

    return ->
      path     = "localfile:/Untitled-#{newFileSeed++}.txt@#{Date.now()}"
      file     = FSHelper.createFileInstance { path, machine: @mountedMachine }
      contents = ''

      @openFile file, contents


  createNewTerminal: (options={}) ->

    { machine, path, resurrectSessions } = options

    machine = @mountedMachine  unless machine instanceof Machine

    if @workspaceData and not path
      { rootPath, isDefault } = @workspaceData
      options.path = rootPath  if rootPath and not isDefault

    @activeTabView.emit 'TerminalPaneRequested', options


  createNewBrowser: (url) ->

    url = ''  unless typeof url is 'string'

    @activeTabView.emit 'PreviewPaneRequested', url


  createNewDrawing: (paneHash) ->

    paneHash = null unless typeof paneHash is 'string'

    @activeTabView.emit 'DrawingPaneRequested', paneHash


  moveTab: (direction) ->

    tabView = @activeTabView
    return unless tabView.parent?

    panel = tabView.parent.parent
    return  unless panel instanceof KDSplitViewPanel

    targetOffset = @layout[direction](panel._layout.data.offset)
    return  unless targetOffset?

    targetPanel = @layoutMap[targetOffset]

    {pane} = tabView.removePane tabView.getActivePane(), yes, yes

    targetPanel.subViews.first.tabView.addPane pane
    @setActiveTabView targetPanel.subViews.first.tabView
    @doResize()


  moveTabUp: -> @moveTab 'north'

  moveTabDown: -> @moveTab 'south'

  moveTabLeft: -> @moveTab 'west'

  moveTabRight: -> @moveTab 'east'


  goToLeftTab: ->

    index = @activeTabView.getActivePaneIndex()
    return if index is 0

    @activeTabView.showPaneByIndex index - 1


  goToRightTab: ->

    index = @activeTabView.getActivePaneIndex()
    return if index is @activeTabView.length - 1

    @activeTabView.showPaneByIndex index + 1


  goToTabNumber: (index) ->

    @activeTabView.showPaneByIndex index


  goToLine: ->

    @activeTabView.emit 'GoToLineRequested'


  closeTab: ->

    @activeTabView.removePane @activeTabView.getActivePane()


  registerIDEView: (ideView) ->

    @ideViews.push ideView
    ideView.mountedMachine = @mountedMachine

    ideView.on 'PaneRemoved', (pane) =>
      ideViewLength  = 0
      ideViewLength += ideView.tabView.panes.length  for ideView in @ideViews
      delete @generatedPanes[pane.view.hash]

      if session = pane.view.remote?.session
        @mountedMachine.getBaseKite().removeFromActiveSessions session

      @statusBar.showInformation()  if ideViewLength is 0
      @writeSnapshot()

    ideView.tabView.on 'PaneAdded', (pane) =>
      @registerPane pane
      @writeSnapshot()

    ideView.on 'ChangeHappened', (change) =>
      @syncChange change  if @rtm

    ideView.on 'UpdateWorkspaceSnapshot', =>
      @writeSnapshot()


  writeSnapshot: ->

    return  unless @isMachineRunning()

    name  = @getWorkspaceSnapshotName()
    value = @getWorkspaceSnapshot()

    @localStorageController.setValue name, value


  getWorkspaceSnapshotName: ->

    return "wss.#{@mountedMachine.uid}.#{@workspaceData.slug}"


  registerPane: (pane) ->

    {view} = pane
    unless view?.hash?
      return warn 'view.hash not found, returning'

    @generatedPanes or= {}
    @generatedPanes[view.hash] = yes

    view.on 'ChangeHappened', (change) =>
      @syncChange change  if @rtm


  forEachSubViewInIDEViews_: (callback = noop, paneType) ->

    if typeof callback is 'string'
      [paneType, callback] = [callback, paneType]

    for ideView in @ideViews
      for pane in ideView.tabView.panes when pane
        return  unless view = pane.getSubViews().first
        if paneType
        then callback view  if view.getOptions().paneType is paneType
        else callback view


  updateSettings: (component, key, value) ->

    # TODO: Refactor this method by passing component type to helper method.
    Class  = if component is 'editor' then IDEEditorPane else IDETerminalPane
    method = "set#{key.capitalize()}"

    if key is 'useAutosave' # autosave is special case, handled by app manager.
      return if value then @enableAutoSave() else @disableAutoSave()

    @forEachSubViewInIDEViews_ (view) ->
      if view instanceof Class
        if component is 'editor'
          view.aceView.ace[method]? value
        else
          view.webtermView.updateSettings()


  initiateAutoSave: ->

    {editorSettingsView} = @settingsPane

    editorSettingsView.on 'SettingsFetched', =>
      @enableAutoSave()  if editorSettingsView.settings.useAutosave


  enableAutoSave: ->

    @autoSaveInterval = kd.utils.repeat 1000, =>
      @forEachSubViewInIDEViews_ 'editor', (ep) => ep.handleAutoSave()


  disableAutoSave: -> kd.utils.killRepeat @autoSaveInterval


  showShortcutsView: ->

    { shortcutsController } = kd.singletons

    shortcutsController.showModal()


  getActivePaneView: ->

    return @activeTabView?.getActivePane()?.getSubViews().first
<commit_msg>'shortcutsController' singleton name is obsolete, use 'shortcuts'<commit_after>ndpane                        = require 'ndpane'
_                             = require 'underscore'
kd                            = require 'kd'
$                             = require 'jquery'
KDBlockingModalView           = kd.BlockingModalView
KDCustomHTMLView              = kd.CustomHTMLView
KDModalView                   = kd.ModalView
KDNotificationView            = kd.NotificationView
KDSplitView                   = kd.SplitView
KDSplitViewPanel              = kd.SplitViewPanel
remote                        = require('app/remote').getInstance()
globals                       = require 'globals'
nick                          = require 'app/util/nick'
showError                     = require 'app/util/showError'
whoami                        = require 'app/util/whoami'
Machine                       = require 'app/providers/machine'
KodingKontrol                 = require 'app/kite/kodingkontrol'
FSHelper                      = require 'app/util/fs/fshelper'
AppController                 = require 'app/appcontroller'
CollaborationController       = require './collaborationcontroller'
IDEContentSearch              = require './views/contentsearch/idecontentsearch'
IDEEditorPane                 = require './workspace/panes/ideeditorpane'
IDEFileFinder                 = require './views/filefinder/idefilefinder'
IDEFilesTabView               = require './views/tabview/idefilestabview'
IDEShortcutsView              = require './views/shortcutsview/ideshortcutsview'
IDEStatusBar                  = require './views/statusbar/idestatusbar'
IDEStatusBarMenu              = require './views/statusbar/idestatusbarmenu'
IDETerminalPane               = require './workspace/panes/ideterminalpane'
IDEView                       = require './views/tabview/ideview'
IDEWorkspace                  = require './workspace/ideworkspace'
splashMarkups                 = require './util/splashmarkups'
IDEApplicationTabView         = require './views/tabview/ideapplicationtabview'
AceFindAndReplaceView         = require 'ace/acefindandreplaceview'
EnvironmentsMachineStateModal = require 'app/providers/environmentsmachinestatemodal'
environmentDataProvider       = require 'app/userenvironmentdataprovider'

require('./routes')()


module.exports =

class IDEAppController extends AppController

  _.extend @prototype, CollaborationController

  {
    Stopped, Running, NotInitialized, Terminated, Unknown, Pending,
    Starting, Building, Stopping, Rebooting, Terminating, Updating
  } = Machine.State

  {noop, warn} = kd

  @options = require './ideappcontrolleroptions'

  constructor: (options = {}, data) ->
    options.appInfo =
      type          : 'application'
      name          : 'IDE'

    super options, data

    layoutOptions     =
      splitOptions    :
        direction     : 'vertical'
        name          : 'BaseSplit'
        sizes         : [ 250, null ]
        maximums      : [ 400, null ]
        views         : [
          {
            type      : 'custom'
            name      : 'filesPane'
            paneClass : IDEFilesTabView
          },
          {
            type      : 'custom'
            name      : 'editorPane'
            paneClass : IDEView
          }
        ]

    $('body').addClass 'dark' # for theming

    @workspace = new IDEWorkspace { layoutOptions }
    @ideViews  = []

    # todo:
    # - following two should be abstracted out into a separate api
    @layout = ndpane(16)
    @layoutMap = new Array(16*16)

    {windowController} = kd.singletons
    windowController.addFocusListener @bound 'setActivePaneFocus'

    @workspace.once 'ready', => @getView().addSubView @workspace.getView()

    kd.singletons.appManager.on 'AppIsBeingShown', (app) =>

      return  unless app instanceof IDEAppController

      @setActivePaneFocus on

      # Temporary fix for IDE is not shown after
      # opening pages which uses old SplitView.
      # TODO: This needs to be fixed. ~Umut
      kd.singletons.windowController.notifyWindowResizeListeners()

      @resizeActiveTerminalPane()

    @localStorageController = kd.getSingleton('localStorageController').storage 'IDE'


  prepareIDE: ->

    panel     = @workspace.getView()
    appView   = @getView()
    ideView   = panel.getPaneByName 'editorPane'

    @setActiveTabView ideView.tabView
    @registerIDEView  ideView

    splitViewPanel = ideView.parent.parent
    @createStatusBar splitViewPanel
    @createFindAndReplaceView splitViewPanel

    appView.emit 'KeyViewIsSet'

    @createInitialView()
    @bindCollapseEvents()

    {@finderPane, @settingsPane} = @workspace.panel.getPaneByName 'filesPane'

    @bindRouteHandler()
    @initiateAutoSave()
    @emit 'ready'


  bindRouteHandler: ->

    {router, mainView} = kd.singletons

    router.on 'RouteInfoHandled', (routeInfo) =>
      if routeInfo.path.indexOf('/IDE') is -1
        if mainView.isSidebarCollapsed
          mainView.toggleSidebar()


  bindCollapseEvents: ->

    { panel } = @workspace

    filesPane = @workspace.panel.getPaneByName 'filesPane'

    # We want double click to work
    # if only the sidebar is collapsed. ~Umut
    expand = (event) =>
      kd.utils.stopDOMEvent event  if event?
      @toggleSidebar()  if @isSidebarCollapsed

    filesPane.on 'TabHandleMousedown', expand

    baseSplit = panel.layout.getSplitViewByName 'BaseSplit'
    baseSplit.resizer.on 'dblclick', @bound 'toggleSidebar'


  setActiveTabView: (tabView) ->

    return  if tabView is @activeTabView
    @setActivePaneFocus off
    @activeTabView = tabView
    @setActivePaneFocus on


  setActivePaneFocus: (state) ->

    return  unless pane = @getActivePaneView()
    return  if pane is @activePaneView

    @activePaneView = pane

    kd.utils.defer -> pane.setFocus? state


  splitTabView: (type = 'vertical', ideViewOptions) ->

    ideView        = @activeTabView.parent
    ideParent      = ideView.parent
    newIDEView     = new IDEView ideViewOptions

    splitViewPanel = @activeTabView.parent.parent
    if splitViewPanel instanceof KDSplitViewPanel
    then layout = splitViewPanel._layout
    else layout = @layout

    @activeTabView = null

    ideView.detach()

    splitView   = new KDSplitView
      type      : type
      views     : [ null, newIDEView ]

    layout.split(type is 'vertical')
    splitView._layout = layout

    @registerIDEView newIDEView

    splitView.once 'viewAppended', =>
      splitView.panels.first.attach ideView
      splitView.panels[0] = ideView.parent
      splitView.options.views[0] = ideView
      splitView.panels.forEach (panel, i) =>
        leaf = layout.leafs[i]
        panel._layout = leaf
        @layoutMap[leaf.data.offset] = panel

    ideParent.addSubView splitView
    @setActiveTabView newIDEView.tabView

    splitView.on 'ResizeDidStop', kd.utils.throttle 500, @bound 'doResize'


  mergeSplitView: ->

    panel     = @activeTabView.parent.parent
    splitView = panel.parent
    {parent}  = splitView

    return  unless panel instanceof KDSplitViewPanel

    if parent instanceof KDSplitViewPanel
      parentSplitView    = parent.parent
      panelIndexInParent = parentSplitView.panels.indexOf parent

    splitView.once 'SplitIsBeingMerged', (views) =>
      for view in views
        index = @ideViews.indexOf view
        @ideViews.splice index, 1

      @layoutMap[splitView._layout.data.offset] = parent

      @handleSplitMerge views, parent, parentSplitView, panelIndexInParent
      @doResize()

    splitView._layout.leafs.forEach (leaf) =>
      @layoutMap[leaf.data.offset] = null
    splitView._layout.merge()

    splitView.merge()


  handleSplitMerge: (views, container, parentSplitView, panelIndexInParent) ->

    ideView = new IDEView createNewEditor: no
    panes   = []

    for view in views
      {tabView} = view

      for p in tabView.panes by -1
        {pane} = tabView.removePane p, yes, (yes if tabView instanceof IDEApplicationTabView)
        panes.push pane

      view.destroy()

    container.addSubView ideView

    for pane in panes
      ideView.tabView.addPane pane

    @setActiveTabView ideView.tabView
    @registerIDEView ideView

    if parentSplitView and panelIndexInParent
      parentSplitView.options.views[panelIndexInParent] = ideView
      parentSplitView.panels[panelIndexInParent]        = ideView.parent


  openFile: (file, contents, callback = noop, emitChange) ->

    @activeTabView.emit 'FileNeedsToBeOpened', file, contents, callback, emitChange


  openMachineTerminal: (machineData) ->

    @activeTabView.emit 'MachineTerminalRequested', machineData


  openMachineWebPage: (machineData) ->

    @activeTabView.emit 'MachineWebPageRequested', machineData


  mountMachine: (machineData) ->

    # interrupt if workspace was changed
    return if machineData.uid isnt @workspaceData.machineUId

    panel        = @workspace.getView()
    filesPane    = panel.getPaneByName 'filesPane'

    path = @workspaceData?.rootPath

    path ?= if owner = machineData.getOwner()
    then "/home/#{owner}"
    else '/'

    filesPane.emit 'MachineMountRequested', machineData, path


  unmountMachine: (machineData) ->

    panel     = @workspace.getView()
    filesPane = panel.getPaneByName 'filesPane'

    filesPane.emit 'MachineUnmountRequested', machineData


  isMachineRunning: ->

    return @mountedMachine.status.state is Running


  createInitialView: ->

    kd.utils.defer =>
      @splitTabView 'horizontal', createNewEditor: no
      @getMountedMachine (err, machine) =>

        return unless machine

        machine = new Machine { machine }  unless machine instanceof Machine

        for ideView in @ideViews
          ideView.mountedMachine = @mountedMachine

        unless @isMachineRunning()
          nickname     = nick()
          machineLabel = machine.slug or machine.label
          splashes     = splashMarkups

          @fakeEditor       = @ideViews.first.createEditor()
          @fakeTabView      = @activeTabView
          fakeTerminalView  = new KDCustomHTMLView partial: splashes.getTerminal nickname
          @fakeTerminalPane = @fakeTabView.parent.createPane_ fakeTerminalView, { name: 'Terminal' }
          @fakeFinderView   = new KDCustomHTMLView partial: splashes.getFileTree nickname, machineLabel

          @finderPane.addSubView @fakeFinderView, '.nfinder .jtreeview-wrapper'
          @fakeEditor.once 'EditorIsReady', => kd.utils.defer => @fakeEditor.setFocus no

        else
          snapshot = @localStorageController.getValue @getWorkspaceSnapshotName()

          if snapshot
            @resurrectLocalSnapshot snapshot
          else
            @ideViews.first.createEditor()
            @ideViews.last.createTerminal { machine }
            @setActiveTabView @ideViews.first.tabView
            @forEachSubViewInIDEViews_ (pane) ->
              pane.isInitial = yes


  setMountedMachine: (machine) ->

    @mountedMachine = machine
    @emit 'MachineDidMount', machine, @workspaceData


  whenMachineReady: (callback) ->

    if @mountedMachine
    then callback @mountedMachine, @workspaceData
    else @once 'MachineDidMount', callback


  getMountedMachine: (callback = noop) ->

    return callback()  unless @mountedMachineUId

    kd.utils.defer =>
      environmentDataProvider.fetchMachineByUId @mountedMachineUId, (machine, ws) =>
        machine = new Machine { machine }  unless machine instanceof Machine
        @setMountedMachine machine

        callback null, machine


  mountMachineByMachineUId: (machineUId) ->

    computeController = kd.getSingleton 'computeController'
    container         = @getView()

    environmentDataProvider.fetchMachineByUId machineUId, (machineItem) =>

      unless machineItem
        return @createMachineStateModal { state: 'NotFound', container }

      unless machineItem instanceof Machine
        machineItem = new Machine machine: machineItem

      @setMountedMachine machineItem

      @prepareIDE()

      callback = =>

        if machineItem
          {state}         = machineItem.status
          machineId       = machineItem._id
          baseMachineKite = machineItem.getBaseKite()
          isKiteConnected = baseMachineKite._state is 1


          if state is Running and isKiteConnected
            @mountMachine machineItem
            baseMachineKite.fetchTerminalSessions()

          else
            unless @machineStateModal

              @createMachineStateModal {
                state, container, machineItem, initial: yes
              }

              if state is NotInitialized
                @machineStateModal.once 'MachineTurnOnStarted', =>
                  kd.getSingleton('mainView').activitySidebar.initiateFakeCounter()

          @prepareCollaboration()
          @bindMachineEvents machineItem

        else
          @createMachineStateModal { state: 'NotFound', container }


      @appStorage = kd.getSingleton('appStorageController').storage 'IDE', '1.0.0'
      @appStorage.fetchStorage =>

        isOnboardingModalShown = @appStorage.getValue 'isOnboardingModalShown'

        callback()


  bindMachineEvents: (machineItem) ->

    actionRequiredStates = [Pending, Stopping, Stopped, Terminating, Terminated]

    kd.getSingleton 'computeController'

      .on "public-#{machineItem._id}", (event) =>

        if event.status in actionRequiredStates
          @showStateMachineModal machineItem, event

      .on "reinit-#{machineItem._id}", @bound 'handleMachineReinit'


  handleMachineReinit: ({status}) ->

    switch status
      when 'Building'
        environmentDataProvider.ensureDefaultWorkspace kd.noop
      when 'Running'
        @quit()


  showStateMachineModal: (machineItem, event) ->

    KodingKontrol.dcNotification?.destroy()
    KodingKontrol.dcNotification = null

    machineItem.getBaseKite( no ).disconnect()

    if @machineStateModal
      @machineStateModal.updateStatus event
    else
      {state}   = machineItem.status
      container = @getView()
      @createMachineStateModal { state, container, machineItem }


  createMachineStateModal: (options = {}) ->

    { mainView } = kd.singletons
    mainView.toggleSidebar()  if mainView.isSidebarCollapsed

    { state, container, machineItem, initial } = options

    container   ?= @getView()
    modalOptions = { state, container, initial }
    @machineStateModal = new EnvironmentsMachineStateModal modalOptions, machineItem

    @machineStateModal.once 'KDObjectWillBeDestroyed', => @machineStateModal = null
    @machineStateModal.once 'IDEBecameReady',          => @handleIDEBecameReady machineItem


  collapseSidebar: ->

    panel        = @workspace.getView()
    splitView    = panel.layout.getSplitViewByName 'BaseSplit'
    floatedPanel = splitView.panels.first
    filesPane    = panel.getPaneByName 'filesPane'
    {tabView}    = filesPane
    desiredSize  = 250

    splitView.resizePanel 39, 0
    @getView().setClass 'sidebar-collapsed'
    floatedPanel.setClass 'floating'
    @activeFilesPaneName = tabView.activePane.name
    tabView.showPaneByName 'Dummy'

    @isSidebarCollapsed = yes

    tabView.on 'PaneDidShow', (pane) ->
      return if pane.options.name is 'Dummy'
      @expandSidebar()  if @isSidebarCollapsed


  expandSidebar: ->

    panel        = @workspace.getView()
    splitView    = panel.layout.getSplitViewByName 'BaseSplit'
    floatedPanel = splitView.panels.first
    filesPane    = panel.getPaneByName 'filesPane'

    splitView.resizePanel 250, 0
    @getView().unsetClass 'sidebar-collapsed'
    floatedPanel.unsetClass 'floating'
    @isSidebarCollapsed = no
    filesPane.tabView.showPaneByName @activeFilesPaneName


  toggleSidebar: ->

    if @isSidebarCollapsed then @expandSidebar() else @collapseSidebar()


  splitVertically: ->

    @splitTabView 'vertical'


  splitHorizontally: ->

    @splitTabView 'horizontal'

  createNewFile: do ->
    newFileSeed = 1

    return ->
      path     = "localfile:/Untitled-#{newFileSeed++}.txt@#{Date.now()}"
      file     = FSHelper.createFileInstance { path, machine: @mountedMachine }
      contents = ''

      @openFile file, contents


  createNewTerminal: (options={}) ->

    { machine, path, resurrectSessions } = options

    machine = @mountedMachine  unless machine instanceof Machine

    if @workspaceData and not path
      { rootPath, isDefault } = @workspaceData
      options.path = rootPath  if rootPath and not isDefault

    @activeTabView.emit 'TerminalPaneRequested', options


  createNewBrowser: (url) ->

    url = ''  unless typeof url is 'string'

    @activeTabView.emit 'PreviewPaneRequested', url


  createNewDrawing: (paneHash) ->

    paneHash = null unless typeof paneHash is 'string'

    @activeTabView.emit 'DrawingPaneRequested', paneHash


  moveTab: (direction) ->

    tabView = @activeTabView
    return unless tabView.parent?

    panel = tabView.parent.parent
    return  unless panel instanceof KDSplitViewPanel

    targetOffset = @layout[direction](panel._layout.data.offset)
    return  unless targetOffset?

    targetPanel = @layoutMap[targetOffset]

    {pane} = tabView.removePane tabView.getActivePane(), yes, yes

    targetPanel.subViews.first.tabView.addPane pane
    @setActiveTabView targetPanel.subViews.first.tabView
    @doResize()


  moveTabUp: -> @moveTab 'north'

  moveTabDown: -> @moveTab 'south'

  moveTabLeft: -> @moveTab 'west'

  moveTabRight: -> @moveTab 'east'


  goToLeftTab: ->

    index = @activeTabView.getActivePaneIndex()
    return if index is 0

    @activeTabView.showPaneByIndex index - 1


  goToRightTab: ->

    index = @activeTabView.getActivePaneIndex()
    return if index is @activeTabView.length - 1

    @activeTabView.showPaneByIndex index + 1


  goToTabNumber: (index) ->

    @activeTabView.showPaneByIndex index


  goToLine: ->

    @activeTabView.emit 'GoToLineRequested'


  closeTab: ->

    @activeTabView.removePane @activeTabView.getActivePane()


  registerIDEView: (ideView) ->

    @ideViews.push ideView
    ideView.mountedMachine = @mountedMachine

    ideView.on 'PaneRemoved', (pane) =>
      ideViewLength  = 0
      ideViewLength += ideView.tabView.panes.length  for ideView in @ideViews
      delete @generatedPanes[pane.view.hash]

      if session = pane.view.remote?.session
        @mountedMachine.getBaseKite().removeFromActiveSessions session

      @statusBar.showInformation()  if ideViewLength is 0
      @writeSnapshot()

    ideView.tabView.on 'PaneAdded', (pane) =>
      @registerPane pane
      @writeSnapshot()

    ideView.on 'ChangeHappened', (change) =>
      @syncChange change  if @rtm

    ideView.on 'UpdateWorkspaceSnapshot', =>
      @writeSnapshot()


  writeSnapshot: ->

    return  unless @isMachineRunning()

    name  = @getWorkspaceSnapshotName()
    value = @getWorkspaceSnapshot()

    @localStorageController.setValue name, value


  getWorkspaceSnapshotName: ->

    return "wss.#{@mountedMachine.uid}.#{@workspaceData.slug}"


  registerPane: (pane) ->

    {view} = pane
    unless view?.hash?
      return warn 'view.hash not found, returning'

    @generatedPanes or= {}
    @generatedPanes[view.hash] = yes

    view.on 'ChangeHappened', (change) =>
      @syncChange change  if @rtm


  forEachSubViewInIDEViews_: (callback = noop, paneType) ->

    if typeof callback is 'string'
      [paneType, callback] = [callback, paneType]

    for ideView in @ideViews
      for pane in ideView.tabView.panes when pane
        return  unless view = pane.getSubViews().first
        if paneType
        then callback view  if view.getOptions().paneType is paneType
        else callback view


  updateSettings: (component, key, value) ->

    # TODO: Refactor this method by passing component type to helper method.
    Class  = if component is 'editor' then IDEEditorPane else IDETerminalPane
    method = "set#{key.capitalize()}"

    if key is 'useAutosave' # autosave is special case, handled by app manager.
      return if value then @enableAutoSave() else @disableAutoSave()

    @forEachSubViewInIDEViews_ (view) ->
      if view instanceof Class
        if component is 'editor'
          view.aceView.ace[method]? value
        else
          view.webtermView.updateSettings()


  initiateAutoSave: ->

    {editorSettingsView} = @settingsPane

    editorSettingsView.on 'SettingsFetched', =>
      @enableAutoSave()  if editorSettingsView.settings.useAutosave


  enableAutoSave: ->

    @autoSaveInterval = kd.utils.repeat 1000, =>
      @forEachSubViewInIDEViews_ 'editor', (ep) => ep.handleAutoSave()


  disableAutoSave: -> kd.utils.killRepeat @autoSaveInterval


  showShortcutsView: ->

    { shortcuts } = kd.singletons

    shortcuts.showModal()


  getActivePaneView: ->

    return @activeTabView?.getActivePane()?.getSubViews().first

{'fraction_non_alphanumeric': 0.08973805411629246, 'fraction_numerical': 0.002158894645941278, 'mean_word_length': 2.754074741107609, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 179, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6689419', 'n_tokens_mistral': 12340, 'n_tokens_neox': 11597, 'n_words': 2622}