Package = require 'package'
fs = require 'fs'
plist = require 'plist'
_ = require 'underscore'
TextMateGrammar = require 'text-mate-grammar'

module.exports =
class TextMatePackage extends Package
  @testName: (packageName) ->
    /(\.|_|-)tmbundle$/.test(packageName)

  @cssSelectorFromScopeSelector: (scopeSelector) ->
    scopeSelector.split(', ').map((commaFragment) ->
      commaFragment.split(' ').map((spaceFragment) ->
        spaceFragment.split('.').map((dotFragment) ->
          '.' + dotFragment.replace(/\+/g, '\\+')
        ).join('')
      ).join(' ')
    ).join(', ')

  constructor: ->
    super
    @preferencesPath = fs.join(@path, "Preferences")
    @syntaxesPath = fs.join(@path, "Syntaxes")
    @grammars = []

  load: ->
    try
      @loadGrammars()
    catch e
      console.warn "Failed to load package at '#{@path}'", e.stack
    this

  getGrammars: -> @grammars

  readGrammars: ->
    grammars = []
    for grammarPath in fs.list(@syntaxesPath)
      try
        grammars.push(TextMateGrammar.readFromPath(grammarPath))
      catch e
        console.warn "Failed to load grammar at path '#{grammarPath}'", e.stack
    grammars

  addGrammar: (rawGrammar) ->
    grammar = new TextMateGrammar(rawGrammar)
    @grammars.push(grammar)
    syntax.addGrammar(grammar)

  loadGrammars: (rawGrammars) ->
    rawGrammars = @readGrammars() unless rawGrammars?

    @grammars = []
    @addGrammar(rawGrammar) for rawGrammar in rawGrammars
    @loadScopedProperties()

  loadScopedProperties: ->
    for { selector, properties } in @getScopedProperties()
      syntax.addProperties(selector, properties)

  getScopedProperties: ->
    scopedProperties = []

    for grammar in @getGrammars()
      if properties = @propertiesFromTextMateSettings(grammar)
        selector = @cssSelectorFromScopeSelector(grammar.scopeName)
        scopedProperties.push({selector, properties})

    for {scope, settings} in @getTextMatePreferenceObjects()
      if properties = @propertiesFromTextMateSettings(settings)
        selector = @cssSelectorFromScopeSelector(scope) if scope?
        scopedProperties.push({selector, properties})

    scopedProperties

  readObjectFromPath: (path, callback) ->
    object = null
    error = null
    if fs.isObjectPath(path)
      object = fs.readObject(path)
    else
      plist.parseString fs.read(path), (e, data) ->
        error = e
        object = data[0]
    error = throw new Error("Failed to load object at path `#{path}`") unless object
    callback(error, object)

  getTextMatePreferenceObjects: ->
    preferenceObjects = []
    if fs.exists(@preferencesPath)
      for preferencePath in fs.list(@preferencesPath)
        @readObjectFromPath preferencePath, (e, object) =>
          if e
            console.warn "Failed to parse preference at path '#{preferencePath}'", e.stack
          else
            preferenceObjects.push(object)
    preferenceObjects

  propertiesFromTextMateSettings: (textMateSettings) ->
    if textMateSettings.shellVariables
      shellVariables = {}
      for {name, value} in textMateSettings.shellVariables
        shellVariables[name] = value
      textMateSettings.shellVariables = shellVariables

    editorProperties = _.compactObject(
      commentStart: _.valueForKeyPath(textMateSettings, 'shellVariables.TM_COMMENT_START')
      commentEnd: _.valueForKeyPath(textMateSettings, 'shellVariables.TM_COMMENT_END')
      increaseIndentPattern: textMateSettings.increaseIndentPattern
      decreaseIndentPattern: textMateSettings.decreaseIndentPattern
      foldEndPattern: textMateSettings.foldingStopMarker
    )
    { editor: editorProperties } if _.size(editorProperties) > 0

  cssSelectorFromScopeSelector: (scopeSelector) ->
    @constructor.cssSelectorFromScopeSelector(scopeSelector)
