#!/usr/bin/env ccp4-python

import os, sys, shutil, re, time
import subprocess as SP
from xml.etree import ElementTree as ET

from pyrvapi import *
from math import cos, sin
import subprocess


class AwARvAPI(object):

    def __init__(self, arg_dict):
        use_jsrview = 'jsrview' in arg_dict and arg_dict['jsrview'] == '1'
        workdir = arg_dict['workdir']
        residues = int(arg_dict['residues'])
        cgr = int(arg_dict['cgr'])
        buildingcycles = int(arg_dict['buildingcycles'])
        restrref = int(arg_dict['restrref'])
        multit = int(arg_dict['multit'])

        ccp4 = os.environ['CCP4']
        jsrview = os.path.join(ccp4, 'libexec', 'jsrview.exe' if sys.platform.startswith('win') else 'jsrview')
        share_jsrview = os.path.join(ccp4, 'share', 'jsrview')

        print 'Qt browser:         ' + jsrview
        print 'JavaScript library: ' + share_jsrview
        print 'Working directory:  ' + workdir
        print 'Report directory:   ' + workdir

        #// Document modes
        #define RVAPI_MODE_Silent  0x00100000
        #define RVAPI_MODE_Html    0x00000001
        #define RVAPI_MODE_Xmli2   0x00000002

        #// Document layouts
        #define RVAPI_LAYOUT_Header   0x00000001
        #define RVAPI_LAYOUT_Toolbar  0x00000002
        #define RVAPI_LAYOUT_Tabs     0x00000004
        #define RVAPI_LAYOUT_Full     0x00000007

        mode = RVAPI_MODE_Xmli2
        if use_jsrview:
            mode |= RVAPI_MODE_Html
            subprocess.Popen([jsrview, os.path.join(workdir, 'index.html')])

        rvapi_init_document(
            'TestRun',         # const char * docId      // mandatory
            workdir,           # const char * outDir     // mandatory
            'RVAPI Demo 1',    # const char * winTitle   // mandatory
            mode,              # const int    mode       // mandatory
            4,                 # const int    layout     // mandatory
            share_jsrview,     # const char * jsUri      // needed
            None,              # const char * helpFName  // may be NULL
            None,              # const char * htmlFName  // may be NULL
            None,              # const char * taskFName  // may be NULL
            'i2.xml'           # const char * xmli2FName // may be NULL
        )

        rvapi_add_header('RVAPI Demo Page 1')
        rvapi_add_tab('tab1', 'Report', True)
        rvapi_add_section('sec1', 'Results', 'tab1', 0, 0, 1, 1, True)

        rvapi_add_table1("sec1/table1", "Summary", 0, 0, 1, 1, 0)
        rvapi_put_vert_theader("table1", "Residues built", "No of residues built", 0)
        rvapi_put_vert_theader("table1", "Chains built", "No of continuous fragments", 1)
        rvapi_put_vert_theader("table1", "Longest chain", "No of continuous in the longest fragment", 2)
        rvapi_put_vert_theader("table1", "R-cryst", "Crystallographic R-factor", 3)
        rvapi_put_vert_theader("table1", "R-free", "Free R-factor", 4)
        rvapi_put_table_string("table1", 'N/A', 0, 0)
        rvapi_put_table_string("table1", 'N/A', 1, 0)
        rvapi_put_table_string("table1", 'N/A', 2, 0)
        rvapi_put_table_string("table1", 'N/A', 3, 0)
        rvapi_put_table_string("table1", 'N/A', 4, 0)

        rvapi_add_loggraph1('sec1/graphWidget1', 0, 1, 1, 1)

        rvapi_add_graph_data1('graphWidget1/data1', 'Refinements')
        rvapi_add_graph_dataset1('graphWidget1/data1/x', 'x', 'argument')
        rvapi_add_graph_dataset1('graphWidget1/data1/y3', 'R-cryst', 'Crystallographic R-factor')
        rvapi_add_graph_dataset1('graphWidget1/data1/y4', 'R-free', 'Crystallographic free R-factor')

        rvapi_add_graph_data1('graphWidget1/data3', 'Building')
        rvapi_add_graph_dataset1('graphWidget1/data3/x3', 'x', 'argument')
        rvapi_add_graph_dataset1('graphWidget1/data3/y33', 'in model', 'Crystallographic R-factor')
        rvapi_add_graph_dataset1('graphWidget1/data3/y34', 'in longest chain', 'Crystallographic free R-factor')

        rvapi_add_graph_plot1('graphWidget1/plot2', 'R-factors', 'Refinement cycle', 'R')
        rvapi_add_plot_line1('graphWidget1/data1/plot2', 'x', 'y3')
        rvapi_add_plot_line1('graphWidget1/data1/plot2', 'x', 'y4')
        rvapi_set_plot_xrange('plot2', 'graphWidget1', 0, buildingcycles* restrref - 1)
        rvapi_set_plot_yrange('plot2', 'graphWidget1', -0.02, 0.6)

        rvapi_add_graph_plot1('graphWidget1/plot3', 'Residues built', 'Building round', 'Residues')
        rvapi_add_plot_line1('graphWidget1/data3/plot3', 'x3', 'y33')
        rvapi_add_plot_line1('graphWidget1/data3/plot3', 'x3', 'y34')
        rvapi_set_plot_xrange('plot3', 'graphWidget1', 1, buildingcycles* multit)
        rvapi_set_plot_yrange('plot3', 'graphWidget1', 0, residues)
        rvapi_flush()

    def cycle_ready(self, items):
        pass

    def cycle_started(self, items):
        pass

    building_cycle = 0
    def building_round_finished(self, items):
        self.building_cycle += 1
        round_no, in_model, chains, in_longest, score = items
        rvapi_add_graph_int1('graphWidget1/data3/x3', self.building_cycle)
        rvapi_add_graph_real1('graphWidget1/data3/y33', float(in_model), '%g')
        rvapi_add_graph_real1('graphWidget1/data3/y34', float(in_longest), '%g')

        rvapi_put_table_string("table1", in_model, 0, 0)
        rvapi_put_table_string("table1", chains, 1, 0)
        rvapi_put_table_string("table1", in_longest, 2, 0)
        rvapi_flush()

    def building_step_finished(self, items):
        pass

    def refinement_step_finished(self, items):
        rvapi_add_graph_int1('graphWidget1/data1/x', int(items[0]))
        rvapi_add_graph_real1('graphWidget1/data1/y3', float(items[1]), '%g')
        rvapi_add_graph_real1('graphWidget1/data1/y4', float(items[2]), '%g')

        rvapi_put_table_string("table1", items[1], 3, 0)
        rvapi_put_table_string("table1", items[2], 4, 0)
        rvapi_flush()

    def cycle_finished(self, items):
        pass


class LogXML(object):

    def __init__(self, arg_dict):
        self.xmlout = arg_dict['xmlout']
        print 'xmlout =', self.xmlout
        self.tree = ET.ElementTree(ET.Element('BuccaneerBuildRefineResult'))

    def cycle_ready(self, items):
        f1 = ET.SubElement(self.tree.getroot(), 'BuildRefineCycle')
        f2 = ET.SubElement(f1, 'Number')
        self.cycle_no = f2
        f2 = ET.SubElement(f1, 'BuccaneerResult')
        f3 = ET.SubElement(f2, 'Title')

        f3 = ET.SubElement(f2, 'Final')
        f4 = ET.SubElement(f3, 'CompletenessByResiduesBuilt')
        self.compl_residue = f4
        f4 = ET.SubElement(f3, 'CompletenessByChainsBuilt')
        self.compl_chain = f4
        f4 = ET.SubElement(f3, 'ChainsBuilt')
        self.chains = f4
        f4 = ET.SubElement(f3, 'FragmentsBuilt')
        f4.text = '0'
        f4 = ET.SubElement(f3, 'ResiduesBuilt')
        self.residues = f4
        f4 = ET.SubElement(f3, 'ResiduesSequenced')
        f4.text = '0'

        f2 = ET.SubElement(f1, 'RefmacResult')
        f3 = ET.SubElement(f2, 'r_factor')
        self.rcryst = f3
        f3 = ET.SubElement(f2, 'r_free')
        self.rfree = f3
        f3 = ET.SubElement(f2, 'rmsBONDx100')
        f3.text = '0'
        f3 = ET.SubElement(f2, 'rmsANGLE')
        f3.text = '0'

        self.cycle_no.text = '0'
        self.compl_residue.text = '0'
        self.compl_chain.text = '0'
        self.chains.text = '0'
        self.residues.text = '0'
        self.rcryst.text = '0'
        self.rfree.text = '0'

    def cycle_started(self, items):
        self.cycle_no.text = items[0]

    def building_round_finished(self, items):
        pass

    def building_step_finished(self, items):
        self.compl_residue.text = items[1]
        self.compl_chain.text = items[0]
        self.chains.text = items[0]
        self.residues.text = items[1]

    def refinement_step_finished(self, items):
        self.rcryst.text = items[1]
        self.rfree.text = items[2]

    def cycle_finished(self, items):
        del self.cycle_no
        del self.compl_residue
        del self.compl_chain
        del self.chains
        del self.residues
        del self.rcryst
        del self.rfree
        newlines(self.tree.getroot(), '\n')
        self.tree.write(self.xmlout)


def newlines(parent, indent):
    if len(parent) > 0:
        parent[-1].tail = indent
        indent += '  ';
        parent.text = indent
        for child in parent[:-1]:
            child.tail = indent

        for child in parent:
            newlines(child, indent)


class LogDataLine(list):

    def __init__(self, pattern=''):
        self.regex = re.compile(pattern)
        self.action_list = list()

    def add_action(self, *action_list):
        self.action_list.extend(action_list)

    def try_next(self, line):
        for next in self:
            data = next.regex.match(line)
            if data:
                for action in next.action_list:
                    action(data.groups())

                return next

        return self


class AwARunner(list):

    def __init__(self, parser):
        self.ipath = None
        self.pause = 0.0
        self.parser = parser
        self.keyval = ([], {})
        self.cmd = ['auto_tracing.sh']
        self.arg_dict = {}
        arg_keys, arg_dict = self.keyval
        for line in sys.stdin:
            if not line.startswith('#'):
                key, sep, value = line.strip().partition(' ')
                value = value.lstrip()
                if key == 'mockdir':
                    self.ipath = os.path.join(value, 'warpNtrace')

                elif key == 'pause':
                    self.pause = 0.001* int(value)

                else:
                    self.arg_dict[key] = value
                    if key != 'xmlout':
                        self.cmd.append(key)
                        self.cmd.append(value)

                arg_keys.append(key)
                arg_dict[key] = value

    def run(self, verbose=False):
        if verbose:
            for arg in self.cmd:
                print arg

        if self.ipath:
            istream = open(self.ipath + '.log')

        else:
            p = SP.Popen(self.cmd, stdout=SP.PIPE)
            istream = p.stdout

        # NB:  'for line in p.stdout' buffers the output

        line = istream.readline()
        while line:
            print line,
            sys.stdout.flush()
            self.parser.parse(line)
            line = istream.readline()
            if self.pause:
                time.sleep(self.pause)

        if self.ipath:
            jobId = self.arg_dict['jobId']
            jobdir = os.path.join(self.arg_dict['workdir'], jobId)
            if not os.path.isdir(jobdir):
                os.mkdir(jobdir)
        
            opath = os.path.join(jobdir, jobId + '_warpNtrace')
            shutil.copy(self.ipath + '.mtz', opath + '.mtz')
            shutil.copy(self.ipath + '.pdb', opath + '.pdb')

        else:
            p.wait()
            sys.exit(p.returncode)


class AwAParser(object):

    def __init__(self):
        self.completed = LogDataLine()
        self.version = LogDataLine(' You are running ARP/wARP version ([0-9]*\.[0-9]*)')
        self.big_cycle = LogDataLine(' Building Cycle ([0-9]*)    Atomic shape factors  ([0-9]*\.[0-9]*)  ([0-9]*\.[0-9]*)\n')
        self.building_round = LogDataLine('   Round ([0-9]*): ([0-9]*) peptides, ([0-9]*) chains. Longest chain ([0-9]*) peptides. Score ([0-9]*\.[0-9]*)\n')
        self.building = LogDataLine('  Chains ([0-9]*), Residues ([0-9]*), Estimated correctness of the model ([0-9]*\.[0-9]*) %\n')
        self.refinement = LogDataLine('  Cycle ([0-9]*): *After refmac, R = ([0-9]*\.[0-9]*) \(Rfree = ([0-9]*\.[0-9]*)\) for ([0-9]*) atoms.\n')
        self.success = LogDataLine(' Normal termination of warpNtrace')

        self.completed.append(self.version)
        self.version.append(self.refinement)
        self.big_cycle.append(self.building_round)
        self.building_round.extend((self.building_round, self.building))
        self.building.append(self.refinement)
        self.refinement.extend((self.refinement, self.big_cycle, self.success))

    def add_listener(self, listener):
        self.version.add_action(listener.cycle_ready)
        self.big_cycle.add_action(listener.cycle_finished, listener.cycle_ready, listener.cycle_started)
        self.building_round.add_action(listener.building_round_finished)
        self.building.add_action(listener.building_step_finished)
        self.refinement.add_action(listener.refinement_step_finished)
        self.success.add_action(listener.cycle_finished)

    def parse(self, line):
        self.completed = self.completed.try_next(line)

def main():
    runner = AwARunner(AwAParser())
    runner.parser.add_listener(AwARvAPI(runner.arg_dict))
    runner.parser.add_listener(LogXML(runner.arg_dict))
    runner.run(True)

if __name__ == '__main__':
    main()


