/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use std::rc::Rc;

use js::jsval::UndefinedValue;
use js::rust::HandleValue as SafeHandleValue;

use super::readablestream::ReaderType;
use super::types::ReadableStream;
use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible};
use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::dom::readablestreambyobreader::ReadableStreamBYOBReader;
use crate::dom::readablestreamdefaultreader::ReadableStreamDefaultReader;
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};

/// <https://streams.spec.whatwg.org/#readablestreamgenericreader>
pub(crate) trait ReadableStreamGenericReader {
    /// <https://streams.spec.whatwg.org/#readable-stream-reader-generic-initialize>
    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
    fn generic_initialize(&self, global: &GlobalScope, stream: &ReadableStream, can_gc: CanGc) {
        // Set reader.[[stream]] to stream.
        self.set_stream(Some(stream));

        // Set stream.[[reader]] to reader.
        let reader_type = if let Some(default_reader) = self.as_default_reader() {
            ReaderType::Default(MutNullableDom::new(Some(default_reader)))
        } else if let Some(byob_reader) = self.as_byob_reader() {
            ReaderType::BYOB(MutNullableDom::new(Some(byob_reader)))
        } else {
            unreachable!("Reader must be either Default or BYOB.");
        };
        stream.set_reader(Some(reader_type));

        if stream.is_readable() {
            // If stream.[[state]] is "readable
            // Set reader.[[closedPromise]] to a new promise.
            self.set_closed_promise(Promise::new(global, can_gc));
        } else if stream.is_closed() {
            // Otherwise, if stream.[[state]] is "closed",
            // Set reader.[[closedPromise]] to a promise resolved with undefined.
            let cx = GlobalScope::get_cx();
            self.set_closed_promise(Promise::new_resolved(global, cx, (), can_gc));
        } else {
            // Assert: stream.[[state]] is "errored"
            assert!(stream.is_errored());

            // Set reader.[[closedPromise]] to a promise rejected with stream.[[storedError]].
            let cx = GlobalScope::get_cx();
            rooted!(in(*cx) let mut error = UndefinedValue());
            stream.get_stored_error(error.handle_mut());
            self.set_closed_promise(Promise::new_rejected(global, cx, error.handle(), can_gc));

            // Set reader.[[closedPromise]].[[PromiseIsHandled]] to true
            self.get_closed_promise().set_promise_is_handled();
        }
    }

    /// <https://streams.spec.whatwg.org/#readable-stream-reader-generic-cancel>
    fn reader_generic_cancel(
        &self,
        cx: SafeJSContext,
        global: &GlobalScope,
        reason: SafeHandleValue,
        can_gc: CanGc,
    ) -> Rc<Promise> {
        // Let stream be reader.[[stream]].
        let stream = self.get_stream();

        // Assert: stream is not undefined.
        let stream =
            stream.expect("Reader should have a stream when generic cancel is called into.");

        // Return ! ReadableStreamCancel(stream, reason).
        stream.cancel(cx, global, reason, can_gc)
    }

    /// <https://streams.spec.whatwg.org/#readable-stream-reader-generic-release>
    fn generic_release(&self, can_gc: CanGc) -> Fallible<()> {
        // Let stream be reader.[[stream]].

        // Assert: stream is not undefined.
        assert!(self.get_stream().is_some());

        if let Some(stream) = self.get_stream() {
            // Assert: stream.[[reader]] is reader.
            if self.as_default_reader().is_some() {
                assert!(stream.has_default_reader());
            } else {
                assert!(stream.has_byob_reader());
            }

            if stream.is_readable() {
                // If stream.[[state]] is "readable", reject reader.[[closedPromise]] with a TypeError exception.
                self.get_closed_promise().reject_error(
                    Error::Type("stream state is not readable".to_owned()),
                    can_gc,
                );
            } else {
                // Otherwise, set reader.[[closedPromise]] to a promise rejected with a TypeError exception.
                let cx = GlobalScope::get_cx();
                rooted!(in(*cx) let mut error = UndefinedValue());
                Error::Type("Cannot release lock due to stream state.".to_owned()).to_jsval(
                    cx,
                    &stream.global(),
                    error.handle_mut(),
                    can_gc,
                );

                self.set_closed_promise(Promise::new_rejected(
                    &stream.global(),
                    cx,
                    error.handle(),
                    can_gc,
                ));
            }
            // Set reader.[[closedPromise]].[[PromiseIsHandled]] to true.
            self.get_closed_promise().set_promise_is_handled();

            // Perform ! stream.[[controller]].[[ReleaseSteps]]().
            stream.perform_release_steps()?;

            // Set stream.[[reader]] to undefined.
            stream.set_reader(None);
            // Set reader.[[stream]] to undefined.
            self.set_stream(None);
        }
        Ok(())
    }

    /// <https://streams.spec.whatwg.org/#generic-reader-closed>
    fn closed(&self) -> Rc<Promise> {
        self.get_closed_promise()
    }

    // <https://streams.spec.whatwg.org/#generic-reader-cancel>
    fn generic_cancel(
        &self,
        cx: SafeJSContext,
        global: &GlobalScope,
        reason: SafeHandleValue,
        can_gc: CanGc,
    ) -> Rc<Promise> {
        if self.get_stream().is_none() {
            // If this.[[stream]] is undefined,
            // return a promise rejected with a TypeError exception.
            let promise = Promise::new(global, can_gc);
            promise.reject_error(Error::Type("stream is undefined".to_owned()), can_gc);
            promise
        } else {
            // Return ! ReadableStreamReaderGenericCancel(this, reason).
            self.reader_generic_cancel(cx, global, reason, can_gc)
        }
    }

    fn set_stream(&self, stream: Option<&ReadableStream>);

    fn get_stream(&self) -> Option<DomRoot<ReadableStream>>;

    fn set_closed_promise(&self, promise: Rc<Promise>);

    fn get_closed_promise(&self) -> Rc<Promise>;

    fn as_default_reader(&self) -> Option<&ReadableStreamDefaultReader> {
        None
    }

    fn as_byob_reader(&self) -> Option<&ReadableStreamBYOBReader> {
        None
    }
}