/* 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/. */

#ifndef mozilla_dom_SerialPort_h
#define mozilla_dom_SerialPort_h

#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/dom/SerialPortChild.h"
#include "mozilla/dom/SerialPortInfo.h"
#include "mozilla/dom/WorkerRef.h"

namespace mozilla::ipc {
class DataPipeReceiver;
class DataPipeSender;
}  // namespace mozilla::ipc

namespace mozilla::dom {

class Promise;
class ReadableStream;
class Serial;
class SerialPortReadAlgorithms;
class SerialPortWriteAlgorithms;
class WritableStream;
struct SerialOptions;
struct SerialOutputSignals;
struct SerialPortInfo;

class SerialPort final : public DOMEventTargetHelper {
 public:
  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(SerialPort, DOMEventTargetHelper)

  SerialPort(const IPCSerialPortInfo& aInfo, Serial* aSerial);

  void EventListenerAdded(nsAtom* aType) override;
  void EventListenerRemoved(nsAtom* aType) override;

  JSObject* WrapObject(JSContext* aCx,
                       JS::Handle<JSObject*> aGivenProto) override;

  void GetEventTargetParent(EventChainPreVisitor& aVisitor) override;

  already_AddRefed<Promise> Open(const SerialOptions& aOptions,
                                 ErrorResult& aRv);

  already_AddRefed<Promise> SetSignals(const SerialOutputSignals& aSignals,
                                       ErrorResult& aRv);

  already_AddRefed<Promise> GetSignals(ErrorResult& aRv);

  MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Close(ErrorResult& aRv);

  MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> Forget(ErrorResult& aRv);

  // Mark this port as forgotten without the full Forget() flow (no IPC,
  // no manager callback). Used when another context (e.g. a worker) has
  // already performed the forget and the manager has been updated.
  MOZ_CAN_RUN_SCRIPT void MarkForgotten();

  // https://wicg.github.io/serial/#dom-serialport-connected
  // Returns this.[[connected]]: whether the device is physically connected.
  bool Connected() const { return mPhysicallyPresent; }

  enum class State : uint8_t {
    Closed,
    Opening,
    Opened,
    Closing,
    Forgetting,
    Forgotten,
  };

  bool IsForgotten() const { return mState == State::Forgotten; }

  // Returns whether the device is physically connected to the system.
  // This does not indicate whether the port is open or being used.
  bool PhysicallyPresent() const { return mPhysicallyPresent; }

  // Silently marks this port as physically present without dispatching a
  // connect event. Used when returning a port from requestPort(), since the
  // port is guaranteed to be connected (GetAvailablePorts only lists connected
  // devices) but the async RecvPortConnected notification may not have arrived.
  void MarkPhysicallyPresent() { mPhysicallyPresent = true; }

  void GetInfo(SerialPortInfo& aRetVal, ErrorResult& aRv);

  MOZ_CAN_RUN_SCRIPT ReadableStream* GetReadable();

  // https://wicg.github.io/serial/#dom-serialport-writable
  MOZ_CAN_RUN_SCRIPT WritableStream* GetWritable();

  IMPL_EVENT_HANDLER(connect)
  IMPL_EVENT_HANDLER(disconnect)

  const nsString& Id() const { return mInfo.id(); }

  const IPCSerialPortInfo& GetPortInfo() const { return mInfo; }

  MOZ_CAN_RUN_SCRIPT void NotifyConnected();
  MOZ_CAN_RUN_SCRIPT void NotifyDisconnected();

  // Called when the PSerialPort child actor is destroyed externally (e.g.
  // by the parent closing the channel due to permission revocation).
  MOZ_CAN_RUN_SCRIPT void OnActorDestroyed();

  MOZ_CAN_RUN_SCRIPT_BOUNDARY void Shutdown();

  void DisconnectFromOwner() override;

  RefPtr<SerialPortChild> GetChild() const { return mChild; }

  void SetChild(SerialPortChild* aChild) { mChild = aChild; }

 private:
  ~SerialPort() override;

  enum class StreamCloseMode : uint8_t {
    // Cancel the readable and abort the writable, running their underlying
    // source/sink algorithms.
    Graceful,
    // Error both streams without running author-observable algorithms or
    // dispatching events.
    Forced,
  };

  MOZ_CAN_RUN_SCRIPT ReadableStream* CreateReadableStream();
  MOZ_CAN_RUN_SCRIPT WritableStream* CreateWritableStream();
  void CloseAfterStreamsClosed();
  void SettleClosePromise(nsresult aResult);
  MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CloseStreams(
      StreamCloseMode aMode);
  void NotifySharingStateChanged(bool aConnected);
  void UpdateWorkerRef();

  RefPtr<Serial> mSerial;
  IPCSerialPortInfo mInfo;
  State mState = State::Closed;
  // Whether the hardware device is physically connected to the system
  bool mPhysicallyPresent = true;
  bool mHasShutdown = false;
  uint32_t mBufferSize = 0;
  uint32_t mPipeCapacity = 0;

  RefPtr<ReadableStream> mReadable;
  RefPtr<WritableStream> mWritable;
  RefPtr<SerialPortChild> mChild;

  RefPtr<Promise> mOpenPromise;
  RefPtr<Promise> mClosePromise;

  // Prevents the worker from shutting down while this port is open or
  // has connect/disconnect event listeners. Managed by UpdateWorkerRef().
  RefPtr<StrongWorkerRef> mWorkerRef;
};

}  // namespace mozilla::dom

#endif  // mozilla_dom_SerialPort_h
