/* 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 WEBGL_PROGRAM_H_
#define WEBGL_PROGRAM_H_

#include <bitset>
#include <map>
#include <memory>
#include <vector>

#include "CacheInvalidator.h"
#include "WebGLObjectModel.h"
#include "WebGLTypes.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Vector.h"
#include "mozilla/WeakPtr.h"

namespace mozilla {
class ErrorResult;
class WebGLContext;
class WebGLProgram;
class WebGLShader;

namespace dom {
template <typename>
struct Nullable;
class OwningUnsignedLongOrUint32ArrayOrBoolean;
template <typename>
class Sequence;
}  // namespace dom

namespace webgl {

enum class TextureBaseType : uint8_t;

struct UniformBlockInfo final {
  const ActiveUniformBlockInfo& info;
  const IndexedBufferBinding* binding = nullptr;
};

struct FragOutputInfo final {
  const uint8_t loc;
  const std::string userName;
  const std::string mappedName;
  const TextureBaseType baseType;
};

struct CachedDrawFetchLimits final {
  uint64_t maxVerts = UINT64_MAX;
  uint64_t maxInstances = UINT64_MAX;
  std::vector<BufferAndIndex> usedBuffers;

  CachedDrawFetchLimits() = default;
  explicit CachedDrawFetchLimits(const CachedDrawFetchLimits&) = delete;
  CachedDrawFetchLimits(CachedDrawFetchLimits&&) = default;

  CachedDrawFetchLimits& operator=(CachedDrawFetchLimits&&) = default;
};

// -

void UniformAs1fv(gl::GLContext& gl, GLint location, GLsizei count,
                  bool transpose, const void* any);

struct ActiveUniformValidationInfo final {
  const ActiveUniformInfo& info;
  bool isArray = false;
  uint8_t channelsPerElem = 0;
  decltype(&UniformAs1fv) pfn = nullptr;

  static ActiveUniformValidationInfo Make(const ActiveUniformInfo&);
};

struct SamplerUniformInfo final {
  const nsTArray<RefPtr<WebGLTexture>>& texListForType;
  // = const decltype(WebGLContext::mBound2DTextures)&
  const webgl::TextureBaseType texBaseType;
  const bool isShadowSampler;
  Vector<uint8_t, 8> texUnits = decltype(texUnits)();
};

struct LocationInfo final {
  const ActiveUniformValidationInfo info;
  const uint32_t indexIntoUniform;
  SamplerUniformInfo* const samplerInfo;

  auto PrettyName() const {
    auto ret = info.info.name;
    if (info.isArray) {
      ret += "[";
      ret += std::to_string(indexIntoUniform);
      ret += "]";
    }
    return ret;
  }
};

// -

struct LinkedProgramInfo final : public RefCounted<LinkedProgramInfo>,
                                 public SupportsWeakPtr,
                                 public CacheInvalidator {
  friend class mozilla::WebGLProgram;

  MOZ_DECLARE_REFCOUNTED_TYPENAME(LinkedProgramInfo)

  //////

  WebGLProgram* const prog;
  const GLenum transformFeedbackBufferMode;

  std::bitset<kMaxDrawBuffers> hasOutput = 0;
  std::unordered_map<uint8_t, const FragOutputInfo> fragOutputs;
  uint8_t zLayerCount = 1;

  mutable std::vector<size_t> componentsPerTFVert;

  bool attrib0Active = false;
  GLint webgl_gl_VertexID_Offset = -1;  // Location

  // -

  std::map<std::string, std::string> nameMap;
  webgl::LinkActiveInfo active;

  std::vector<std::unique_ptr<SamplerUniformInfo>> samplerUniforms;
  std::unordered_map<uint32_t, LocationInfo> locationMap;

  mutable std::vector<UniformBlockInfo> uniformBlocks;

  //////

 private:
  mutable CachedDrawFetchLimits mScratchFetchLimits;

 public:
  const CachedDrawFetchLimits* GetDrawFetchLimits() const;

  //////

  explicit LinkedProgramInfo(WebGLProgram* prog);
  ~LinkedProgramInfo();
};

}  // namespace webgl

class WebGLProgram final : public WebGLContextBoundObject {
  friend class WebGLTransformFeedback;
  friend struct webgl::LinkedProgramInfo;

  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(WebGLProgram, override)

 public:
  explicit WebGLProgram(WebGLContext* webgl);

  void Delete();

  // GL funcs
  void AttachShader(WebGLShader& shader);
  void BindAttribLocation(GLuint index, const std::string& name);
  void DetachShader(const WebGLShader& shader);
  void UniformBlockBinding(GLuint uniformBlockIndex,
                           GLuint uniformBlockBinding) const;

  void LinkProgram();
  bool UseProgram() const;
  bool ValidateProgram() const;

  ////////////////

  void TransformFeedbackVaryings(const std::vector<std::string>& varyings,
                                 GLenum bufferMode);

  bool IsLinked() const { return mMostRecentLinkInfo; }

  const webgl::LinkedProgramInfo* LinkInfo() const {
    return mMostRecentLinkInfo.get();
  }

  const auto& VertShader() const { return mVertShader; }
  const auto& FragShader() const { return mFragShader; }

  const auto& LinkLog() const { return mLinkLog; }

 private:
  ~WebGLProgram();

  void LinkAndUpdate();
  bool ValidateForLink();
  bool ValidateAfterTentativeLink(std::string* const out_linkLog) const;

 public:
  const GLuint mGLName;

 private:
  RefPtr<WebGLShader> mVertShader;
  RefPtr<WebGLShader> mFragShader;
  size_t mNumActiveTFOs;

  std::map<std::string, GLuint> mNextLink_BoundAttribLocs;

  std::vector<std::string> mNextLink_TransformFeedbackVaryings;
  GLenum mNextLink_TransformFeedbackBufferMode;

  std::string mLinkLog;
  RefPtr<const webgl::LinkedProgramInfo> mMostRecentLinkInfo;
};

}  // namespace mozilla

#endif  // WEBGL_PROGRAM_H_
