#!/usr/bin/env python3
"""\
@file perfbot_run.py
@brief Run a number of non interactive Viewers (PerfBots) with
       a variety of options and settings. Pass --help for details.

$LicenseInfo:firstyear=2007&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2021, Linden Research, Inc.

This library 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;
version 2.1 of the License only.

This library 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 library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
$/LicenseInfo$
"""

import argparse
import subprocess
import os
import math
import time

# Required parameters that are always passed in
# Specify noninteractive mode (SL-15999 for details)
PARAM_NON_INTERACTIVE = "--noninteractive"
# Run multiple Viewers at once
PARAM_MULTI = "--multiple"
# Specify username (first and last) and password
PARAM_LOGIN = "--login"
# SLURL to teleport to after login
PARAM_SLURL = "--slurl"


def gen_niv_script(args):
    print(f"Reading creds from {(args.creds)} folder")
    print(f"Using the non interactive Viewer from {(args.viewer)}")
    print(f"Sleeping for {args.sleep}ms between Viewer launches")

    # Read the lines from the creds file.  Typically this will be
    # stored in the build-secrets-git private repository but you
    # can point to any location with the --creds parameter
    creds_lines = []
    with open(args.creds) as file:
        creds_lines = file.readlines()
        creds_lines = [line.rstrip() for line in creds_lines]
        creds_lines = [line for line in creds_lines if not line.startswith("#") and len(line)]
    # We cannot log in more users than we have credentials for
    if args.num==0:
        args.num = len(creds_lines)
    if args.num > len(creds_lines):
        print(
            f"The number of agents specified ({(args.num)}) exceeds "
            f"the number of valid entries ({(len(creds_lines))}) in "
            f"the creds file "
        )
        return

    print(f"Launching {(args.num)} instances of the Viewer")

    # The Viewer (in dev environments at least) needs a well specified
    # working directory to function properly. We try to guess what it
    # might be based on the full path to the Viewer executable but
    # you can also specify it explicitly with the --cwd parameter
    # (required for dev builds)
    args.viewer = os.path.abspath(args.viewer)
    if len(args.cwd) == 0:
        working_dir = os.path.dirname(os.path.abspath(args.viewer))
    else:
        working_dir = os.path.abspath(args.cwd)
    print(f"Working directory is {working_dir}, cwd {args.cwd}")
    os.chdir(working_dir)

    if args.dryrun:
        print("Running in dry-run mode - no Viewers will be started")
    print("")

    for inst in range(args.num):

        # Format of each cred line is username_first username_last password
        # A space is used to separate each and a # at the start of a line
        # removes it from the pool (useful if someone else is using a subset
        # of the available ones)
        creds = creds_lines[inst].split(" ")
        username_first = creds[0]
        username_last = creds[1]
        password = creds[2]

        # The default layout is an evenly spaced circle in the
        # center of the region.  We may extend this to allow other
        # patterns like a square/rectangle or a spiral. (Hint: it
        # likely won't be needed :))
        center_x = 128
        center_y = 128
        if args.layout == "circle":
            radius = 6
            angle = (2 * math.pi / args.num) * inst
            region_x = int(math.sin(angle) * radius + center_x)
            region_y = int(math.cos(angle) * radius + center_y)
            region_z = 0
        elif args.layout == "square":
            region_x = center_x
            region_y = center_y
        elif args.layout == "spiral":
            region_x = center_x
            region_y = center_y
        slurl = f"secondlife://{args.region}/{region_x}/{region_y}/{region_z}"

        # Build the script line
        script_cmd = [args.viewer]
        script_cmd.append(PARAM_NON_INTERACTIVE)
        script_cmd.append(PARAM_MULTI)
        script_cmd.append(PARAM_LOGIN)
        script_cmd.append(username_first)
        script_cmd.append(username_last)
        script_cmd.append(password)
        script_cmd.append(PARAM_SLURL)
        script_cmd.append(slurl)

        # Display the script we will execute.
        cmd = ""
        for p in script_cmd:
            cmd = cmd + " " + p
        print(cmd)

        # If --dry-run is specified, we do everything (including, most
        # usefully, display the script lines) but do not start the Viewer
        if args.dryrun == False:
            print("opening viewer session with",script_cmd)
            viewer_session = subprocess.Popen(script_cmd)

        # Sleeping a bit between launches seems to help avoid a CPU
        # surge when N Viewers are started simulatanously. The default
        # value can be changed with the --sleep parameter
        time.sleep(args.sleep / 1000)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(allow_abbrev=False)
    parser.add_argument(
        "--num",
        type=int,
        default=0,
        dest="num",
        help="How many avatars to add to the script",
    )
    parser.add_argument(
        "--creds",
        default="../../../build-secrets-git/perf/perfbot_creds.txt",
        dest="creds",
        help="Location of the text file containing user credentials",
    )
    parser.add_argument(
        "--viewer",
        default="C:/Program Files/SecondLife/SecondLifeViewer.exe",
        dest="viewer",
        help="Location of the non interactive Viewer build",
    )
    parser.add_argument(
        "--cwd",
        default="",
        dest="cwd",
        help="Location of the current working directory to use",
    )
    parser.add_argument(
        "--region",
        default="Lag Me 5",
        dest="region",
        help="The SLURL for the Second Life region to visit",
    )
    parser.add_argument(
        "--layout",
        default="circle",
        dest="layout",
        choices={"circle", "square", "spiral"},
        help="The geometric layout of the avatar destination locations",
    )
    parser.add_argument(
        "--sleep",
        type=int,
        default=1000,
        dest="sleep",
        help="Time to sleep between launches in milliseconds",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        dest="dryrun",
        help="Dryrun mode - display parameters and script lines but do not start any Viewers",
    )
    args = parser.parse_args()

    gen_niv_script(args)