# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
#
# Copyright (c) 2008 - 2012 by Wilbert Berendsen
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
# See http://www.gnu.org/licenses/ for more information.

"""
Computes and caches various information about files.
"""

from __future__ import unicode_literals

import itertools
import os

import ly.document
import lydocinfo
import ly.lex
import filecache
import util
import variables


_docinfo_cache = filecache.FileCache()


def docinfo(filename):
    """Return a (cached) LyDocInfo instance for the specified file."""
    filename = os.path.realpath(filename)
    try:
        return _docinfo_cache[filename]
    except KeyError:
        pass
    with open(filename) as f:
        text = util.decode(f.read())
    v = variables.variables(text)
    doc = ly.document.Document(text, v.get("mode"))
    doc.filename = filename
    info = _docinfo_cache[filename] = lydocinfo.DocInfo(doc, v)
    return info


def textmode(text, guess=True):
    """Returns the type of the given text ('lilypond, 'html', etc.).
    
    Checks the mode variable and guesses otherwise if guess is True.
    
    """
    mode = variables.variables(text).get("mode")
    if mode in ly.lex.modes:
        return mode
    if guess:
        return ly.lex.guessMode(text)


def includefiles(dinfo, include_path=()):
    """Returns a set of filenames that are included by the DocInfo's document.
        
    The specified include path is used to find files. The own filename 
    is NOT added to the set. Included files are checked recursively, 
    relative to our file, relative to the including file, and if that 
    still yields no file, relative to the directories in the include_path.
    
    If the document has no local filename, only the include_path is 
    searched for files.
    
    """
    filename = dinfo.document.filename
    basedir = os.path.dirname(filename) if filename else None
    files = set()
    
    def tryarg(directory, arg):
        path = os.path.realpath(os.path.join(directory, arg))
        if path not in files and os.path.isfile(path):
            files.add(path)
            args = docinfo(path).include_args()
            find(args, os.path.dirname(path))
            return True
            
    def find(incl_args, directory):
        for arg in incl_args:
            # new, recursive, relative include
            if not (directory and tryarg(directory, arg)):
                # old include (relative to master file)
                if not (basedir and tryarg(basedir, arg)):
                    # if path is given, also search there:
                    for p in include_path:
                        if tryarg(p, arg):
                            break
    
    find(dinfo.include_args(), basedir)
    return files


def basenames(dinfo, includefiles=(), filename=None):
    """Returns the list of basenames a document is expected to create.
    
    The list is created based on includefiles and the define output-suffix and
    \bookOutputName and \bookOutputSuffix commands.
    You should add '.ext' and/or '-[0-9]+.ext' to find created files.
    
    If filename is given, it is regarded as the filename LilyPond is run on. 
    Otherwise, the filename of the info's document is read.
    
    """
    basenames = []
    basepath = os.path.splitext(filename or dinfo.document.filename)[0]
    dirname, basename = os.path.split(basepath)

    if basepath:
        basenames.append(basepath)
    
    def args():
        yield dinfo.output_args()
        for filename in includefiles:
            yield docinfo(filename).output_args()
                
    for type, arg in itertools.chain.from_iterable(args()):
        if type == "suffix":
            arg = basename + '-' + arg
        path = os.path.normpath(os.path.join(dirname, arg))
        if path not in basenames:
            basenames.append(path)
    return basenames


