"use strict"
var __createBinding =
  (this && this.__createBinding) ||
  (Object.create
    ? function (o, m, k, k2) {
        if (k2 === undefined) k2 = k
        Object.defineProperty(o, k2, {
          enumerable: true,
          get: function () {
            return m[k]
          }
        })
      }
    : function (o, m, k, k2) {
        if (k2 === undefined) k2 = k
        o[k2] = m[k]
      })
var __setModuleDefault =
  (this && this.__setModuleDefault) ||
  (Object.create
    ? function (o, v) {
        Object.defineProperty(o, "default", { enumerable: true, value: v })
      }
    : function (o, v) {
        o["default"] = v
      })
var __importStar =
  (this && this.__importStar) ||
  function (mod) {
    if (mod && mod.__esModule) return mod
    var result = {}
    if (mod != null)
      for (var k in mod)
        if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k))
          __createBinding(result, mod, k)
    __setModuleDefault(result, mod)
    return result
  }
var _a, _b, _c, _d
Object.defineProperty(exports, "__esModule", { value: true })
exports.isEmpty =
  exports.empty =
  exports.single =
  exports.then =
  exports.then_ =
  exports.both =
  exports.both_ =
  exports.Single =
  exports.Both =
  exports.Then =
  exports.Empty =
  exports.isParSeq =
  exports._ParSeqBrand =
    void 0
const tracing_1 = __importStar(require("../../../Tracing"))
const fileName_1 =
  "(@effect-ts/system): _src/Collections/Immutable/ParSeq/primitives.ts"
const commons_1 = require("../../../Effect/commons")
const Function_1 = require("../../../Function")
const IO = __importStar(require("../../../IO"))
const St = __importStar(require("../../../Structural"))
const HS = __importStar(require("../HashSet"))
const L = __importStar(require("../List/core"))
const Tp = __importStar(require("../Tuple"))
exports._ParSeqBrand = Symbol()
function isParSeq(u) {
  return typeof u === "object" && u != null && exports._ParSeqBrand in u
}
exports.isParSeq = isParSeq
const _emptyHash = St.opt(St.randomInt())
class Empty {
  constructor() {
    this._tag = "Empty"
    this[_a] = exports._ParSeqBrand
  }
  [((_a = exports._ParSeqBrand), St.equalsSym)](that) {
    return isParSeq(that) && IO.run(this.equalsSafe(that))
  }
  get [St.hashSym]() {
    return _emptyHash
  }
  equalsSafe(that) {
    return IO.succeed(that._tag === "Empty")
  }
}
exports.Empty = Empty
class Then {
  constructor(left, right) {
    this.left = left
    this.right = right
    this._tag = "Then"
    this[_b] = exports._ParSeqBrand
  }
  [((_b = exports._ParSeqBrand), St.equalsSym)](that) {
    return isParSeq(that) && IO.run(this.equalsSafe(that))
  }
  get [St.hashSym]() {
    return hashCode(this)
  }
  equalsSafe(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    return IO.gen(function* (_) {
      return (
        (yield* _(self.eq(that))) ||
        (yield* _(symmetric(associateThen)(self, that))) ||
        (yield* _(symmetric(distributiveThen)(self, that))) ||
        (yield* _(symmetric(zero)(self, that)))
      )
    })
  }
  eq(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    if (that._tag === "Then") {
      return IO.gen(function* (_) {
        return (
          (yield* _(self.left.equalsSafe(that.left))) &&
          (yield* _(self.right.equalsSafe(that.right)))
        )
      })
    }
    return IO.succeed(false)
  }
}
exports.Then = Then
function associateThen(self, that) {
  return IO.gen(function* (_) {
    if (
      self._tag === "Then" &&
      self.left._tag === "Then" &&
      that._tag === "Then" &&
      that.right._tag === "Then"
    ) {
      const al = self.left.left
      const bl = self.left.right
      const cl = self.right
      const ar = that.left
      const br = that.right.left
      const cr = that.right.right
      return (
        (yield* _(al.equalsSafe(ar))) &&
        (yield* _(bl.equalsSafe(br))) &&
        (yield* _(cl.equalsSafe(cr)))
      )
    }
    return false
  })
}
function distributiveThen(self, that) {
  return IO.gen(function* (_) {
    if (
      self._tag === "Then" &&
      self.right._tag === "Both" &&
      that._tag === "Both" &&
      that.left._tag === "Then" &&
      that.right._tag === "Then"
    ) {
      const al = self.left
      const bl = self.right.left
      const cl = self.right.right
      const ar1 = that.left.left
      const br = that.left.right
      const ar2 = that.right.left
      const cr = that.right.right
      if (
        (yield* _(ar1.equalsSafe(ar2))) &&
        (yield* _(al.equalsSafe(ar1))) &&
        (yield* _(bl.equalsSafe(br))) &&
        (yield* _(cl.equalsSafe(cr)))
      ) {
        return true
      }
    }
    if (
      self._tag === "Then" &&
      self.left._tag === "Both" &&
      that._tag === "Both" &&
      that.left._tag === "Then" &&
      that.right._tag === "Then"
    ) {
      const al = self.left.left
      const bl = self.left.right
      const cl = self.right
      const ar = that.left.left
      const cr1 = that.left.right
      const br = that.right.left
      const cr2 = that.right.right
      if (
        (yield* _(cr1.equalsSafe(cr2))) &&
        (yield* _(al.equalsSafe(ar))) &&
        (yield* _(bl.equalsSafe(br))) &&
        (yield* _(cl.equalsSafe(cr1)))
      ) {
        return true
      }
    }
    return false
  })
}
class Both {
  constructor(left, right) {
    this.left = left
    this.right = right
    this._tag = "Both"
    this[_c] = exports._ParSeqBrand
  }
  [((_c = exports._ParSeqBrand), St.equalsSym)](that) {
    return isParSeq(that) && IO.run(this.equalsSafe(that))
  }
  get [St.hashSym]() {
    return hashCode(this)
  }
  equalsSafe(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    return IO.gen(function* (_) {
      return (
        (yield* _(self.eq(that))) ||
        (yield* _(symmetric(associativeBoth)(self, that))) ||
        (yield* _(symmetric(distributiveBoth)(self, that))) ||
        (yield* _(commutativeBoth(self, that))) ||
        (yield* _(symmetric(zero)(self, that)))
      )
    })
  }
  eq(that) {
    // eslint-disable-next-line @typescript-eslint/no-this-alias
    const self = this
    if (that._tag === "Both") {
      return IO.gen(function* (_) {
        return (
          (yield* _(self.left.equalsSafe(that.left))) &&
          (yield* _(self.right.equalsSafe(that.right)))
        )
      })
    }
    return IO.succeed(false)
  }
}
exports.Both = Both
function associativeBoth(self, that) {
  return IO.gen(function* (_) {
    if (
      self._tag === "Both" &&
      self.left._tag === "Both" &&
      that._tag === "Both" &&
      that.right._tag === "Both"
    ) {
      const al = self.left.left
      const bl = self.left.right
      const cl = self.right
      const ar = that.left
      const br = that.right.left
      const cr = that.right.right
      return (
        (yield* _(al.equalsSafe(ar))) &&
        (yield* _(bl.equalsSafe(br))) &&
        (yield* _(cl.equalsSafe(cr)))
      )
    }
    return false
  })
}
function distributiveBoth(self, that) {
  return IO.gen(function* (_) {
    if (
      self._tag === "Both" &&
      self.left._tag === "Then" &&
      self.right._tag === "Then" &&
      that._tag === "Then" &&
      that.right._tag === "Both"
    ) {
      const al1 = self.left.left
      const bl = self.left.right
      const al2 = self.right.left
      const cl = self.right.right
      const ar = that.left
      const br = that.right.left
      const cr = that.right.right
      if (
        (yield* _(al1.equalsSafe(al2))) &&
        (yield* _(al1.equalsSafe(ar))) &&
        (yield* _(bl.equalsSafe(br))) &&
        (yield* _(cl.equalsSafe(cr)))
      ) {
        return true
      }
    }
    if (
      self._tag === "Both" &&
      self.left._tag === "Then" &&
      self.right._tag === "Then" &&
      that._tag === "Then" &&
      that.left._tag === "Both"
    ) {
      const al = self.left.left
      const cl1 = self.left.right
      const bl = self.right.left
      const cl2 = self.right.right
      const ar = that.left.left
      const br = that.left.right
      const cr = that.right
      if (
        (yield* _(cl1.equalsSafe(cl2))) &&
        (yield* _(al.equalsSafe(ar))) &&
        (yield* _(bl.equalsSafe(br))) &&
        (yield* _(cl1.equalsSafe(cr)))
      ) {
        return true
      }
    }
    return false
  })
}
function commutativeBoth(self, that) {
  return IO.gen(function* (_) {
    if (that._tag === "Both") {
      return (
        (yield* _(self.left.equalsSafe(that.right))) &&
        (yield* _(self.right.equalsSafe(that.left)))
      )
    }
    return false
  })
}
class Single {
  constructor(a) {
    this.a = a
    this._tag = "Single"
    this[_d] = exports._ParSeqBrand
  }
  [((_d = exports._ParSeqBrand), St.equalsSym)](that) {
    return isParSeq(that) && IO.run(this.equalsSafe(that))
  }
  get [St.hashSym]() {
    return St.combineHash(St.hashString(this._tag), St.hash(this.a))
  }
  equalsSafe(that) {
    return IO.succeed(that._tag === "Single" && St.equals(this.a, that.a))
  }
}
exports.Single = Single
function zero(self, that) {
  if (self._tag === "Then" && self.right._tag === "Empty") {
    return self.left.equalsSafe(that)
  }
  if (self._tag === "Then" && self.left._tag === "Empty") {
    return self.right.equalsSafe(that)
  }
  if (self._tag === "Both" && self.right._tag === "Empty") {
    return self.left.equalsSafe(that)
  }
  if (self._tag === "Both" && self.left._tag === "Empty") {
    return self.right.equalsSafe(that)
  }
  return IO.succeed(false)
}
function symmetric(f) {
  return (a, b) =>
    IO.gen(function* (_) {
      return (yield* _(f(a, b))) || (yield* _(f(b, a)))
    })
}
/**
 * Combines this collection of events with that collection of events to
 * return a new collection of events that represents this collection of
 * events in parallel with that collection of events.
 */
function both_(left, right) {
  return isEmpty(left) ? right : isEmpty(right) ? left : new Both(left, right)
}
exports.both_ = both_
/**
 * Combines this collection of events with that collection of events to
 * return a new collection of events that represents this collection of
 * events in parallel with that collection of events.
 *
 * @dataFirst both_
 */
function both(right) {
  return (left) => both_(left, right)
}
exports.both = both
/**
 * Combines this collection of events with that collection of events to
 * return a new collection of events that represents this collection of
 * events followed by that collection of events.
 */
function then_(left, right) {
  return isEmpty(left) ? right : isEmpty(right) ? left : new Then(left, right)
}
exports.then_ = then_
/**
 * Combines this collection of events with that collection of events to
 * return a new collection of events that represents this collection of
 * events followed by that collection of events.
 *
 * @dataFirst then_
 */
function then(right) {
  return (left) => then_(left, right)
}
exports.then = then
/**
 * Constructs a new collection of events that contains the specified event.
 */
function single(a) {
  return new Single(a)
}
exports.single = single
/**
 * Empty collection of events
 */
exports.empty = new Empty()
function isEmptyLoop(self) {
  while (!L.isEmpty(self)) {
    const head = L.unsafeFirst(self)
    const tail = L.tail(self)
    switch (head._tag) {
      case "Empty": {
        self = tail
        break
      }
      case "Single": {
        return false
      }
      case "Both": {
        self = L.prepend_(L.prepend_(tail, head.right), head.left)
        break
      }
      case "Then": {
        self = L.prepend_(L.prepend_(tail, head.right), head.left)
        break
      }
    }
  }
  return true
}
/**
 * Checks if the ParSeq is empty
 */
function isEmpty(self) {
  return isEmptyLoop(L.of(self))
}
exports.isEmpty = isEmpty
function stepLoop(cause, stack, parallel, sequential) {
  // eslint-disable-next-line no-constant-condition
  while (1) {
    switch (cause._tag) {
      case "Empty": {
        if (L.isEmpty(stack)) {
          return Tp.tuple(parallel, sequential)
        } else {
          cause = L.unsafeFirst(stack)
          stack = L.tail(stack)
        }
        break
      }
      case "Both": {
        stack = L.prepend_(stack, cause.right)
        cause = cause.left
        break
      }
      case "Then": {
        const left = cause.left
        const right = cause.right
        switch (left._tag) {
          case "Empty": {
            cause = cause.right
            break
          }
          case "Then": {
            cause = then_(left.left, then_(left.right, right))
            break
          }
          case "Both": {
            cause = both_(then_(left.left, right), then_(left.right, right))
            break
          }
          default: {
            cause = left
            sequential = L.prepend_(sequential, right)
          }
        }
        break
      }
      default: {
        if (L.isEmpty(stack)) {
          return Tp.tuple(HS.add_(parallel, cause), sequential)
        } else {
          parallel = HS.add_(parallel, cause)
          cause = L.unsafeFirst(stack)
          stack = L.tail(stack)
          break
        }
      }
    }
  }
  throw new Error("Bug")
}
function step(self) {
  return stepLoop(self, L.empty(), HS.make(), L.empty())
}
function flattenLoop(causes, flattened) {
  // eslint-disable-next-line no-constant-condition
  while (1) {
    const [parallel, sequential] = L.reduce_(
      causes,
      (0, Function_1.tuple)(HS.make(), L.empty()),
      ([parallel, sequential], cause) => {
        const [set, seq] = step(cause).tuple
        return (0, Function_1.tuple)(
          HS.union_(parallel, set),
          L.concat_(sequential, seq)
        )
      }
    )
    const updated = HS.size(parallel) > 0 ? L.prepend_(flattened, parallel) : flattened
    if (L.isEmpty(sequential)) {
      return L.reverse(updated)
    } else {
      causes = sequential
      flattened = updated
    }
  }
  throw new Error("Bug")
}
function flatten(self) {
  return flattenLoop(L.of(self), L.empty())
}
function hashCode(self) {
  const flat = flatten(self)
  const size = L.size(flat)
  let head
  if (size === 0) {
    return _emptyHash
  } else if (size === 1 && (head = L.unsafeFirst(flat)) && HS.size(head) === 1) {
    return L.unsafeFirst(L.from(head))[St.hashSym]
  } else {
    return St.hashIterator(flat[Symbol.iterator]())
  }
}
//# sourceMappingURL=primitives.js.map
