#!/usr/bin/env python
#
# Copyright (C) 2011, 2012  Strahinja Val Markovic  <val@markovic.io>
#
# This file is part of YouCompleteMe.
#
# YouCompleteMe is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# YouCompleteMe is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with YouCompleteMe.  If not, see <http://www.gnu.org/licenses/>.

import os
import vim
import subprocess
import tempfile
import json
from ycm import vimsupport
from ycm import utils
from ycm.completers.all.omni_completer import OmniCompleter
from ycm.completers.general import syntax_parse
from ycm.client.base_request import BaseRequest, BuildRequestData
from ycm.client.command_request import SendCommandRequest
from ycm.client.completion_request import CompletionRequest
from ycm.client.event_notification import SendEventNotificationAsync

try:
  from UltiSnips import UltiSnips_Manager
  USE_ULTISNIPS_DATA = True
except ImportError:
  USE_ULTISNIPS_DATA = False

SERVER_PORT_RANGE_START = 10000

class YouCompleteMe( object ):
  def __init__( self, user_options ):
    self._user_options = user_options
    self._omnicomp = OmniCompleter( user_options )
    self._current_completion_request = None
    self._server_stdout = None
    self._server_stderr = None
    self._server_popen = None
    self._filetypes_with_keywords_loaded = set()
    self._temp_options_filename = None
    self._SetupServer()


  def _SetupServer( self ):
    server_port = SERVER_PORT_RANGE_START + os.getpid()
    with tempfile.NamedTemporaryFile( delete = False ) as options_file:
      self._temp_options_filename = options_file.name
      json.dump( dict( self._user_options ), options_file )
      command = ''.join( [ 'python ',
                          _PathToServerScript(),
                          ' --port=',
                          str( server_port ),
                          ' --options_file=',
                          options_file.name,
                          ' --log=',
                          self._user_options[ 'server_log_level' ] ] )

      BaseRequest.server_location = 'http://localhost:' + str( server_port )

      if self._user_options[ 'server_use_vim_stdout' ]:
        self._server_popen = subprocess.Popen( command, shell = True )
      else:
        filename_format = os.path.join( utils.PathToTempDir(),
                                        'server_{port}_{std}.log' )

        self._server_stdout = filename_format.format( port = server_port,
                                                      std = 'stdout' )
        self._server_stderr = filename_format.format( port = server_port,
                                                      std = 'stderr' )

        with open( self._server_stderr, 'w' ) as fstderr:
          with open( self._server_stdout, 'w' ) as fstdout:
            self._server_popen = subprocess.Popen( command,
                                                   stdout = fstdout,
                                                   stderr = fstderr,
                                                   shell = True )


  def CreateCompletionRequest( self ):
    # We have to store a reference to the newly created CompletionRequest
    # because VimScript can't store a reference to a Python object across
    # function calls... Thus we need to keep this request somewhere.
    self._current_completion_request = CompletionRequest()
    return self._current_completion_request


  def SendCommandRequest( self, arguments, completer ):
    return SendCommandRequest( arguments, completer )


  def GetDefinedSubcommands( self ):
    return BaseRequest.PostDataToHandler( BuildRequestData(),
                                          'defined_subcommands' )


  def GetCurrentCompletionRequest( self ):
    return self._current_completion_request


  def GetOmniCompleter( self ):
    return self._omnicomp


  def NativeFiletypeCompletionAvailable( self ):
    try:
      return BaseRequest.PostDataToHandler( BuildRequestData(),
                                            'filetype_completion_available')
    except:
      return False


  # TODO: This may not be needed at all when the server is ready. Evaluate this
  # later.
  # def FiletypeCompletionAvailable( self ):
  #   return bool( self.GetFiletypeCompleter() )


  def NativeFiletypeCompletionUsable( self ):
    return ( self.CurrentFiletypeCompletionEnabled() and
             self.NativeFiletypeCompletionAvailable() )


  # TODO: This may not be needed at all when the server is ready. Evaluate this
  # later.
  # def FiletypeCompletionUsable( self ):
  #   return ( self.CurrentFiletypeCompletionEnabled() and
  #            self.FiletypeCompletionAvailable() )


  def OnFileReadyToParse( self ):
    extra_data = {}
    if self._user_options[ 'collect_identifiers_from_tags_files' ]:
      extra_data[ 'tag_files' ] = _GetTagFiles()

    if self._user_options[ 'seed_identifiers_with_syntax' ]:
      self._AddSyntaxDataIfNeeded( extra_data )

    SendEventNotificationAsync( 'FileReadyToParse', extra_data )


  def OnBufferUnload( self, deleted_buffer_file ):
    SendEventNotificationAsync( 'BufferUnload',
                                { 'unloaded_buffer': deleted_buffer_file } )


  def OnBufferVisit( self ):
    extra_data = {}
    _AddUltiSnipsDataIfNeeded( extra_data )
    SendEventNotificationAsync( 'BufferVisit', extra_data )


  def OnInsertLeave( self ):
    SendEventNotificationAsync( 'InsertLeave' )


  def OnVimLeave( self ):
    self._server_popen.terminate()
    os.remove( self._temp_options_filename )


  def OnCurrentIdentifierFinished( self ):
    SendEventNotificationAsync( 'CurrentIdentifierFinished' )


  # TODO: Make this work again.
  def DiagnosticsForCurrentFileReady( self ):
    # if self.FiletypeCompletionUsable():
    #   return self.GetFiletypeCompleter().DiagnosticsForCurrentFileReady()
    return False


  # TODO: Make this work again.
  def GetDiagnosticsForCurrentFile( self ):
    # if self.FiletypeCompletionUsable():
    #   return self.GetFiletypeCompleter().GetDiagnosticsForCurrentFile()
    return []


  # TODO: Make this work again.
  def GetDetailedDiagnostic( self ):
    # if self.FiletypeCompletionUsable():
    #   return self.GetFiletypeCompleter().GetDetailedDiagnostic()
    pass


  # TODO: Make this work again.
  def GettingCompletions( self ):
    # if self.FiletypeCompletionUsable():
    #   return self.GetFiletypeCompleter().GettingCompletions()
    return False


  def DebugInfo( self ):
    debug_info = BaseRequest.PostDataToHandler( BuildRequestData(),
                                                'debug_info' )
    debug_info += '\nServer running at: {}'.format(
        BaseRequest.server_location )
    if self._server_stderr or self._server_stdout:
      debug_info += '\nServer logfiles:\n  {}\n  {}'.format(
        self._server_stdout,
        self._server_stderr )

    return debug_info


  def CurrentFiletypeCompletionEnabled( self ):
    filetypes = vimsupport.CurrentFiletypes()
    filetype_to_disable = self._user_options[
      'filetype_specific_completion_to_disable' ]
    return not all([ x in filetype_to_disable for x in filetypes ])


  def _AddSyntaxDataIfNeeded( self, extra_data ):
    filetype = vimsupport.CurrentFiletypes()[ 0 ]
    if filetype in self._filetypes_with_keywords_loaded:
      return

    self._filetypes_with_keywords_loaded.add( filetype )
    extra_data[ 'syntax_keywords' ] = list(
       syntax_parse.SyntaxKeywordsForCurrentBuffer() )


def _GetTagFiles():
  tag_files = vim.eval( 'tagfiles()' )
  current_working_directory = os.getcwd()
  return [ os.path.join( current_working_directory, x ) for x in tag_files ]


def _PathToServerScript():
  dir_of_current_script = os.path.dirname( os.path.abspath( __file__ ) )
  return os.path.join( dir_of_current_script, 'server/ycmd.py' )


def _AddUltiSnipsDataIfNeeded( extra_data ):
  if not USE_ULTISNIPS_DATA:
    return

  try:
    rawsnips = UltiSnips_Manager._snips( '', 1 )
  except:
    return

  # UltiSnips_Manager._snips() returns a class instance where:
  # class.trigger - name of snippet trigger word ( e.g. defn or testcase )
  # class.description - description of the snippet
  extra_data[ 'ultisnips_snippets' ] = [ { 'trigger': x.trigger,
                                           'description': x.description
                                         } for x in rawsnips ]


