#!/usr/bin/python
# @package      hubzero-hubgraph
# @file         hzhubgraph-config
# @author       Nicholas J. Kisseberth <nkissebe@purdue.edu>
# @copyright    Copyright (c) 2014 HUBzero Foundation, LLC.
# @license      http://www.gnu.org/licenses/lgpl-3.0.html LGPLv3
#
# Copyright (c) 2014 HUBzero Foundation, LLC.
#
# This file is part of: The HUBzero(R) Platform for Scientific Collaboration
#
# The HUBzero(R) Platform for Scientific Collaboration (HUBzero) is free
# software: you can redistribute it and/or modify it under the terms of
# the GNU Lesser General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# HUBzero 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# HUBzero is a registered trademark of HUBzero Foundation, LLC.
#

#
# hzhubgraph-config configure --enable|--disable
# hzhubgraph-config init
# hzhubgraph-config status
#

import re, glob, string, ConfigParser, sys, MySQLdb, time, warnings, argparse, os, pwd, grp, subprocess

debug = False

os.umask(0027)

def configureHubgraph(args):

    uid = os.geteuid()

    if uid != 0:            
        print 'hzhubgraph-config configure: must be run with root privileges'
        return 1

    if args.enable:
        return enableHubgraph()
    else:
        return disableHubgraph()

def enableHubgraph():

    print "Configuring hubzero-hubgraph"

    try:
        grp.getgrnam('hubgraph')
    except KeyError:
        print 'Creating hubgraph group'

        adduser = ["/usr/sbin/groupadd", "--system", "hubgraph"]

        try:
            proc = subprocess.Popen(adduser, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    
            procStdOut, procStdErr = proc.communicate()

            rc = proc.returncode

        except Exception, ex:   
            print ex
            raise
            #raise Exception('exShellCommand error ' + str(argArray) + ' ' + str(ex) + "\n" + traceback.format_exc())

        if rc != 0:
            print procStdOut
            print procStdErr
            return 1

    try:
        pwd.getpwnam('hubgraph')
    except KeyError:
        print 'Creating hubgraph user'

        adduser = ["/usr/sbin/useradd", "--system", "--home-dir" , "/var/lib/hubgraph", "--gid", "hubgraph", "--shell", "/bin/bash", "--comment", "HUBgraph", "hubgraph"]

        try:
            proc = subprocess.Popen(adduser, shell=False, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    
            procStdOut, procStdErr = proc.communicate()

            rc = proc.returncode

        except Exception, ex:   
            print ex
            raise
            #raise Exception('exShellCommand error ' + str(argArray) + ' ' + str(ex) + "\n" + traceback.format_exc())

        if rc != 0:
            print procStdOut
            print procStdErr
            return 1

    config = ConfigParser.ConfigParser()

    hzconfig = ConfigParser.ConfigParser()

    if hzconfig.read('/etc/hubzero.conf') == []:
        print "Unable to continue, failed to read /etc/hubzero.conf."
        return 1

    if hzconfig.has_option('DEFAULT','site'):
        site = hzconfig.get('DEFAULT','site')
    else:
        print "Unable to continue, failed to find default hub site in /etc/hubzero.conf"
        return 2

    if hzconfig.has_option(site,'documentroot'):
        document_root = hzconfig.get(site,'documentroot')
    else:
        print "Unable to continue, no document root set for default site in /etc/hubzero.conf"
        return 3

    data = { 'cmsconfig' : {} }

    try:
        for m in re.finditer("\s*(?:var|public)\s+\$(\w+)\s*=\s*\'*(.*?)\'*\s*\;\s*", open(document_root + '/configuration.php','r').read()):
            data['cmsconfig'][m.group(1)] = m.group(2).strip(" \'\"\t")
    except Exception as e:
        print "Unable to continue, failed to read '" +  document_root + "/configuration.php'"
        return 4

    if 'db' not in data['cmsconfig'] or 'user' not in data['cmsconfig'] or 'password' not in data['cmsconfig']:
        print "Unable to continue, missing configuration in  '" +  document_root + "/configuration.php'"
        return 5

    if data['cmsconfig']['db'] == '' or data['cmsconfig']['user']  == '' or data['cmsconfig']['password'] == '':
        print "Unable to continue, invalid configuration in  '" +  document_root + "/configuration.php'"
        return 6

    config.read("/etc/hubgraph.conf")

    if not config.has_section('mysql'):
        config.add_section('mysql')

    config.set('mysql','database',data['cmsconfig']['db'])
    config.set('mysql','user',data['cmsconfig']['user'])
    config.set('mysql','password',data['cmsconfig']['password'])
    
    if 'port' in data['cmsconfig']:
        config.set('mysql','port',data['cmsconfig']['port'])
    else:
        config.set('mysql','port', 3306)

    if 'host' in data['cmsconfig']:
        config.set('mysql','host',data['cmsconfig']['host'])
    else:
        config.set('mysql','host','localhost')

    if not config.has_section('hubgraph'):
        config.add_section('hubgraph')

    if not config.has_option('hubgraph','root') or config.get('hubgraph','root') == '':
        config.set('hubgraph','root', pwd.getpwnam('hubgraph').pw_dir)

    if not config.has_option('hubgraph','log') or config.get('hubgraph','log') == '':
        config.set('hubgraph','log', '/var/log/hubgraph/log')

    if not config.has_option('hubgraph','listen') or config.get('hubgraph','listen') == '':
        config.set('hubgraph','listen','/var/run/hubgraph/hubgraph-server.sock')

    if not config.has_option('hubgraph','seconds_per_update') or config.get('hubgraph','seconds_per_update') == '':
        config.set('hubgraph','seconds_per_update',60)

    print "Writing new /etc/hubgraph.conf"

    hg_uid = pwd.getpwnam('hubgraph').pw_uid
    hg_gid = grp.getgrnam('hubgraph').gr_gid

    with open("/etc/hubgraph.conf","wb") as configfile:
        configfile.truncate()
        os.fchown(configfile.fileno(), hg_uid, hg_gid)
        os.fchmod(configfile.fileno(), 0640)
        config.write(configfile)

    try:
        os.mkdir('/var/lib/hubgraph',0750)
        os.mkdir('/var/lib/hubgraph/lib',0750)
        os.mkdir('/var/lib/hubgraph/lib/resources',0750)
        os.mkdir('/var/lib/hubgraph/lib/views',0750)
        os.mkdir('/var/log/hubgraph',0750)
        os.mkdir('/var/log/hubgraph/daily',0750)

        os.chown('/var/lib/hubgraph', hg_uid, hg_gid)
        os.chown('/var/lib/hubgraph/lib', hg_uid, hg_gid)
        os.chown('/var/lib/hubgraph/lib/resources', hg_uid, hg_gid)
        os.chown('/var/lib/hubgraph/lib/views', hg_uid, hg_gid)
        os.chown('/var/log/hubgraph', hg_uid, hg_gid)
        os.chown('/var/log/hubgraph/daily', hg_uid, hg_gid)

    except OSError:
        pass

    for file in glob.glob('/usr/lib/hubzero-hubgraph/node_modules/hubgraph-hubzero/lib/views/*'): 
        if not os.path.exists('/var/lib/hubgraph/lib/views/' + os.path.basename(file)):
            os.symlink(file, '/var/lib/hubgraph/lib/views/' + os.path.basename(file))
            os.lchown('/var/lib/hubgraph/lib/views/' + os.path.basename(file), hg_uid, hg_gid)

    for file in glob.glob('/usr/lib/hubzero-hubgraph/node_modules/hubgraph-hubzero/lib/resources/*'):
        if not os.path.exists('/var/lib/hubgraph/lib/resources/' + os.path.basename(file)):
            os.symlink(file,'/var/lib/hubgraph/lib/resources/' + os.path.basename(file))
            os.lchown('/var/lib/hubgraph/lib/resources/' + os.path.basename(file), hg_uid, hg_gid)


def disableHubgraph():
    print "Unconfiguring hubzero-hubgraph"

    config = ConfigParser.ConfigParser()

    if config.read("/etc/hubgraph.conf") == []:
        return 0

    if not config.has_section('mysql'):
        return 0

    config.remove_option('mysql','database')
    config.remove_option('mysql','user')
    config.remove_option('mysql','password')
    config.remove_option('mysql','port')
    config.remove_option('mysql','host')

    print "Writing new /etc/hubgraph.conf"

    with open("/etc/hubgraph.conf","wb") as configfile:
        configfile.truncate()
        config.write(configfile)

    return 0


def removeTriggers(args):
    print "Removing MySQL triggers for hubzero-hubgraph"

    config = ConfigParser.ConfigParser()

    if config.read("/etc/hubgraph.conf") == []:
        return 1

    if not config.has_section('mysql'):
        return 2

    database = ''
    user = ''
    password = ''
    host = 'localhost'
    port = 3306

    try:
        if config.has_option('mysql','database'):
            database = config.get('mysql','database')
        if config.has_option('mysql','user'):
            user = config.get('mysql','user')
        if config.has_option('mysql','password'):
            password = config.get('mysql','password')
        if config.has_option('mysql','host'):
            host = config.get('mysql','host')
        if config.has_option('mysql','port'):
            port = config.get('mysql','port')
    except ConfigParser.NoSectionError:
        return 3

    if database == '' or user == '' or password == '':
        return 4

    try:
        port = int(port)
    except ValueError:
        pass

    try:
        db = MySQLdb.connect(host=host, user=user, passwd=password, db=database, port=port)
    except MySQLdb.OperationalError, message:
        if message[0] == 2002 or message[0] == 2003:    # server not running
            return 101
        else:
            return 6

    c = db.cursor()

    c.execute("SELECT TRIGGER_NAME, ACTION_STATEMENT, ACTION_TIMING, EVENT_MANIPULATION, EVENT_OBJECT_TABLE from information_schema.TRIGGERS where trigger_schema='" + database + "' and TRIGGER_NAME LIKE 'hg_jos_%';")

    triggers = c.fetchall()

    for trigger in triggers:
        sql = 'DROP TRIGGER IF EXISTS ' + trigger[0] + ';'
        if debug: print sql
        c.execute(sql)

    return 0

def status(args):
    if debug: print "Checking hubgraph configuration status"

    uid = os.geteuid()

    pwname = pwd.getpwuid(uid).pw_name

    if uid != 0 and pwname != 'hubgraph':
        print "Insufficient access privileges. Script must be run as root or as user 'hubgraph'"
        return 1

    if not os.path.exists("/etc/hubgraph.conf"):
        print "/etc/hubgraph.conf does not exist"
        return 2

    config = ConfigParser.ConfigParser()

    if config.read("/etc/hubgraph.conf") == []:
        print "/etc/hubgraph.conf not readable"
        return 3

    if not config.has_section('mysql'):
        print "No [mysql] section in /etc/hubgraph.conf"
        return 4

    database = ''
    user = ''
    password = ''
    host = 'localhost'
    port = 3306

    try:
        if config.has_option('mysql','database'):
            database = config.get('mysql','database')
        if config.has_option('mysql','user'):
            user = config.get('mysql','user')
        if config.has_option('mysql','password'):
            password = config.get('mysql','password')
        if config.has_option('mysql','host'):
            host = config.get('mysql','host')
        if config.has_option('mysql','port'):
            port = config.get('mysql','port')
    except ConfigParser.NoSectionError:
        print "No [mysql] section in /etc/hubgraph.conf"
        return 5

    if database == '' or user == '' or password == '':
        print "Missing [mysql] database, user or password option in /etc/hubgraph.conf"
        return 6

    print "ok"
    return 0

def initHubgraph(args):

    if debug: print "Initializing hubzero-hubgraph"

    config = ConfigParser.ConfigParser()

    if config.read("/etc/hubgraph.conf") == []:
        return 1

    if not config.has_section('mysql'):
        return 2

    database = ''
    user = ''
    password = ''
    host = 'localhost'
    port = 3306

    try:
        if config.has_option('mysql','database'):
            database = config.get('mysql','database')
        if config.has_option('mysql','user'):
            user = config.get('mysql','user')
        if config.has_option('mysql','password'):
            password = config.get('mysql','password')
        if config.has_option('mysql','host'):
            host = config.get('mysql','host')
        if config.has_option('mysql','port'):
            port = config.get('mysql','port')
    except ConfigParser.NoSectionError:
        return 3

    if database == '' or user == '' or password == '':
        return 4

    try:
        port = int(port)
    except ValueError:
        pass

    try:
        db = MySQLdb.connect(host=host, user=user, passwd=password, db=database, port=port)
    except MySQLdb.OperationalError, message:
        if message[0] == 2002 or message[0] == 2003:    # server not running
            return 101
        else:
            return 6

    db.set_character_set('utf8')

    idName = { 'xprofiles' : 'uidNumber', 
               'xprofiles_bio': 'uidNumber',
               'xgroups'      : 'gidNumber' }
              
    notes = { 'vote_log': [ {'table': 'category'}, {'id': 'referenceid'} ],
              'wishlist_vote': [ {'wish_id': 'wishid'} ],
              'faq_categories': [ {'alias': 'alias'} ],
              'wiki_page_authors': [ {'page_id': 'page_id'}, {'user_id': 'user_id'} ],
              'resource_taxonomy_audience': [{'resource_id': 'rid'} ],
              'resource_assoc': [ {'parent_id': 'parent_id'}, {'child_id': 'child_id'} ],
              'wiki_version': [ {'page_id': 'pageid'} ],
              'forum_categories': [ {'alias': 'alias'} ],
              'forum_sections': [ {'alias': 'alias'} ],
              'categories': [ {'alias': 'alias'} ],
              'answers_responses': [ {'question_id': 'question_id'} ],
              'tags_object': [ {'other_id': 'objectid'}, {'table': 'tbl'} ],
              'author_assoc': [ {'other_id': 'subid'}, {'table': 'subtable'} ],
              'document_resource_rel': [ {'other_id': 'resource_id'} ],
              'xgroups_members': [ {'group_id': 'gidNumber'}, {'user_id': 'uidNumber'} ] }

    tables = set()

    for file in glob.glob('/usr/lib/hubzero-hubgraph/node_modules/hubgraph-hubzero/lib/resources/*.js'):
        tables |= set(re.compile(r'jos_[a-zA-Z0-9_]+').findall(open(file,'r').read()))              

    if 'jos_xgroups_members' in tables: tables.remove('jos_xgroups_members')

    warnings.filterwarnings('ignore','Trigger does not exist')
    warnings.filterwarnings('ignore', category = MySQLdb.Warning)

    c = db.cursor()

    sql = "CREATE TABLE IF NOT EXISTS `hg_update_queue` ( `action` enum('INSERT','UPDATE','DELETE') NOT NULL, `table_name` varchar(50) NOT NULL, `id` int(11) NOT NULL, `other_id` int(11) DEFAULT NULL, `note` text) ENGINE=MyISAM DEFAULT CHARSET=utf8"
    if debug: print sql
    c.execute(sql)

    sql = "SELECT TRIGGER_NAME, ACTION_STATEMENT, ACTION_TIMING, EVENT_MANIPULATION, EVENT_OBJECT_TABLE from information_schema.TRIGGERS where trigger_schema='" + database + "' and TRIGGER_NAME LIKE 'hg_jos_%';"
    if debug: print sql
    c.execute(sql)

    triggers = c.fetchall()
    triggerdef = {}

    for trigger in triggers:

        triggerdef[trigger[0]] = trigger[1:]

        table = trigger[0][3:-15]
        if table not in tables:
            sql = 'DROP TRIGGER IF EXISTS ' + trigger[0] + ';'
            if debug: print sql
            c.execute(sql)

    for tbl in tables:
        baseName = re.sub('^jos_','',tbl)

        id = 'id' if baseName not in idName else idName[baseName]

        c.execute("SELECT COUNT(*) AS count FROM information_schema.TABLES where table_name = '" + tbl + "' AND table_schema = database()")

        if c.fetchone()[0] < 1:
            continue

        try:
            trigger_name = 'hg_' + tbl + '_insert_trigger'
            action_statement = 'BEGIN INSERT INTO hg_update_queue(action, table_name, id) VALUES (\'INSERT\', \'' + baseName + '\', NEW.' + id + '); END'
            action_timing = 'AFTER'
            event_manipulation = 'INSERT'
            event_object_table = tbl
            sql = 'CREATE TRIGGER ' + trigger_name + ' ' + action_timing + ' ' + event_manipulation + ' ON ' + event_object_table + ' FOR EACH ROW ' + action_statement + ';'

            if trigger_name in triggerdef:
                trigger = triggerdef[trigger_name]
            else:
                trigger = ['','','','']

            if trigger[0] != action_statement or trigger[1] != action_timing or trigger[2] != event_manipulation or trigger[3] != event_object_table:
                if debug: print sql
                c.execute('DROP TRIGGER IF EXISTS ' + trigger_name + ';')
                c.execute(sql)


        except Exception as e:
            print e
            pass

        try:
            trigger_name = 'hg_' + tbl + '_update_trigger'
            action_statement = 'BEGIN INSERT INTO hg_update_queue(action, table_name, id) VALUES (\'UPDATE\', \'' + baseName + '\', NEW.' + id + '); END'
            action_timing = 'AFTER'
            event_manipulation = 'UPDATE'
            event_object_table = tbl          
            sql = 'CREATE TRIGGER ' + trigger_name + ' ' + action_timing + ' ' + event_manipulation + ' ON ' + event_object_table + ' FOR EACH ROW ' + action_statement + ';'
            if trigger_name in triggerdef:
                trigger = triggerdef[trigger_name]
            else:
                trigger = ['','','','']

            if trigger[0] != action_statement or trigger[1] != action_timing or trigger[2] != event_manipulation or trigger[3] != event_object_table:
                if debug: print sql
                c.execute('DROP TRIGGER IF EXISTS ' + trigger_name + ';')
                c.execute(sql)
        except Exception as e:
            print e
            pass

        try:
            trigger_name = 'hg_' + tbl + '_delete_trigger'
            action_statement = 'BEGIN INSERT INTO hg_update_queue(action, table_name, id) VALUES (\'UPDATE\', \'' + baseName + '\', NEW.' + id + '); END'
            action_timing = 'BEFORE'
            event_manipulation = 'DELETE'
            event_object_table = tbl          

            if baseName in notes:
                noteConcat = []

                for n in notes[baseName]:
                    for k,v in n.iteritems():
                        noteConcat.append('"' + k + '": "\', OLD.' + v + ', \'"')
        
                action_statement = 'BEGIN INSERT INTO hg_update_queue(action, table_name, id, note) VALUES (\'DELETE\', \'' + baseName + '\', OLD.' + id + ', concat(\'{' + string.join(noteConcat, ', ') + '}\')); END'
            else:
                action_statement = 'BEGIN INSERT INTO hg_update_queue(action, table_name, id) VALUES (\'DELETE\', \'' + baseName + '\', OLD.' + id + '); END'

            sql = 'CREATE TRIGGER ' + trigger_name + ' ' + action_timing + ' ' + event_manipulation + ' ON ' + event_object_table + ' FOR EACH ROW ' + action_statement + ';'
            if trigger_name in triggerdef:
                trigger = triggerdef[trigger_name]
            else:
                trigger = ['','','','']

            if trigger[0] != action_statement or trigger[1] != action_timing or trigger[2] != event_manipulation or trigger[3] != event_object_table:
                if debug: print sql
                c.execute('DROP TRIGGER IF EXISTS ' + trigger_name + ';')
                c.execute(sql)
        except Exception as e:
            print e
            pass

    return 0

parser = argparse.ArgumentParser(prog="hzhubgraph-config")
parser.add_argument('--debug', dest='debug', action='store_true', help='enable debugging output', default=False)

subparsers = parser.add_subparsers()

parser_configure = subparsers.add_parser('configure', help='configure hubzero-hubgraph package')
parser_configure.set_defaults(func=configureHubgraph)
action = parser_configure.add_mutually_exclusive_group(required=True)
action.add_argument('--enable', dest='enable', action='store_true', help='enable hubzero-hubgraph configuration',default=True)
action.add_argument('--disable', dest='enable', action='store_false', help='disable hubzero-hubgraph configuration',default=False)

parser_init = subparsers.add_parser('init', help='initialize hubzero-hubgraph package')
parser_init.set_defaults(func=initHubgraph)

parser_init = subparsers.add_parser('status', help='return status of hubzero-hubgraph configuration')
parser_init.set_defaults(func=status)

parser_init = subparsers.add_parser('remove-triggers', help='remove hubzero-hubgraph triggers')
parser_init.set_defaults(func=removeTriggers)

args =  parser.parse_args()

debug = args.debug

ret = args.func(args)

sys.exit(ret)
