from bottle import request, response
import bottle
import psycopg
import random
from contextlib import contextmanager

from data import midi2wavtool, wavtoolm2midi

dbconnstr = "dbname=composer_new_dataset_v3 host=localhost"

app = bottle.Bottle()


@app.route("/<:re:.*>", method="OPTIONS")
def enable_cors_generic_route():
    add_cors_headers()


@app.hook("after_request")
def enable_cors_after_request_hook():
    add_cors_headers()


def add_cors_headers():
    response.headers["Access-Control-Allow-Origin"] = "*"
    response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, OPTIONS"
    response.headers["Access-Control-Allow-Headers"] = (
        "Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token"
    )


@contextmanager
def dbconn():
    with psycopg.connect(dbconnstr) as conn:
        conn.autocommit = True
        yield conn


def setup_tag(cur, tag_value):
    cur.execute(
        "INSERT INTO tag_values (value) VALUES (%s) ON CONFLICT DO NOTHING",
        (tag_value,),
    )
    cur.execute("SELECT id FROM tag_values WHERE value = %s", (tag_value,))
    return cur.fetchone()[0]


def setup_tags(conn):
    with conn.cursor() as cur, conn.transaction():
        return setup_tag(cur, "Good at Time"), setup_tag(cur, "Bad at Time")


@app.post("/rate")
def rate_clip():
    with dbconn() as conn, conn.cursor() as cur:
        good_tag_id, bad_tag_id = setup_tags(conn)

        data = request.json
        if not isinstance(data, dict):
            return {"error": "Invalid JSON"}

        if "id" not in data or "is_good" not in data:
            return {"error": "Missing fields"}

        file_id, start = data["id"].split("-")
        file_id = int(file_id)
        start = int(start)

        cur.execute(
            "INSERT INTO manual_file_tags (file_id, tag_value_id, start) VALUES (%s, %s, %s)",
            (file_id, good_tag_id if data["is_good"] else bad_tag_id, start),
        )

        return {"success": True}


def dedupe_clips(clips):
    accum = []
    for wclip in clips:
        for note in wavtoolm2midi(wclip):
            if not all(
                anote["note"] != note["note"]
                or anote["offBeat"] <= note["onBeat"]
                or anote["onBeat"] >= note["offBeat"]
                for anote in accum
            ):
                continue
            accum.append(note)
    return midi2wavtool(accum)


@app.post("/clip")
def get_clip():
    with dbconn() as conn, conn.cursor() as cur:
        good_tag_id, bad_tag_id = setup_tags(conn)

        def sample():
            # choose a random file
            randval = random.random()
            cur.execute(
                "select id from files where rand_order > %s order by rand_order asc limit 1",
                (randval,),
            )
            if cur.rowcount == 0:
                return None
            file_id = cur.fetchone()[0]

            # choose a random time from the file that hasn't already been tagged
            cur.execute(
                """
                with starts as (
                    select distinct start::integer from extracted_clips where file_id = %s
                ) select start from starts where not exists (
                    select from manual_file_tags t where
                      file_id = %s
                      and tag_value_id = any(%s)
                      and t.start = starts.start
                ) order by random() limit 1;
                """,
                (file_id, file_id, [good_tag_id, bad_tag_id]),
            )
            if cur.rowcount == 0:
                return None
            start = cur.fetchone()[0]

            # get all extracted clips for the chosen time
            cur.execute(
                """
                select n.notes, exists (
                    select from automatic_extracted_clip_tags t
                    join tag_values v on t.tag_value_id = v.id
                    where t.extracted_clip_id = c.id and v.value in ('ML Analyzed Drums', 'Analyzed Drums', 'Flagged Drums')
                ) as is_drums
                from extracted_clips c
                join extracted_clip_notes n on c.id = n.extracted_clip_id
                where c.file_id = %s and c.start = %s
                and c.symbolic_length is not null
                and exists (
                    select from automatic_extracted_clip_tags t
                    join tag_values v on t.tag_value_id = v.id
                    where t.extracted_clip_id = c.id and v.value = 'Trainable as Main'
                )
                """,
                (file_id, start),
            )
            rows = cur.fetchall()
            if len(rows) == 0:
                return None

            return {
                "id": f"{file_id}-{start}",
                "piano": dedupe_clips([row[0] for row in rows if not row[1]]),
                "drums": dedupe_clips([row[0] for row in rows if row[1]]),
            }

        for _ in range(100):
            clip = sample()
            if clip is not None:
                return clip
        response.status = 404
        return {"error": "No clips available"}


app.run(host="0.0.0.0", port=12349, debug=False)
