// Copyright 2020 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "src/tint/reader/spirv/usage.h"

#include <sstream>

namespace tint::reader::spirv {

Usage::Usage() {}
Usage::Usage(const Usage& other) = default;
Usage::~Usage() = default;

std::ostream& Usage::operator<<(std::ostream& out) const {
  out << "Usage(";
  if (IsSampler()) {
    out << "Sampler(";
    if (is_comparison_sampler_) {
      out << " comparison";
    }
    out << " )";
  }
  if (IsTexture()) {
    out << "Texture(";
    if (is_sampled_) {
      out << " is_sampled";
    }
    if (is_multisampled_) {
      out << " ms";
    }
    if (is_depth_) {
      out << " depth";
    }
    if (is_storage_read_) {
      out << " read";
    }
    if (is_storage_write_) {
      out << " write";
    }
    out << " )";
  }
  out << ")";
  return out;
}

bool Usage::IsValid() const {
  // Check sampler state internal consistency.
  if (is_comparison_sampler_ && !is_sampler_) {
    return false;
  }

  // Check texture state.
  // |is_texture_| is implied by any of the later texture-based properties.
  if ((IsStorageTexture() || is_sampled_ || is_multisampled_ || is_depth_) &&
      !is_texture_) {
    return false;
  }
  if (is_texture_) {
    // Multisampled implies sampled.
    if (is_multisampled_) {
      if (!is_sampled_) {
        return false;
      }
    }
    // Depth implies sampled.
    if (is_depth_) {
      if (!is_sampled_) {
        return false;
      }
    }

    // Sampled can't be storage.
    if (is_sampled_) {
      if (IsStorageTexture()) {
        return false;
      }
    }

    // Storage can't be sampled.
    if (IsStorageTexture()) {
      if (is_sampled_) {
        return false;
      }
    }
    // Storage texture can't also be a sampler.
    if (IsStorageTexture()) {
      if (is_sampler_) {
        return false;
      }
    }

    // Can't be both read and write.  This is a restriction in WebGPU.
    if (is_storage_read_ && is_storage_write_) {
      return false;
    }
  }
  return true;
}

bool Usage::IsComplete() const {
  if (!IsValid()) {
    return false;
  }
  if (IsSampler()) {
    return true;
  }
  if (IsTexture()) {
    return is_sampled_ || IsStorageTexture();
  }
  return false;
}

bool Usage::operator==(const Usage& other) const {
  return is_sampler_ == other.is_sampler_ &&
         is_comparison_sampler_ == other.is_comparison_sampler_ &&
         is_texture_ == other.is_texture_ && is_sampled_ == other.is_sampled_ &&
         is_multisampled_ == other.is_multisampled_ &&
         is_depth_ == other.is_depth_ &&
         is_storage_read_ == other.is_storage_read_ &&
         is_storage_write_ == other.is_storage_write_;
}

void Usage::Add(const Usage& other) {
  is_sampler_ = is_sampler_ || other.is_sampler_;
  is_comparison_sampler_ =
      is_comparison_sampler_ || other.is_comparison_sampler_;
  is_texture_ = is_texture_ || other.is_texture_;
  is_sampled_ = is_sampled_ || other.is_sampled_;
  is_multisampled_ = is_multisampled_ || other.is_multisampled_;
  is_depth_ = is_depth_ || other.is_depth_;
  is_storage_read_ = is_storage_read_ || other.is_storage_read_;
  is_storage_write_ = is_storage_write_ || other.is_storage_write_;
}

void Usage::AddSampler() {
  is_sampler_ = true;
}

void Usage::AddComparisonSampler() {
  AddSampler();
  is_comparison_sampler_ = true;
}

void Usage::AddTexture() {
  is_texture_ = true;
}

void Usage::AddStorageReadTexture() {
  AddTexture();
  is_storage_read_ = true;
}

void Usage::AddStorageWriteTexture() {
  AddTexture();
  is_storage_write_ = true;
}

void Usage::AddSampledTexture() {
  AddTexture();
  is_sampled_ = true;
}

void Usage::AddMultisampledTexture() {
  AddSampledTexture();
  is_multisampled_ = true;
}

void Usage::AddDepthTexture() {
  AddSampledTexture();
  is_depth_ = true;
}

std::string Usage::to_str() const {
  std::ostringstream ss;
  ss << *this;
  return ss.str();
}

}  // namespace tint::reader::spirv
