// Copyright 2016 The Draco 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 "mesh/mesh_attribute_corner_table.h"

#include "mesh/mesh_misc_functions.h"

namespace draco {

MeshAttributeCornerTable::MeshAttributeCornerTable()
    : no_interior_seams_(true), corner_table_(nullptr) {}

bool MeshAttributeCornerTable::InitEmpty(const CornerTable *table) {
  if (table == nullptr)
    return false;
  is_edge_on_seam_.assign(table->num_corners(), false);
  is_vertex_on_seam_.assign(table->num_vertices(), false);
  corner_to_vertex_map_.assign(table->num_corners(), kInvalidVertexIndex);
  vertex_to_attribute_entry_id_map_.reserve(table->num_vertices());
  vertex_to_left_most_corner_map_.reserve(table->num_vertices());
  corner_table_ = table;
  no_interior_seams_ = true;
  return true;
}

bool MeshAttributeCornerTable::InitFromAttribute(const Mesh *mesh,
                                                 const CornerTable *table,
                                                 const PointAttribute *att) {
  if (!InitEmpty(table))
    return false;

  // Find all necessary data for encoding attributes. For now we check which of
  // the mesh vertices is part of an attribute seam, because seams require
  // special handling.
  for (int c = 0; c < corner_table_->num_corners(); ++c) {
    const FaceIndex f = corner_table_->Face(CornerIndex(c));
    if (corner_table_->IsDegenerated(f))
      continue;  // Ignore corners on degenerated faces.
    const CornerIndex opp_corner = corner_table_->Opposite(CornerIndex(c));
    if (opp_corner < 0) {
      // Boundary. Mark it as seam edge.
      is_edge_on_seam_[c] = true;
      // Mark seam vertices.
      VertexIndex v;
      v = corner_table_->Vertex(corner_table_->Next(CornerIndex(c)));
      is_vertex_on_seam_[v.value()] = true;
      v = corner_table_->Vertex(corner_table_->Previous(CornerIndex(c)));
      is_vertex_on_seam_[v.value()] = true;
      continue;
    }
    if (opp_corner < c)
      continue;  // Opposite corner was already processed.

    CornerIndex act_c(c), act_sibling_c(opp_corner);
    for (int i = 0; i < 2; ++i) {
      // Get the sibling corners. I.e., the two corners attached to the same
      // vertex but divided by the seam edge.
      act_c = corner_table_->Next(act_c);
      act_sibling_c = corner_table_->Previous(act_sibling_c);
      const PointIndex point_id = CornerToPointId(act_c.value(), mesh);
      const PointIndex sibling_point_id =
          CornerToPointId(act_sibling_c.value(), mesh);
      if (att->mapped_index(point_id) != att->mapped_index(sibling_point_id)) {
        no_interior_seams_ = false;
        is_edge_on_seam_[c] = true;
        is_edge_on_seam_[opp_corner.value()] = true;
        // Mark seam vertices.
        is_vertex_on_seam_[corner_table_
                               ->Vertex(corner_table_->Next(CornerIndex(c)))
                               .value()] = true;
        is_vertex_on_seam_[corner_table_
                               ->Vertex(corner_table_->Previous(CornerIndex(c)))
                               .value()] = true;
        is_vertex_on_seam_
            [corner_table_->Vertex(corner_table_->Next(opp_corner)).value()] =
                true;
        is_vertex_on_seam_[corner_table_
                               ->Vertex(corner_table_->Previous(opp_corner))
                               .value()] = true;
        break;
      }
    }
  }
  RecomputeVertices(mesh, att);
  return true;
}

void MeshAttributeCornerTable::AddSeamEdge(CornerIndex c) {
  is_edge_on_seam_[c.value()] = true;
  // Mark seam vertices.
  is_vertex_on_seam_[corner_table_->Vertex(corner_table_->Next(c)).value()] =
      true;
  is_vertex_on_seam_[corner_table_->Vertex(corner_table_->Previous(c))
                         .value()] = true;

  const CornerIndex opp_corner = corner_table_->Opposite(c);
  if (opp_corner >= 0) {
    no_interior_seams_ = false;
    is_edge_on_seam_[opp_corner.value()] = true;
    is_vertex_on_seam_[corner_table_->Vertex(corner_table_->Next(opp_corner))
                           .value()] = true;
    is_vertex_on_seam_
        [corner_table_->Vertex(corner_table_->Previous(opp_corner)).value()] =
            true;
  }
}

void MeshAttributeCornerTable::RecomputeVertices(const Mesh *mesh,
                                                 const PointAttribute *att) {
  if (mesh != nullptr && att != nullptr) {
    RecomputeVerticesInternal<true>(mesh, att);
  } else {
    RecomputeVerticesInternal<false>(nullptr, nullptr);
  }
}

template <bool init_vertex_to_attribute_entry_map>
void MeshAttributeCornerTable::RecomputeVerticesInternal(
    const Mesh *mesh, const PointAttribute *att) {
  int num_new_vertices = 0;
  for (VertexIndex v(0); v < corner_table_->num_vertices(); ++v) {
    const CornerIndex c = corner_table_->LeftMostCorner(v);
    if (c < 0)
      continue;  // Isolated vertex?
    AttributeValueIndex first_vert_id(num_new_vertices++);
    if (init_vertex_to_attribute_entry_map) {
      const PointIndex point_id = CornerToPointId(c.value(), mesh);
      vertex_to_attribute_entry_id_map_.push_back(att->mapped_index(point_id));
    } else {
      // Identity mapping
      vertex_to_attribute_entry_id_map_.push_back(first_vert_id);
    }
    CornerIndex first_c = c;
    CornerIndex act_c;
    // Check if the vertex is on a seam edge, if it is we need to find the first
    // attribute entry on the seam edge when traversing in the ccw direction.
    if (is_vertex_on_seam_[v.value()]) {
      // Try to swing left on the modified corner table. We need to get the
      // first corner that defines an attribute seam.
      act_c = SwingLeft(first_c);
      while (act_c >= 0) {
        first_c = act_c;
        act_c = SwingLeft(act_c);
      }
    }
    corner_to_vertex_map_[first_c.value()] = VertexIndex(first_vert_id.value());
    vertex_to_left_most_corner_map_.push_back(first_c);
    act_c = corner_table_->SwingRight(first_c);
    while (act_c >= 0 && act_c != first_c) {
      if (IsCornerOppositeToSeamEdge(corner_table_->Next(act_c))) {
        first_vert_id = AttributeValueIndex(num_new_vertices++);
        if (init_vertex_to_attribute_entry_map) {
          const PointIndex point_id = CornerToPointId(act_c.value(), mesh);
          vertex_to_attribute_entry_id_map_.push_back(
              att->mapped_index(point_id));
        } else {
          // Identity mapping.
          vertex_to_attribute_entry_id_map_.push_back(first_vert_id);
        }
        vertex_to_left_most_corner_map_.push_back(act_c);
      }
      corner_to_vertex_map_[act_c.value()] = VertexIndex(first_vert_id.value());
      act_c = corner_table_->SwingRight(act_c);
    }
  }
}

}  // namespace draco
