#!/usr/bin/env python3
from __future__ import print_function

import os
import json
try:
    import httplib                                  # Python 2
except ModuleNotFoundError:
    import http.client as httplib                   # Python 3
try:
    from urllib.parse import urlparse, urlencode    # Python 3
except ImportError:
    from urllib import urlencode                    # Python 2
    from urlparse import urlparse                   # Python 2
import sys
from optparse import OptionParser
import ssl
import logging
import getpass
import subprocess
import shlex
try:
    import readline
except:  # Probably windows
    pass

try:
    _create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
    # Legacy Python that doesn't verify HTTPS certificates by default
    pass
else:
    # Handle target environment that doesn't support HTTPS verification
    ssl._create_default_https_context = _create_unverified_https_context

def getService():
    credList = []  
    for i in range (0, len(creds), 2):
        key = creds[i]   
        if creds[i + 1] == "-":
            value = getpass.getpass()
        else:
            value = creds[i + 1]
        credList.append({key:value})
    jsonDump = {}
    jsonDump["plugin"] = plugin
    jsonDump["credentials"] = credList
    parameters = {"json": json.dumps(jsonDump)}
    result = json.loads(_process("session", parameters, "POST").read())  
    return result["sessionId"]
     
def clear(args):
    parser.set_usage(usagebase + "clear")
    parser.set_description("Clear all lucene indices")
    options, args = parser.parse_args(args)

    if len(args) > 0:
        fatal("Must have no arguments after the operation 'clear'")

    try:
        sessionId = getService()
        parameters = {"sessionId": sessionId}
        _process("lucene/db/", parameters, "DELETE")
    except Exception as e:
        fatal(e)

def commit(args): 
    parser.set_usage(usagebase + "commit")
    parser.set_description("Commit all lucene indices")
    options, args = parser.parse_args(args)

    if len(args) > 0:
        fatal("Must have no arguments after the operation 'commit'")

    try:
        sessionId = getService()
        parameters = {"sessionId": sessionId}
        _process("lucene/db/", parameters, "POST")
    except Exception as e:
        fatal(e)
        
def _process(relativeUrl, parameters, method, headers=None, body=None):
    path = icatpath + relativeUrl
    if parameters: parameters = urlencode(parameters)
    if parameters and method != "POST":
        path = path + "?" + parameters
    urllen = 4 + len(path) + len(host)
    if secure:
        conn = httplib.HTTPSConnection(host)
        urllen += 5
    else:
        conn = httplib.HTTPConnection(host)
        urllen += 4
    if urllen > 2048: fatal("Generated URI is of length " + urllen + " which exceeds 2048")
          
    conn.putrequest(method, path, skip_accept_encoding=True)
    conn.putheader("Cache-Control", "no-cache")
    conn.putheader("Pragma", "no-cache")
    conn.putheader("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2")
    conn.putheader("Connection", "keep-alive") 
     
    if parameters and method == "POST":
        conn.putheader('Content-Length', str(len(parameters)))
    elif body:
        conn.putheader('Transfer-Encoding', 'chunked')
        
    if headers:
        for header in headers:
            conn.putheader(header, headers[header])
             
    if parameters and method == "POST":
        conn.putheader('Content-Type', 'application/x-www-form-urlencoded')       
             
    conn.endheaders()
     
    if parameters and method == "POST":
        conn.send(parameters.encode("ascii"))
    elif body:
        blocksize = 8192
        datablock = body.read(blocksize)
        crc32 = 0
        while datablock:
            conn.send(hex(len(datablock))[2:] + "\r\n")
            conn.send(datablock + "\r\n")
            crc32 = zlib.crc32(datablock, crc32)
            datablock = body.read(blocksize)
        conn.send("0\r\n\r\n")
    
    response = conn.getresponse()
    rc = response.status
    if (rc // 100 != 2):
        try:
            responseContent = response.read()
            om = json.loads(responseContent)
        except Exception:
            fatal("InternalException " + responseContent.decode())
        code = om["code"]
        message = om["message"]
        fatal(code + " " + message)
    if body:
        return response, crc32 & 0xffffffff
    else:
        return response        
       
def getPopulating(args): 
    parser.set_usage(usagebase + "populating")
    parser.set_description("Find entity names in the populate list")
    options, args = parser.parse_args(args)

    if len(args) > 0:
        fatal("Must have no arguments after the operation 'populating'")
    try:
        sessionId = getService()
        parameters = {"sessionId": sessionId}
        print(_process("lucene/db", parameters, "GET").read().decode())
    except Exception as e:
        fatal(e)

def populate(args):
    parser.set_usage(usagebase + "populate [<name>]")
    parser.set_description("Populate lucene (for that entry name)")
    parser.add_option(
        "-e",
        "--entity-name",
        action="append",
        dest="entityName",
        help="Name of entity to populate.",
    )
    parser.add_option(
        "--min-id",
        dest="minId",
        help="Minimum (exclusive) ICAT entity id to populate",
        type="int",
    )
    parser.add_option(
        "--max-id",
        dest="maxId",
        help="Maximum (inclusive) ICAT entity id to populate",
        type="int",
    )
    parser.add_option(
        "-d",
        "--delete",
        dest="delete",
        action="store_true",
        help="Whether to delete all existing documents for this index",
    )
    options, args = parser.parse_args(args)
    entities = options.entityName or []
    entities += args
    if not entities:
        # This does not need to include "nested" entities such as ParameterType, as this
        # will be included in the READ operation on the DB implicitly
        entities = [
            "Datafile",
            "Dataset",
            "Investigation",
            "DatafileParameter",
			"DatasetParameter",
            "DatasetTechnique",
            "InstrumentScientist",
            "InvestigationFacilityCycle",
			"InvestigationInstrument",
            "InvestigationParameter",
            "InvestigationUser",
            "Sample",
            "SampleParameter",
        ]

    try:
        sessionId = getService()
        parameters = {"sessionId": sessionId}
        if options.minId:
            parameters["minId"] = options.minId
        if options.maxId:
            parameters["maxId"] = options.maxId
        if options.delete:
            parameters["delete"] = True
        else:
            parameters["delete"] = False
            
        for entity in entities:
            _process("lucene/db/" + entity, parameters, "POST")
    except Exception as e:
        fatal(e)

def help(args):
    parser.set_usage(usagebase + "[subcommand [parameters...] [options...]]")
    parser.set_description("Interact with the ICATAdmin interface. Subcommands are " + str(subcommands.keys()) + 
                            """ All subcommands accept a '-h' or '--help'. """) 
    parser.print_help()

logging.basicConfig(level=logging.CRITICAL)

usagebase = "usage: %prog <url> <username> <password> "
       
subcommands = {}
subcommands["populate"] = populate
subcommands["commit"] = commit
subcommands["clear"] = clear
subcommands["populating"] = getPopulating
subcommands["-h"] = help
subcommands["--help"] = help

def fatal(msg):
    print(msg, file=sys.stderr)
    sys.exit(1)

parser = OptionParser()

try:
    pos = sys.argv.index("--")
    start = sys.argv[1:pos]
    end = sys.argv[pos + 1:]
except ValueError:
    start = sys.argv[1:]
    end = []
 
if len(start) > 2:
    url, plugin = start[:2]
    creds = start[2:]
       
    o = urlparse(url)
    secure = o.scheme == "https"
    host = o.netloc
    icatpath = o.path
    if not icatpath.endswith("/"): icatpath = icatpath + "/"
    icatpath = icatpath + "icat/"
    
    if not end:
        try:
            sessionId = getService()
        except Exception as e:
            fatal(e)
       
        print("Use CTRL-D or 'exit' to exit")
        run = True
        while run:
            try:
                command = raw_input("icatadmin> ")
                if command:
                    if command == "exit":
                        run = False
                        continue
                    tokens = shlex.split(command)
                    op = subcommands.get(tokens[0])
                    if not op:
                        print("First argument must be one of " + str(subcommands.keys()))
                    else:
                        op(tokens[1:])
            except EOFError:
                print()
                sys.exit()
            except:
                pass
        sys.exit()

    op = subcommands.get(end[0])
    if op:
        op(end[1:])
    else:
        fatal("Subcommand " + end[0] + " must be one of " + str(subcommands.keys()))

else:
    fatal("First arguments must be url and plugin mnemonic followed by pairs of arguments to represent the\ncredentials (with '-' to be prompted) optionally followed by '--' and one of\n" + str(subcommands.keys()))
