#!/usr/bin/env python
# vim: set fileencoding=utf-8 :

# Copyright (C) 2010  Jekyll Wu <adaptee at gmail.com>
#
# 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 3 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, see <http://www.gnu.org/licenses/>.

"""
Strip BOM from UTF-8 encoded files, because it is evil and non-sense.
"""

import os
import sys
import codecs
from argparse import ArgumentParser

BOM = u'\uFEFF'


def has_bom(text):
    """
        Does the unicode text begin with BOM?
    """
    return text[0] == BOM


def strip_bom(text):
    """
        return all but the leading BOM.
    """
    return text[1:]


def calc_backup_filename(filename):
    """ Calculate the filename for backfup. """
    return "%s-%d.bom" % (filename, os.getpid())


class File(object):
    def __init__(self, filename):
        self.in_file = codecs.open(filename, 'rwb+', 'utf-8')
        self.out_file = codecs.open(filename, 'rwb+', 'utf-8')

    def strip_bom(self, backup=False, pretend=False):
        """
            strip BOM, if present, from file
        """
        in_file_name = self.in_file.name
        text = self.read()

        if text and has_bom(text):
            if not pretend:
                if backup:
                    os.rename(in_file_name, calc_backup_filename(in_file_name))
                self.write(strip_bom(text))
            report = ("BOM stripped from: %s" % in_file_name)
        elif not text:
            report = ("Empty file: %s" % in_file_name)
        else:
            report = ("No BOM in: %s" % in_file_name)

        return report

    def read(self):
        """
            get unicode text from file
        """
        return self.in_file.read()

    def write(self, text):
        """
            write unicode text into file
        """
        self.out_file.seek(0)
        self.out_file.truncate(0)
        self.out_file.write(text)
        self.out_file.flush()


class TTYFile(object):

    def __init__(self):
        self.in_file = sys.stdin
        self.out_file = sys.stdout

    def strip_bom(self, _backup, _pretend):
        text = self.read()

        if text and has_bom(text):
            self.write(strip_bom(text))
        else:
            self.write(text)

        return ""

    def read(self):
        try:
            return self.in_file.read().decode("utf-8")
        except UnicodeDecodeError:
            raise UnicodeError("The encoding usde in termianl is not UTF-8")

    def write(self, text):
        self.out_file.write(text.encode("utf-8"))
        self.out_file.flush()


def get_fileobj(filename):

    if filename == "-":
        return TTYFile()
    else:
        return File(filename)


def report(text, verbose=True):
    """
       report succeful operation
    """
    if verbose:
        print (text)


def report_error(text):
    """
       report failed operation
    """
    sys.stderr.write("%s\n" % text)


if __name__ == '__main__':

    argparser = ArgumentParser(
                description="strip BOM from UTF-8 encoded file."
                              )

    argparser.add_argument( '-b', '--backup',
                            dest="backup",
                            default=False,
                            action="store_true",
                            help="Create backups ending with .bom ."
                          )

    argparser.add_argument( '-i', '--in-place',
                            dest="backup",
                            default=False,
                            action="store_false",
                            help="No backups. Modify in place."
                          )

    argparser.add_argument( '-p', '--pretend',
                            dest="pretend",
                            default=False,
                            action="store_true",
                            help="Do not really perform modification."
                          )

    argparser.add_argument( '-q', '--quiet',
                            dest="verbose",
                            default=True,
                            action="store_false",
                            help="Do not report results. "
                          )

    argparser.add_argument( '-v', '--verbose',
                            dest="verbose",
                            default=True,
                            action="store_true",
                            help="Report results. "
                          )

    argparser.add_argument( "files",
                            metavar="FILE",
                            nargs='*',
                            help="File to be stripped of BOM."
                          )

    args = argparser.parse_args()

    # conform to the unix converntion:
    # when no filename is suppolied, get data from stdin
    if not args.files:
        args.files = ["-"]

    # when stdin is involved, bomstrip is most likely used in a pipeline
    # Then we should keep quiet, conforming to the unix convention.
    # Also, just do it, no pretending
    if "-" in args.files:
        args.verbose = False
        args.pretend = False

    exit_code = 0

    for filename in args.files:
        try:
            fileobj = get_fileobj(filename)
            message = fileobj.strip_bom(args.backup, args.pretend)
        except IOError as e:
            exit_code += 1
            report_error("Fail to open: %s" % e.filename)
        except UnicodeError as e:
            exit_code += 1
            report_error(e)
        else:
            report(message, args.verbose)

    sys.exit(exit_code)
