from tqdm import tqdm
from data import Vocab, wavtoolm2midi, VocabError
import torch.multiprocessing as mp
import sys
import json
import psycopg

# TODO make this into a more flexible tool with Fire
# TODO no reset / select dataset should be options


def reset_lengths(conn):
    with conn.transaction(), conn.cursor() as cur:
        cur.execute("ALTER TABLE extracted_clips DROP COLUMN IF EXISTS symbolic_length")
        cur.execute("ALTER TABLE extracted_clips ADD COLUMN symbolic_length smallint")


def update_lengths_worker(args):
    vocab, dbconnstr, rank, size, limit = args
    with psycopg.connect(dbconnstr) as conn:
        conn.autocommit = True
        whereclause = """
            WHERE c.id %% %s = %s
        """
        with conn.transaction():
            with conn.cursor() as cur:
                cur.execute(
                    "SELECT count(1) FROM extracted_clips c JOIN files f ON c.file_id = f.id "
                    + whereclause,
                    (size, rank),
                )
                total = cur.fetchone()[0]
                cur.execute("SET cursor_tuple_fraction = 0.0001")  # dank
            with conn.cursor(
                name=f"update_lengths_{rank}"
            ) as cur, conn.cursor() as updcur:
                count = 0
                success_count = 0
                cur.itersize = 100

                cur.execute(
                    "SELECT c.id, cn.notes FROM extracted_clips c JOIN extracted_clip_notes cn ON c.id = cn.extracted_clip_id JOIN files f ON c.file_id = f.id "
                    + whereclause
                    + "ORDER BY c.id ASC",
                    (size, rank),
                )

                updlst = []

                def flush():
                    nonlocal updlst
                    updcur.executemany(
                        "UPDATE extracted_clips SET symbolic_length = %s WHERE id = %s",
                        updlst,
                    )
                    updlst = []

                for id, notes in tqdm(cur, total=total, disable=rank != 0):
                    count += 1
                    if rank == 0 and count % 10000 == 0:
                        tqdm.write(f"success rate: {success_count / count}")
                    try:
                        res = vocab.fast_estimate_length(wavtoolm2midi(notes))
                    except VocabError as e:
                        continue
                    if res > 30000 or res <= 5:
                        continue
                    updlst.append((res, id))
                    success_count += 1
                    if len(updlst) >= 1000:
                        flush()
                    if limit is not None and count >= limit:
                        break
                if len(updlst) > 0:
                    flush()


def update_lengths(vocab, parallelism, dbconnstr, limit=None):
    with psycopg.connect(dbconnstr) as conn:
        conn.autocommit = True
        reset_lengths(conn)
        with mp.Pool(parallelism) as pool:
            pool.map(
                update_lengths_worker,
                [(vocab, dbconnstr, i, parallelism, limit) for i in range(parallelism)],
            )
        with conn.cursor() as cur:
            cur.execute("ANALYZE extracted_clips")


if __name__ == "__main__":
    with open(sys.argv[1], "r") as f:
        config = json.load(f)
    vocab = Vocab.from_config(config)
    update_lengths(
        vocab,
        4,
        config["dataset_class"]["conn_str"],
        limit=int(sys.argv[2]) if len(sys.argv) > 2 else None,
    )
