# Copyright (c) 2011-2014, B.I.Stepanov Institute of Physics, National Academy
# of Sciences of Belarus.
#
# 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.

"""Miscellaneous text manipulation functions."""

import math
import os
import os.path
import win32api

# **** String converters ******************************************************
def stringToFloat(string):
    """Return a finite floating point number represented by 'string' or 'None'
    if the conversion can not be accomplished."""

    # Allow the decimal mark to be either point or comma.
    # If these characters are mixed, the conversion will fail anyway.
    string = string.replace(',', '.')

    try:
        value = float(string)
    except ValueError:
        return None

    # Filter out string values like 'nan' and 'inf'.
    if math.isinf(value) or math.isnan(value):
        return None

    return value

def stringToInt(string):
    """Return an integer represented by 'string' or 'None' if the conversion
    can not be accomplished."""

    try:
        value = int(string)
    except ValueError:
        return None

    return value

# **** Path functions *********************************************************
def absolutePath(path):
    """Return an absolute and properly formatted version of the given file
    path."""

    # On Windows, 'unicode' strings should always be used with Python's path
    # functions, as usage of plain byte strings may lead to corruption of file
    # paths that contain Unicode characters missing from the default system
    # codepage. Also, the following will make it possible to pass 'QString's to
    # this function.
    path = unicode(path)

    # Convert the path to the absolute version, if required.
    if not os.path.isabs(path):
        # Non-Unicode version of the function ('os.getcwd') may sometimes
        # return corrupted file paths on Windows (e.g. latin letter "a" with
        # accent is replaced with ordinary "a" if default codepage is
        # cyryllic). However, such a behaviour seems to only be possible if
        # 'os.chdir' was called at some time earlier with correct Unicode
        # directory name specified in its argument. Otherwise, path returned by
        # both 'os.getcwd' and 'os.getcwdu' will contain mangled Windows
        # directory names whenever they don't match the system codepage.
        curDirPath = os.getcwdu()

        # Replace mangled directory names, if any, with their correct Unicode
        # representations. This function requires the path to point to an
        # existing file or directory, and thus should not be applied to 'path',
        # which may be arbitrary. 'W' suffix means 'Unicode version'.
        curDirPath = win32api.GetLongPathNameW(curDirPath)

        path = os.path.join(curDirPath, path)

    # Replace forward slash characters, if any, with backslashes on Windows.
    path = os.path.normpath(path)

    # Capitalize the driver letter on Windows, so that its case is always the
    # same. Neither 'os.path.normpath' nor 'win32api.GetLongPathNameW'
    # functions change the drive letter's case.
    (drive, tail) = os.path.splitdrive(path)
    path = drive.upper() + tail

    return path

def portablePath(path):
    """Return a version of the given file path that is suitable for storage in
    a settings file to be loaded during the next application startup."""

    # Return the absolute version of 'path' if it does not point to a location
    # within the current directory, and its relative version otherwise. By
    # default, current directory will coincide with the application's root.
    # Thus, none of the stored file paths will become invalid if the user moves
    # or renames the application's root folder.

    absPath = absolutePath(path)
    curDirPath = absolutePath('.')

    # 'os.path.normcase' will assure correct case-insensitive comparison on
    # Windows. However, it's actually not enough, since Windows file names may
    # be mangled, and both true and mangled file names are considered valid and
    # pointing to the same location. Fortunately, file paths in this
    # application (unless they are predefined ones) may currently only
    # originate from Qt's file/directory selection dialogs, and also from
    # settings files. Qt's dialogs, on the other hand, will currently always
    # return valid Unicode file paths, and settings files therefore will
    # normally contain no mangled file paths either.
    if os.path.normcase(absPath) == os.path.normcase(curDirPath):
        return '.'

    # Add a directory separator to the current directory's path, so that it
    # could be safely compared with 'absPath' if they don't coincide.
    curDirPath += os.sep

    if not os.path.normcase(absPath).startswith(os.path.normcase(curDirPath)):
        portablePath = absPath
    else:
        portablePath = os.path.relpath(absPath, curDirPath)

    # Use forward slashes as directory separators, so that paths are
    # represented in the most portable way possible (this might only be useful
    # for relative paths though).
    return portablePath.replace(os.sep, '/')

# **** Rich text functions ****************************************************
def quote(text, setBoldFont = True, addQuotes = True):
    """Return a copy of 'text' enclosed in boldface font tags and quotes."""

    # Remove any HTML formatting from 'text'.
    text = text.replace('&', '&amp;')
    text = text.replace('<', '&lt;')
    text = text.replace('>', '&gt;')

    return quoteRichText(text, setBoldFont, addQuotes)

def quoteRichText(text, setBoldFont = True, addQuotes = True):
    """Same as 'quote', but allow HTML formatting to be embedded in 'text'."""

    if addQuotes:
        text = u'\u201c' + text + u'\u201d'
    if setBoldFont:
        text = '<b>' + text + '</b>'

    return text

def quotePath(path):
    """Return an absolute and properly formatted version of the given file
    path, enclosed in boldface font tags and quotes."""

    # Convert the path to the absolute version, if required.
    path = absolutePath(path)
    # Insert zero-width spaces after directory separators, so that 'QLabel's
    # word wrapping algorithm could split the path at these points.
    path = path.replace(os.sep, os.sep + u'\u200b')

    return quote(path)

def quoteNumber(number):
    """Same as 'quote', but take an integer or a floating point number as the
    argument and do not add quotes to it, just the boldface tags."""

    return quote(str(number), addQuotes = False)

def unQuote(text):
    """Return a copy of 'text' without any of the formatting tags that might
    have been added by 'quote'."""

    return text.replace('<b>', '').replace('</b>', '')

# *****************************************************************************
class Error(Exception):
    """An 'Exception' that holds a rich text message."""

    def __init__(self, text):
        self.text = text
