"""
YouCompleteMe extra configuration for Platformio based
projects.

Based on the `.ycm_extra_conf.py` by @ladislas in his Bare-Arduino-Project.

Anthony Ford <github.com/ajford>

"""
import os
import ycm_core
import logging

# Logger for additional logging.
# To enable debug logging, add `let g:ycm_server_log_level = 'debug'` to
# your .vimrc file.
logger = logging.getLogger('ycm-extra-conf')

# Platformio Autogen libs.
## Platformio automatically copies over the libs you use after your first run.
## Be warned that you will not receive autocompletion on libraries until after
## your first `platformio run`.
PlatformioAutogen = ".pioenvs/"

# All Platformio Arduin Libs
## This will link directly to the Platformio Libs for Arduino.
## Be warned that this can polute your namespace (in #include)
## and slightly increase startup time (while crawling the lib
## dir for header files). This will however allow you to
## complete for header files you haven't included yet.
PlatformioArduinoLibs = "~/.platformio/packages/framework-arduinoavr/libraries/"

# Platformio Arduino Core
## This links to the Platformio Arduino Cores. This provides 
## the core libs, such as Arduino.h and HardwareSerial.h
PlatformioArduinoCore = "~/.platformio/packages/framework-arduinoavr/cores/arduino/"

# Platformio Arduino Std Libs
## Arduino Std libs from .platformio packages. Provides stdlib.h and such.
PlatformioArduinoSTD = '~/.platformio/packages/toolchain-atmelavr/avr/include'

# This is the list of all directories to search for header files.
# Dirs in this list can be paths relative to this file, absolute
# paths, or paths relative to the user (using ~/path/to/file).
libDirs = [
           "lib"
           ,PlatformioAutogen
           ,PlatformioArduinoCore
           ,PlatformioArduinoLibs
           ,PlatformioArduinoSTD
           ]

flags = [
    # General flags
    '-Wall'
    ,'-x'
    ,'c++'
    ,'-ansi'

    # Customize microcontroler and Arduino version
    ,'-mmcu=atmega328p'
    ,'-DF_CPU=16000000L'
    ,'-DARDUINO_ARCH_AVR'
    ,'-DARDUINO_AVR_DUEMILANOVE'
    ,'-DARDUINO=106000'
    # ,'-MMD -DUSB_VID=null'
    # ,'-DUSB_PID=null'
]


compilation_database_folder = ''

if os.path.exists( compilation_database_folder ):
    database = ycm_core.CompilationDatabase( compilation_database_folder )
else:
    database = None

SOURCE_EXTENSIONS = [ '.cpp', '.cxx', '.cc', '.c', '.ino', '.m', '.mm' ]

def DirectoryOfThisScript():
    return os.path.dirname( os.path.abspath( __file__ ) )

def MakeRelativePathsInFlagsAbsolute( flags, working_directory ):
    if not working_directory:
        return list( flags )

    new_flags = []
    make_next_absolute = False
    path_flags = [ '-isystem', '-I', '-iquote', '--sysroot=' ]

    for libDir in libDirs:

        # dir is relative to $HOME
        if libDir.startswith('~'):
            libDir = os.path.expanduser(libDir)

        # dir is relative to `working_directory`
        if not libDir.startswith('/'):
            libDir = os.path.join(working_directory,libDir)

        # Else, assume dir is absolute

        for path, dirs, files in os.walk(libDir):
            # Add to flags if dir contains a header file and is not
            # one of the metadata dirs (examples and extras). 
            if any(IsHeaderFile(x) for x in files) and\
              path.find("examples") is -1 and path.find("extras") is -1:
                logger.debug("Directory contains header files - %s"%path)
                flags.append('-I'+path)


    for flag in flags:
        new_flag = flag

        if make_next_absolute:
            make_next_absolute = False
            if not flag.startswith( '/' ):
                new_flag = os.path.join( working_directory, flag )

        for path_flag in path_flags:
            if flag == path_flag:
                make_next_absolute = True
                break

            if flag.startswith( path_flag ):
                path = flag[ len( path_flag ): ]
                new_flag = path_flag + os.path.join( working_directory, path )
                break

        if new_flag:
            new_flags.append( new_flag )
    return new_flags


def IsHeaderFile( filename ):
    extension = os.path.splitext( filename )[ 1 ]
    return extension in [ '.h', '.hxx', '.hpp', '.hh' ]


def GetCompilationInfoForFile( filename ):
    # The compilation_commands.json file generated by CMake does not have entries
    # for header files. So we do our best by asking the db for flags for a
    # corresponding source file, if any. If one exists, the flags for that file
    # should be good enough.
    if IsHeaderFile( filename ):
        basename = os.path.splitext( filename )[ 0 ]
        for extension in SOURCE_EXTENSIONS:
            replacement_file = basename + extension
            if os.path.exists( replacement_file ):
                compilation_info = database.GetCompilationInfoForFile(
                    replacement_file )
                if compilation_info.compiler_flags_:
                    return compilation_info
        return None
    return database.GetCompilationInfoForFile( filename )


def FlagsForFile( filename, **kwargs ):
    if database:
        # Bear in mind that compilation_info.compiler_flags_ does NOT return a
        # python list, but a "list-like" StringVec object
        compilation_info = GetCompilationInfoForFile( filename )
        if not compilation_info:
            return None

        final_flags = MakeRelativePathsInFlagsAbsolute(
            compilation_info.compiler_flags_,
            compilation_info.compiler_working_dir_ )

        # NOTE: This is just for YouCompleteMe. it's highly likely that your project
        # does NOT need to remove the stdlib flag. DO NOT USE THIS IN YOUR
        # ycm_extra_conf IF YOU'RE NOT 100% SURE YOU NEED IT.
        #try:
        #    final_flags.remove( '-stdlib=libc++' )
        #except ValueError:
        #    pass
    else:
        relative_to = DirectoryOfThisScript()
        final_flags = MakeRelativePathsInFlagsAbsolute( flags, relative_to )

    return {
        'flags': final_flags,
        'do_cache': True
    }