#!/usr/bin/env python
"""
sd api txt2img benchmark
"""
import os
import asyncio
import base64
import io
import json
import time
import argparse
from PIL import Image
import sdapi
from util import Map, log


oom = 0
args = None
options = None


async def txt2img():
    t0 = time.perf_counter()
    data = {}
    try:
        data = await sdapi.post('/sdapi/v1/txt2img', options)
    except Exception:
        return -1
    if 'error' in data:
        return -1
    if 'info' in data:
        info = Map(json.loads(data['info']))
    else:
        return 0
    log.debug({ 'info': info })
    if options['batch_size'] != len(data['images']):
        log.error({ 'requested': options['batch_size'], 'received': len(data['images']) })
        return 0
    for i in range(len(data['images'])):
        data['images'][i] = Image.open(io.BytesIO(base64.b64decode(data['images'][i].split(',',1)[0])))
        if args.save:
            fn = os.path.join(args.save, f'benchmark-{i}-{len(data["images"])}.png')
            data["images"][i].save(fn)
            log.debug({ 'save': fn })
    log.debug({ "images": data["images"] })
    t1 = time.perf_counter()
    return t1 - t0


def memstats():
    mem = sdapi.getsync('/sdapi/v1/memory')
    cpu = mem.get('ram', 'unavailable')
    gpu = mem.get('cuda', 'unavailable')
    if 'active' in gpu:
        gpu['session'] = gpu.pop('active')
    if 'reserved' in gpu:
        gpu.pop('allocated')
        gpu.pop('reserved')
        gpu.pop('inactive')
    if 'events' in gpu:
        global oom # pylint: disable=global-statement
        oom = gpu['events']['oom']
        gpu.pop('events')
    return cpu, gpu


def gb(val: float):
    return round(val / 1024 / 1024 / 1024, 2)


async def main():
    sdapi.quiet = True
    await sdapi.session()
    await sdapi.interrupt()
    ver = await sdapi.get("/sdapi/v1/version")
    log.info({ 'version': ver})
    platform = await sdapi.get("/sdapi/v1/platform")
    log.info({ 'platform': platform })
    opts = await sdapi.get('/sdapi/v1/options')
    opts = Map(opts)
    log.info({ 'model': opts.sd_model_checkpoint })
    cpu, gpu = memstats()
    log.info({ 'system': { 'cpu': cpu, 'gpu': gpu }})
    batch = [1, 1, 2, 4, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256]
    batch = [b for b in batch if b <= args.maxbatch]
    log.info({"batch-sizes": batch})
    for i in range(len(batch)):
        if oom > 0:
            continue
        options['batch_size'] = batch[i]
        warmup = await txt2img()
        ts = await txt2img()
        if i == 0:
            ts += warmup
        if ts > 0.01: # cannot be faster than 10ms per run
            await asyncio.sleep(0)
            cpu, gpu = memstats()
            if i == 0:
                log.info({ 'warmup': round(ts, 2) })
            else:
                peak = gpu['system']['used'] # gpu['session']['peak'] if 'session' in gpu else 0
                log.info({ 'batch': batch[i], 'its': round(options.steps / (ts / batch[i]), 2), 'img': round(ts / batch[i], 2), 'wall': round(ts, 2), 'peak': gb(peak), 'oom': oom > 0 })
        else:
            await asyncio.sleep(10)
            cpu, gpu = memstats()
            log.info({ 'batch': batch[i], 'result': 'error', 'gpu': gpu, 'oom': oom > 0 })
            break
    if oom > 0:
        log.info({ 'benchmark': 'ended with oom so you should probably restart your automatic server now' })
    await sdapi.close()


if __name__ == '__main__':
    log.info({ 'run-benchmark' })
    parser = argparse.ArgumentParser(description = 'run-benchmark')
    parser.add_argument("--steps", type=int, default=50, required=False, help="steps")
    parser.add_argument("--sampler", type=str, default='Euler a', required=False, help="Use specific sampler")
    parser.add_argument("--prompt", type=str, default='photo of two dice on a table', required=False, help="prompt")
    parser.add_argument("--negative", type=str, default='foggy, blurry', required=False, help="prompt")
    parser.add_argument("--maxbatch", type=int, default=16, required=False, help="max batch size")
    parser.add_argument("--width", type=int, default=512, required=False, help="width")
    parser.add_argument("--height", type=int, default=512, required=False, help="height")
    parser.add_argument('--debug', default = False, action='store_true', help = 'debug logging')
    parser.add_argument('--taesd', default = False, action='store_true', help = 'use taesd as vae')
    parser.add_argument("--save", type=str, default='', required=False, help="save images to folder")
    args = parser.parse_args()
    if args.debug:
        log.setLevel('DEBUG')
    options = Map(
        {
            "prompt": args.prompt,
            "negative_prompt": args.negative,
            "steps": args.steps,
            "sampler_name": args.sampler,
            "width": args.width,
            "height": args.height,
            "full_quality": not args.taesd,
            "cfg_scale": 0,
            "batch_size": 1,
            "n_iter": 1,
            "seed": -1,
        }
    )
    log.info({"options": options})
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        log.warning({ 'interrupted': 'keyboard request' })
        sdapi.interruptsync()
