"""Disk-backed original paired reads for context scoring within bounded RAM."""
import gzip
import itertools
import os
from pathlib import Path
import sqlite3


def fastq(path):
    path = Path(path)
    opener = gzip.open if path.suffix == '.gz' else open
    with opener(path, 'rt') as stream:
        while True:
            header = stream.readline()
            if not header:
                return
            seq, plus, qual = [stream.readline().rstrip('\n\r') for _ in range(3)]
            if not header.startswith('@') or not plus.startswith('+') or len(seq) != len(qual):
                raise ValueError('Invalid FASTQ')
            name = header[1:].split()[0]
            if name.endswith(('/1', '/2')):
                name = name[:-2]
            yield name, seq.upper(), qual


class ReadStore:
    """Read-only per-process SQLite connections; no inherited writable handle."""

    def __init__(self, path):
        self.path = Path(path).resolve(strict=True)
        self._connection = None
        self._pid = None

    @classmethod
    def build(cls, paths, output):
        if len(paths) != 2:
            raise ValueError('Expected paired FASTQ paths')
        output = Path(output)
        # Refuse overwrite, including an interrupted build.
        with output.open('xb'):
            pass
        db = sqlite3.connect(output)
        try:
            db.execute('PRAGMA cache_size=-8192')
            db.execute('PRAGMA temp_store=FILE')
            db.execute('CREATE TABLE reads(name TEXT PRIMARY KEY, s1 TEXT, q1 TEXT, s2 TEXT, q2 TEXT) WITHOUT ROWID')
            batch = []
            for a, b in itertools.zip_longest(fastq(paths[0]), fastq(paths[1])):
                if a is None or b is None or a[0] != b[0]:
                    raise ValueError('Mismatched or repeated fragment identifier')
                batch.append((a[0], a[1], a[2], b[1], b[2]))
                if len(batch) >= 1000:
                    db.executemany('INSERT INTO reads VALUES (?,?,?,?,?)', batch)
                    batch.clear()
            db.executemany('INSERT INTO reads VALUES (?,?,?,?,?)', batch)
            db.execute('CREATE TABLE complete(n INTEGER)')
            db.execute('INSERT INTO complete SELECT COUNT(*) FROM reads')
            db.commit()
        except sqlite3.IntegrityError as error:
            db.rollback()
            raise ValueError('Mismatched or repeated fragment identifier') from error
        finally:
            db.close()
        return cls(output)

    def connection(self):
        if self._pid != os.getpid():
            self.close()
            self._connection = sqlite3.connect(self.path.as_uri() + '?mode=ro', uri=True)
            self._connection.execute('PRAGMA cache_size=-8192')
            self._connection.execute('PRAGMA temp_store=FILE')
            self._connection.execute('SELECT n FROM complete').fetchone()
            self._pid = os.getpid()
        return self._connection

    def __contains__(self, name):
        return self.connection().execute('SELECT 1 FROM reads WHERE name=?', (name,)).fetchone() is not None

    def __getitem__(self, name):
        row = self.connection().execute('SELECT s1,q1,s2,q2 FROM reads WHERE name=?', (name,)).fetchone()
        if row is None:
            raise KeyError(name)
        return ((row[0], row[1]), (row[2], row[3]))

    def __len__(self):
        return self.connection().execute('SELECT n FROM complete').fetchone()[0]

    def close(self):
        if self._connection is not None:
            self._connection.close()
        self._connection = None
        self._pid = None
