package measurex

//
// DB
//
// This file defines two types:
//
// - WritableDB is the interface allowing networking code
// (e.g., Dialer to save measurement events);
//
// - MeasurementDB implements WritableDB and allows high-level
// code to generate a Measurement from all the events.
//

import "sync"

// WritableDB is an events "database" in which networking code
// (e.g., Dialer) can save measurement events (e.g., the result
// of a connect, a TLS handshake, a read).
type WritableDB interface {
	// InsertIntoDial saves a Dial event.
	InsertIntoDial(ev *NetworkEvent)

	// InsertIntoReadWrite saves an I/O event.
	InsertIntoReadWrite(ev *NetworkEvent)

	// InsertIntoClose saves a close event.
	InsertIntoClose(ev *NetworkEvent)

	// InsertIntoTLSHandshake saves a TLS handshake event.
	InsertIntoTLSHandshake(ev *QUICTLSHandshakeEvent)

	// InsertIntoLookupHost saves a lookup host event.
	InsertIntoLookupHost(ev *DNSLookupEvent)

	// InsertIntoLookupHTTPSvc saves an HTTPSvc lookup event.
	InsertIntoLookupHTTPSSvc(ev *DNSLookupEvent)

	// InsertIntoDNSRoundTrip saves a DNS round trip event.
	InsertIntoDNSRoundTrip(ev *DNSRoundTripEvent)

	// InsertIntoHTTPRoundTrip saves an HTTP round trip event.
	InsertIntoHTTPRoundTrip(ev *HTTPRoundTripEvent)

	// InsertIntoHTTPRedirect saves an HTTP redirect event.
	InsertIntoHTTPRedirect(ev *HTTPRedirectEvent)

	// InsertIntoQUICHandshake saves a QUIC handshake event.
	InsertIntoQUICHandshake(ev *QUICTLSHandshakeEvent)
}

// MeasurementDB is a WritableDB that also allows high-level code
// to generate a Measurement from all the saved events.
type MeasurementDB struct {
	// database "tables"
	dialTable          []*NetworkEvent
	readWriteTable     []*NetworkEvent
	closeTable         []*NetworkEvent
	tlsHandshakeTable  []*QUICTLSHandshakeEvent
	lookupHostTable    []*DNSLookupEvent
	lookupHTTPSvcTable []*DNSLookupEvent
	dnsRoundTripTable  []*DNSRoundTripEvent
	httpRoundTripTable []*HTTPRoundTripEvent
	httpRedirectTable  []*HTTPRedirectEvent
	quicHandshakeTable []*QUICTLSHandshakeEvent

	// mu protects all the fields
	mu sync.Mutex
}

var _ WritableDB = &MeasurementDB{}

// DeleteAll deletes all the content of the DB.
func (db *MeasurementDB) DeleteAll() {
	db.mu.Lock()
	db.dialTable = nil
	db.readWriteTable = nil
	db.closeTable = nil
	db.tlsHandshakeTable = nil
	db.lookupHostTable = nil
	db.lookupHTTPSvcTable = nil
	db.dnsRoundTripTable = nil
	db.httpRoundTripTable = nil
	db.httpRedirectTable = nil
	db.quicHandshakeTable = nil
	db.mu.Unlock()
}

// InsertIntoDial implements EventDB.InsertIntoDial.
func (db *MeasurementDB) InsertIntoDial(ev *NetworkEvent) {
	db.mu.Lock()
	db.dialTable = append(db.dialTable, ev)
	db.mu.Unlock()
}

// selectAllFromDialUnlocked returns all dial events.
func (db *MeasurementDB) selectAllFromDialUnlocked() (out []*NetworkEvent) {
	out = append(out, db.dialTable...)
	return
}

// InsertIntoReadWrite implements EventDB.InsertIntoReadWrite.
func (db *MeasurementDB) InsertIntoReadWrite(ev *NetworkEvent) {
	db.mu.Lock()
	db.readWriteTable = append(db.readWriteTable, ev)
	db.mu.Unlock()
}

// selectAllFromReadWriteUnlocked returns all I/O events.
func (db *MeasurementDB) selectAllFromReadWriteUnlocked() (out []*NetworkEvent) {
	out = append(out, db.readWriteTable...)
	return
}

// InsertIntoClose implements EventDB.InsertIntoClose.
func (db *MeasurementDB) InsertIntoClose(ev *NetworkEvent) {
	db.mu.Lock()
	db.closeTable = append(db.closeTable, ev)
	db.mu.Unlock()
}

// selectAllFromCloseUnlocked returns all close events.
func (db *MeasurementDB) selectAllFromCloseUnlocked() (out []*NetworkEvent) {
	out = append(out, db.closeTable...)
	return
}

// InsertIntoTLSHandshake implements EventDB.InsertIntoTLSHandshake.
func (db *MeasurementDB) InsertIntoTLSHandshake(ev *QUICTLSHandshakeEvent) {
	db.mu.Lock()
	db.tlsHandshakeTable = append(db.tlsHandshakeTable, ev)
	db.mu.Unlock()
}

// selectAllFromTLSHandshakeUnlocked returns all TLS handshake events.
func (db *MeasurementDB) selectAllFromTLSHandshakeUnlocked() (out []*QUICTLSHandshakeEvent) {
	out = append(out, db.tlsHandshakeTable...)
	return
}

// InsertIntoLookupHost implements EventDB.InsertIntoLookupHost.
func (db *MeasurementDB) InsertIntoLookupHost(ev *DNSLookupEvent) {
	db.mu.Lock()
	db.lookupHostTable = append(db.lookupHostTable, ev)
	db.mu.Unlock()
}

// selectAllFromLookupHostUnlocked returns all the lookup host events.
func (db *MeasurementDB) selectAllFromLookupHostUnlocked() (out []*DNSLookupEvent) {
	out = append(out, db.lookupHostTable...)
	return
}

// InsertIntoHTTPSSvc implements EventDB.InsertIntoHTTPSSvc
func (db *MeasurementDB) InsertIntoLookupHTTPSSvc(ev *DNSLookupEvent) {
	db.mu.Lock()
	db.lookupHTTPSvcTable = append(db.lookupHTTPSvcTable, ev)
	db.mu.Unlock()
}

// selectAllFromLookupHTTPSSvcUnlocked returns all HTTPSSvc lookup events.
func (db *MeasurementDB) selectAllFromLookupHTTPSSvcUnlocked() (out []*DNSLookupEvent) {
	out = append(out, db.lookupHTTPSvcTable...)
	return
}

// InsertIntoDNSRoundTrip implements EventDB.InsertIntoDNSRoundTrip.
func (db *MeasurementDB) InsertIntoDNSRoundTrip(ev *DNSRoundTripEvent) {
	db.mu.Lock()
	db.dnsRoundTripTable = append(db.dnsRoundTripTable, ev)
	db.mu.Unlock()
}

// selectAllFromDNSRoundTripUnlocked returns all DNS round trip events.
func (db *MeasurementDB) selectAllFromDNSRoundTripUnlocked() (out []*DNSRoundTripEvent) {
	out = append(out, db.dnsRoundTripTable...)
	return
}

// InsertIntoHTTPRoundTrip implements EventDB.InsertIntoHTTPRoundTrip.
func (db *MeasurementDB) InsertIntoHTTPRoundTrip(ev *HTTPRoundTripEvent) {
	db.mu.Lock()
	db.httpRoundTripTable = append(db.httpRoundTripTable, ev)
	db.mu.Unlock()
}

// selectAllFromHTTPRoundTripUnlocked returns all HTTP round trip events.
func (db *MeasurementDB) selectAllFromHTTPRoundTripUnlocked() (out []*HTTPRoundTripEvent) {
	out = append(out, db.httpRoundTripTable...)
	return
}

// InsertIntoHTTPRedirect implements EventDB.InsertIntoHTTPRedirect.
func (db *MeasurementDB) InsertIntoHTTPRedirect(ev *HTTPRedirectEvent) {
	db.mu.Lock()
	db.httpRedirectTable = append(db.httpRedirectTable, ev)
	db.mu.Unlock()
}

// selectAllFromHTTPRedirectUnlocked returns all HTTP redirections.
func (db *MeasurementDB) selectAllFromHTTPRedirectUnlocked() (out []*HTTPRedirectEvent) {
	out = append(out, db.httpRedirectTable...)
	return
}

// InsertIntoQUICHandshake implements EventDB.InsertIntoQUICHandshake.
func (db *MeasurementDB) InsertIntoQUICHandshake(ev *QUICTLSHandshakeEvent) {
	db.mu.Lock()
	db.quicHandshakeTable = append(db.quicHandshakeTable, ev)
	db.mu.Unlock()
}

// selectAllFromQUICHandshakeUnlocked returns all QUIC handshake events.
func (db *MeasurementDB) selectAllFromQUICHandshakeUnlocked() (out []*QUICTLSHandshakeEvent) {
	out = append(out, db.quicHandshakeTable...)
	return
}

// AsMeasurement converts the current state of the database into
// a finalized Measurement structure. The original events will remain
// into the database. To start a new measurement cycle, just create
// a new MeasurementDB instance and use that.
func (db *MeasurementDB) AsMeasurement() *Measurement {
	db.mu.Lock()
	meas := &Measurement{
		Connect:        db.selectAllFromDialUnlocked(),
		ReadWrite:      db.selectAllFromReadWriteUnlocked(),
		Close:          db.selectAllFromCloseUnlocked(),
		TLSHandshake:   db.selectAllFromTLSHandshakeUnlocked(),
		QUICHandshake:  db.selectAllFromQUICHandshakeUnlocked(),
		LookupHost:     db.selectAllFromLookupHostUnlocked(),
		LookupHTTPSSvc: db.selectAllFromLookupHTTPSSvcUnlocked(),
		DNSRoundTrip:   db.selectAllFromDNSRoundTripUnlocked(),
		HTTPRoundTrip:  db.selectAllFromHTTPRoundTripUnlocked(),
		HTTPRedirect:   db.selectAllFromHTTPRedirectUnlocked(),
	}
	db.mu.Unlock()
	return meas
}