#!/usr/bin/env python
#
# vim:ts=4 sw=4 expandtab
#
# Copyright (c) 2013, Olivier Sobrie <olivier@sobrie.be>
# Copyright (c) 2007, Hans Meine <hans_meine@gmx.net>
#  All rights reserved.
#
# This is licensed according to the new BSD license.
# Please send patches / comments, I would be happy about any feedback.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
#   notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
#   notice, this list of conditions and the following disclaimer in the
#   documentation and/or other materials provided with the distribution.
#
# * Neither the name of the University of Hamburg nor the names of its
#   contributors may be used to endorse or promote products derived from
#   this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

import sys
import os
import subprocess
import glob
import time
import optparse
import subprocess
import tempfile

DEFAULT_PDFVIEWER_CMD = "zathura"
DEFAULT_TEMPLATE_FILE = os.path.expanduser("~/.tikz2pdf.tex")
DEFAULT_TEMPLATE = r"""\documentclass[a4paper,10pt]{article}
\usepackage{pgfplots,pgfplotstable}
\usepackage{makecell}
\usepackage{tikz,nicefrac,amsmath,pifont,fp}
\usetikzlibrary{arrows,snakes,backgrounds,patterns,matrix,shapes,fit,calc,shadows,plotmarks,decorations.pathmorphing,plotmarks,external,positioning,fpu}
\usepgfplotslibrary{groupplots}
\usepackage[graphics,tightpage,active]{preview}
\usepackage{tabularx}
\PreviewEnvironment[]{tikzpicture}
\PreviewEnvironment[]{tabular}
\newlength{\imagewidth}
\newlength{\imagescale}

\definecolor{green}{RGB}{0,140,0}
\definecolor{red}{RGB}{160,0,0}
\definecolor{blue}{RGB}{0,0,160}
\definecolor{orange}{RGB}{235,135,0}
\definecolor{mygreen}{rgb}{.1,.5,.1}
\definecolor{myred}{rgb}{.5,0,0}
\definecolor{myblue}{RGB}{55, 126, 184}
\definecolor{mypurple}{RGB}{152, 78, 163}
\definecolor{myorange}{RGB}{255, 127, 0}
\definecolor{mybrown}{rgb}{.6,0,0}
\definecolor{mygray}{RGB}{112,128,144}  

\begin{document}
\pgfplotsset{ wstyle/.style = {width = 3in}, hstyle/.style = {height = 2.8in} }
\pgfplotsset{ addstyle/.style = {} }
\tikzset{ addstyle/.style = {} }

\input{"%s"}

\end{document}
"""

quiet = False
verbose = False
tmpfile = None
dependencies = {}
relative_path = ""

def log_error(msg):
    sys.stderr.write(msg)

def log_info(msg):
    if quiet is True:
        return

    sys.stderr.write(msg)

def log_verbose(msg):
    if verbose is False:
        return

    sys.stderr.write(msg)

def which(program):
    def is_exe(fpath):
        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            path = path.strip('"')
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file

    return None

def find_template():
    current = os.getcwd()
    while current != "/":
        candidate = os.path.join(current, ".tikz2pdf.tex")
        if os.path.isfile(candidate):
            log_verbose("Using template %s\n" % candidate)
            return candidate

        current = os.path.split(current)[0]

    return None

def remove_tempfile():
    if tmpfile is None:
        return

    for temp in glob.glob(os.path.splitext(tmpfile.name)[0] + ".*"):
        os.unlink(temp)

def get_updated_dependencies():
    global dependencies

    l = []
    for fname, prev_time in dependencies.items():
        try:
            current_time = os.path.getmtime(fname)
        except:
            continue

        if current_time != prev_time:
            dependencies[fname] = current_time
            l.append(fname)

    return l

def update_dependencies(tmpfile):
    global dependencies

    try:
        f = open(os.path.splitext(tmpfile.name)[0] + ".fls")
    except:
        return

    discarded_files = glob.glob(os.path.splitext(tmpfile.name)[0] + ".*")

    dependencies = {}
    for line in f:
        line = line.split()
        if line[0] != "INPUT":
            continue

        if os.path.realpath(line[1]) in discarded_files:
            continue

        try:
            dependencies[line[1]] = os.path.getmtime("%s " % line[1])
        except:
            pass

    f.close()

def save_dependencies(tikfile, output_file, dependencies):
    if len(dependencies) < 2:
        return

    f = open("%s/.%s.d" % (os.path.dirname(output_file),
                           os.path.basename(output_file)), 'w+')

    for dep in dependencies.keys():
        fname = relative_path + os.path.basename(output_file)

        if dep == tikfile:
            continue

        if dep[0] != os.sep:
            dep = relative_path + dep

        f.write("%s: %s\n" % (fname, dep))

    f.close()

def generate_pdf(pdfcmd, template, tikzfile, output_file, save_deps):
    global tmpfile
    global verbose

    tmpfile = tempfile.NamedTemporaryFile(prefix = os.getcwd() + "/tikz2pdf_",
                                          suffix=".tex")
    buf = template % tikzfile
    tmpfile.write(buf.encode('utf-8'))
    tmpfile.flush()

    texname = tmpfile.name
    pdfname = os.path.splitext(texname)[0] + ".pdf"

    if verbose is True:
        out = sys.stderr
    else:
        out = tempfile.TemporaryFile()

    rc = subprocess.call([pdfcmd, "-shell-escape", "-halt-on-error",
                          "-recorder", texname], stdout = out, stderr = out)
    if rc:
        log_error("\nERROR generating %s with pdflatex (rc = %d)\n"
                  % (output_file, rc))

        if out is not sys.stderr:
            out.seek(0)
            sys.stderr.write(out.read().decode("utf-8"))
            out.close()

        tmpfile.close()
        remove_tempfile()
        return rc

    update_dependencies(tmpfile)
    if save_deps is True:
        save_dependencies(tikzfile, output_file, dependencies)

    tmpfile.close()

    os.rename(pdfname, output_file)
    remove_tempfile()

    log_verbose("Successfully generated %s\n" % output_file)

    return 0

def start_pdfviewer(output_file):
    subprocess.Popen(DEFAULT_PDFVIEWER_CMD +  " \"%s\"" % output_file,
                     shell = True)

if __name__ == "__main__":
    op = optparse.OptionParser(usage="%prog [options] foo.tikz")
    op.add_option("-v", "--verbose", action = "store_true",
                  dest = "verbose", default = False,
                  help = "verbose output")
    op.add_option("-o", "--once", action = "store_true",
                  dest = "once", default = False,
                  help = "only convert once and exit (obsolete)")
    op.add_option("-w", "--watch", action = "store_true",
                  dest = "watch", default = False,
                  help = "watch tikz file and procude a new pdf if it has changed")
    op.add_option("-s", "--view", action = "store_true",
                  dest = "view", default = False,
                  help = "start viewer after first successful compilation")
    op.add_option("-f", "--file", action = "store",
                  dest = "output_file", default = None,
                  help = "ouput file name")
    op.add_option("-d", "--depends", action = "store_true",
                  dest = "save_deps", default = False,
                  help = "save dependencies in Makefile format into hidden \
                          file \".filename.d\"")

    options, args = op.parse_args()

    verbose = options.verbose

    if len(args) == 0:
        log_error("No input file specified!\n")
        exit(1)

    relative_path = os.path.dirname(args[0])
    if relative_path != "":
        relative_path += os.sep

    tikzfile = os.path.realpath(args[0])
    if not os.path.isfile(tikzfile):
        log_error("Invalid file! (%s)\n" % tikzfile)
        exit(1)

    directory = os.path.dirname(tikzfile) + os.sep

    if options.output_file is None:
	    options.output_file = os.path.splitext(tikzfile)[0] + ".pdf"

    options.output_file = os.path.realpath(options.output_file)

    os.chdir(directory)

    if which('pdflatex') is not None:
        pdfcmd = 'pdflatex'
    elif which('lualatex') is not None:
        pdfcmd = 'lualatex'
    else:
        log_error("Neither 'lualtex' nor 'pdflatex' have been found")
        exit(1)

    log_verbose("Using '%s'\n" % pdfcmd)

    template_file = find_template()
    if template_file is not None:
	    template = open(template_file, 'r').read()
    elif os.path.isfile(DEFAULT_TEMPLATE_FILE):
	    template = open(DEFAULT_TEMPLATE_FILE, 'r').read()
    else:
        template = DEFAULT_TEMPLATE

    if options.watch is False or options.once is True:
        rc = generate_pdf(pdfcmd, template, tikzfile, options.output_file,
                          options.save_deps)
        if rc == 0 and options.view is True:
            start_pdfviewer(options.output_file)
    else:
        previous_time = None
        try:
            i = 0
            while True:
                try:
                    current_time = os.path.getmtime(tikzfile)
                except OSError:
                    time.sleep(1)
                    continue

                nupdated_depends = len(get_updated_dependencies())
                if current_time == previous_time and nupdated_depends == 0:
                    time.sleep(1)
                    continue

                previous_time = current_time

                i += 1
                msginfo = "Regenerating %s (#%d)..." \
                          % (options.output_file, i)
                if verbose is True:
                    msginfo += "\n"

                log_info(msginfo)

                rc = generate_pdf(pdfcmd, template, tikzfile,
                                  options.output_file,
                                  options.save_deps)
                if rc == 0:
                    log_info(" done\n")

                if rc == 0 and options.view is True:
                    start_pdfviewer(options.output_file)
                    options.view = False

        except KeyboardInterrupt:
            remove_tempfile()
            pass

    exit(rc)
