#!/usr/bin/env python3


import os
import re
import sys
import time
import argparse
import datetime
import fileinput
import subprocess

class BeosRunnerException(Exception):
    def __init__(self, message):
        self.message = message
    def __str__(self):
        return self.message

def run_wallet_unlock_postconf():
    wallet_password = None
    with open("${WALLET_PASSWORD_PATH}", "r") as password_file:
        wallet_password = password_file.readline()

    parameters = ["${CLEOS_EXECUTABLE}", 
        "wallet", "unlock", 
        "-n", "${MASTER_WALLET_NAME}", 
        "--password", wallet_password
    ]
    print("Unlocking your wallet with command: {0}".format(" ".join(parameters)))
    subprocess.Popen(parameters)

def save_screen_cfg(cfg_file_name, log_file_path):
    with open(cfg_file_name, "w") as cfg:
        cfg.write("logfile {0}\n".format(log_file_path))
        cfg.write("deflog on\n")
        cfg.write("logfile flush 1\n")

def save_pid_file(pid_file_name, exec_name):
    with open(pid_file_name, "w") as pid_file:
            pid_file.write("{0}-{1}\n".format(exec_name, datetime.datetime.now().strftime("%Y-%m-%d")))

def wait_for_string_in_file(log_file_name, string, timeout):
    step = 0.5
    to_timeout = 0.
    while True:
        time.sleep(step)
        to_timeout = to_timeout + step
        if to_timeout > timeout:
            msg = "Timeout during wait for string {0}".format(string)
            print(msg)
            raise BeosRunnerException(msg)
        if not os.path.exists(log_file_name):
            continue
        with open(log_file_name, "r") as log_file:
            leave = False
            for line in log_file.readlines()[-5:]:
                if string in line:
                    leave = True
                    break
            if leave:
                break

def run_keosd_postconf(ip_address, port, wallet_dir, use_https = False):
    parameters = None
    if use_https:
        # run kleosd in https mode
        parameters = [
            "${KEOSD_EXECUTABLE}",
            "--https-server-address","{0}:{1}".format(ip_address, port),
            "--https-certificate-chain-file", "${KEOSD_CERTIFICATE_CHAIN_FILE}",
            "--https-private-key-file", "${KEOSD_PRIVATE_KEY_FILE}",
            "--wallet-dir", wallet_dir,
        ]
    else:
        # run kleosd in http mode
        parameters = [
            "${KEOSD_EXECUTABLE}",
            "--http-server-address","{0}:{1}".format(ip_address, port) ,
            "--wallet-dir", wallet_dir,
        ]
    
    log_file_name = "./{0}-{1}.log".format("keosd", datetime.datetime.now().strftime("%Y-%m-%d"))
    save_screen_cfg("./keosd_screen.cfg", log_file_name)
    screen_params = [
        "screen",
        "-m",
        "-d",
        "-L",
        "-c",
        "./keosd_screen.cfg",
        "-S",
        "{0}-{1}".format("keosd", datetime.datetime.now().strftime("%Y-%m-%d"))
    ]

    parameters = screen_params + parameters
    print("Running keosd with command: {0}".format(" ".join(parameters)))
    try:
        subprocess.Popen(parameters)
        save_pid_file("./run_keosd.pid", "keosd")
        wait_for_string_in_file(log_file_name, "add api url: /v1/wallet/unlock", 10.)
        run_wallet_unlock_postconf()
    except Exception as ex:
        print("Exception during keosd run: {0}".format(ex))
        kill_process("./run_keosd.pid", "keosd", "${KEOSD_IP_ADDRESS}", "${KEOSD_PORT}")
        sys.exit(1)

def run_nodeos_postconf(node_index, name, public_key, use_https = False):
    working_dir = "{0}{1}-{2}/".format("${NODEOS_WORKING_DIR}", node_index, name)

    parameters = [
        "${NODEOS_EXECUTABLE}",
        "--contracts-console",
        "--blocks-dir", os.path.abspath(working_dir) + '/blocks',
        "--config-dir", os.path.abspath(working_dir),
        "--data-dir", os.path.abspath(working_dir),
    ]

    log_file_name = "./{0}-{1}.log".format("nodeos", datetime.datetime.now().strftime("%Y-%m-%d"))
    save_screen_cfg("./nodeos_screen.cfg", log_file_name)
    screen_params = [
        "screen",
        "-m",
        "-d",
        "-L",
        "-c",
        "./nodeos_screen.cfg",
        "-S",
        "{0}-{1}".format("nodeos", datetime.datetime.now().strftime("%Y-%m-%d"))
    ]

    parameters = screen_params + parameters
    print("Running nodeos with command: {0}".format(" ".join(parameters)))
    try:
        subprocess.Popen(parameters)
        save_pid_file("./run_nodeos.pid", "nodeos")
        wait_for_string_in_file(log_file_name, "] Produced block", 60.)
    except Exception as ex:
        print("Exception during nodeos run: {0}".format(ex))
        kill_process("./run_nodeos.pid", "nodeos", "${NODEOS_IP_ADDRESS}", "${NODEOS_PORT}")
        sys.exit(1)

def kill_process(pid_file_name, proc_name, ip_address, port):
    pids = []
    pid_name = None
    with open(pid_file_name, "r") as pid_file:
        pid_name = pid_file.readline()
        pid_name = pid_name.strip()
    for line in os.popen("ps ax | grep " + proc_name + " | grep -v grep"):
        if pid_name is not None and pid_name in line:
            line = line.strip().split()
            pids.append(line[0])
    for pid in pids:
        for line in os.popen("ps --no-header --ppid {0}".format(pid)):
            line = line.strip().split()
            os.kill(int(line[0]), 2)
        os.kill(int(pid), 2)
    if os.path.exists(pid_file_name):
        os.remove(pid_file_name)

def clone_nodeos( _path_to_clone_to, _clone_index, _name, _additional_producers = None, _clear_data_dir = False):
    config_ini = "config.ini"
    p2p_listen_endpoint = "p2p-listen-endpoint"
    http_server_address = "http-server-address"
    working_dir = "{0}{1}-{2}/".format("${NODEOS_WORKING_DIR}", "${START_NODE_INDEX}", "eosio")
    copied_working_dir  = "{0}/{1}-{2}/".format(_path_to_clone_to, _clone_index, _name)
    if os.path.exists(copied_working_dir) and _clear_data_dir:
        clear = "rm -rf {0}".format(copied_working_dir)
        subprocess.call(clear, shell=True)
    if not os.path.exists(copied_working_dir):
        copy = "cp -rf {0} {1}".format(working_dir, copied_working_dir)
        subprocess.call(copy, shell=True)
        with fileinput.FileInput(copied_working_dir + '/' + config_ini, inplace=True) as config:
            for line in config:
                if p2p_listen_endpoint in line or http_server_address in line:
                    port = re.findall(":[0-9]+", line)
                    if port:
                        if p2p_listen_endpoint in line:
                            print(line.replace(port[0][1:], str(int(port[0][1:])+_clone_index)), end='') 
                        else:
                            print(line.replace(port[0][1:], str(_clone_index)), end='')
                    else :
                        print(line, end='')
                else :
                    print(line, end='')
    if _additional_producers:
        with open(copied_working_dir + '/' + config_ini, "a") as config:
            for prod, key in _additional_producers.items():
                config.write("producer-name = {0}\n".format(prod))
                config.write("signature-provider = {0}=KEOSD:http://${KEOSD_IP_ADDRESS}:${KEOSD_PORT}/v1/wallet/sign_digest\n".format(key))


def run_custom_nodeos(_node_index, _name, _path_to_data_dir, _log_path):
    working_dir = "{0}/{1}-{2}/".format(_path_to_data_dir, _node_index, _name)

    parameters = [
        "${NODEOS_EXECUTABLE}",
        "--contracts-console",
        "--blocks-dir", os.path.abspath(working_dir) + '/blocks',
        "--config-dir", os.path.abspath(working_dir),
        "--data-dir", os.path.abspath(working_dir),
    ]

    log_file_name = _log_path+"/{0}-{1}-{2}-{3}.log".format("nodeos", datetime.datetime.now().strftime("%Y-%m-%d-%H:%M:%S"), _node_index, _name)

    save_screen_cfg(_path_to_data_dir+"/nodeos_screen_{0}_{1}.cfg".format(_node_index, _name), log_file_name)
    screen_params = [
        "screen",
        "-m",
        "-d",
        "-L",
        "-c",
        _path_to_data_dir+"/nodeos_screen_{0}_{1}.cfg".format(_node_index, _name),
        "-S",
        "{0}-{1}".format("nodeos-{0}".format(_node_index), datetime.datetime.now().strftime("%Y-%m-%d"))
    ]

    parameters = screen_params + parameters
    print("Running nodeos with command: {0}".format(" ".join(parameters)))
    try:
        subprocess.Popen(parameters)
        save_pid_file(_path_to_data_dir+"/run_nodeos_{0}_{1}.pid".format(_node_index, _name), "nodeos-{0}".format(_node_index))
        wait_for_string_in_file(log_file_name, "] Produced block", 60.)
    except Exception as ex:
        print("Exception during nodeos run: {0}".format(ex))
        kill_process(_path_to_data_dir+"/run_nodeos_{0}_{1}.pid".format(_node_index, _name), "nodeos-{0}".format(_node_index), "${NODEOS_IP_ADDRESS}", "${NODEOS_PORT}")
        sys.exit(1)

description = """\nUsage:
    \nTo run beos wallet and node use: python3 run.py
    \nTo kill running beos wallet and node use: python3 run.py --cancel
    \nTo only run beos wallet use: python3 run.py --wallet
    \nTo kill running beos wallet and node use: python3 run.py --cancel --wallet """

parser = argparse.ArgumentParser(description=description)
parser.add_argument('--cancel', help="Cancel runned nodeo(s) and keosd.", default=False, action="store_true")
parser.add_argument('--wallet',  help="Run/kill wallet.", default=False, action="store_true")
parser.add_argument('--node',  help="Run/kill node.", default=False, action="store_true")

if __name__ == "__main__":
    args = parser.parse_args()
    if args.cancel:
        if args.wallet or args.node:
            if args.wallet:
                kill_process("./run_keosd.pid", "keosd", "${KEOSD_IP_ADDRESS}", "${KEOSD_PORT}")
            if args.node:
                kill_process("./run_nodeos.pid", "nodeos", "${NODEOS_IP_ADDRESS}", "${NODEOS_PORT}")
            sys.exit(0)
        else:
            kill_process("./run_keosd.pid", "keosd", "${KEOSD_IP_ADDRESS}", "${KEOSD_PORT}")
            kill_process("./run_nodeos.pid", "nodeos", "${NODEOS_IP_ADDRESS}", "${NODEOS_PORT}")
            sys.exit(0)

    if args.wallet or args.node:
        if args.wallet:
            if os.path.exists("./run_keosd.pid"):
                print("run_keosd.pid exists in the filesystem. Please use --cancel option first")
                sys.exit(1)
            run_keosd_postconf("${KEOSD_IP_ADDRESS}", "${KEOSD_PORT}", "${DEFAULT_WALLET_DIR}")
        if args.node:
            if os.path.exists("./run_nodeos.pid"):
                print("run_nodeos.pid exists in the filesystem. Please use --cancel option first")
                sys.exit(1)
            run_nodeos_postconf(0, "eosio", "${EOSIO_ROOT_KEY}")
    else:
        if os.path.exists("./run_keosd.pid"):
            print("run_keosd.pid exists in the filesystem. Please use --cancel option first")
            sys.exit(1)
        if os.path.exists("./run_nodeos.pid"):
            print("run_nodeos.pid exists in the filesystem. Please use --cancel option first")
            sys.exit(1)
        run_keosd_postconf("${KEOSD_IP_ADDRESS}", "${KEOSD_PORT}", "${DEFAULT_WALLET_DIR}")
        run_nodeos_postconf(0, "eosio", "${EOSIO_ROOT_KEY}")
        sys.exit(0)




