//===-- include/flang/Parser/tools.h ----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef FORTRAN_PARSER_TOOLS_H_
#define FORTRAN_PARSER_TOOLS_H_

#include "parse-tree.h"

namespace Fortran::parser {

// GetLastName() isolates and returns a reference to the rightmost Name
// in a variable (i.e., the Name whose symbol's type determines the type
// of the variable or expression).
const Name &GetLastName(const Name &);
const Name &GetLastName(const StructureComponent &);
const Name &GetLastName(const DataRef &);
const Name &GetLastName(const Substring &);
const Name &GetLastName(const Designator &);
const Name &GetLastName(const ProcComponentRef &);
const Name &GetLastName(const ProcedureDesignator &);
const Name &GetLastName(const Call &);
const Name &GetLastName(const FunctionReference &);
const Name &GetLastName(const Variable &);
const Name &GetLastName(const AllocateObject &);

// GetFirstName() isolates and returns a reference to the leftmost Name
// in a variable or entity declaration.
const Name &GetFirstName(const Name &);
const Name &GetFirstName(const StructureComponent &);
const Name &GetFirstName(const DataRef &);
const Name &GetFirstName(const Substring &);
const Name &GetFirstName(const Designator &);
const Name &GetFirstName(const ProcComponentRef &);
const Name &GetFirstName(const ProcedureDesignator &);
const Name &GetFirstName(const Call &);
const Name &GetFirstName(const FunctionReference &);
const Name &GetFirstName(const Variable &);
const Name &GetFirstName(const EntityDecl &);

// When a parse tree node is an instance of a specific type wrapped in
// layers of packaging, return a pointer to that object.
// Implemented with mutually recursive template functions that are
// wrapped in a struct to avoid prototypes.
struct UnwrapperHelper {

  template <typename A, typename B> static const A *Unwrap(B *p) {
    if (p) {
      return Unwrap<A>(*p);
    } else {
      return nullptr;
    }
  }

  template <typename A, typename B, bool COPY>
  static const A *Unwrap(const common::Indirection<B, COPY> &x) {
    return Unwrap<A>(x.value());
  }

  template <typename A, typename... Bs>
  static const A *Unwrap(const std::variant<Bs...> &x) {
    return common::visit([](const auto &y) { return Unwrap<A>(y); }, x);
  }

  template <typename A, std::size_t J = 0, typename... Bs>
  static const A *Unwrap(const std::tuple<Bs...> &x) {
    if constexpr (J < sizeof...(Bs)) {
      if (auto result{Unwrap<A>(std::get<J>(x))}) {
        return result;
      }
      return Unwrap<A, (J + 1)>(x);
    } else {
      return nullptr;
    }
  }

  template <typename A, typename B>
  static const A *Unwrap(const std::optional<B> &o) {
    if (o) {
      return Unwrap<A>(*o);
    } else {
      return nullptr;
    }
  }

  template <typename A, typename B>
  static const A *Unwrap(const UnlabeledStatement<B> &x) {
    return Unwrap<A>(x.statement);
  }
  template <typename A, typename B>
  static const A *Unwrap(const Statement<B> &x) {
    return Unwrap<A>(x.statement);
  }

  template <typename A, typename B> static const A *Unwrap(B &x) {
    if constexpr (std::is_same_v<std::decay_t<A>, std::decay_t<B>>) {
      return &x;
    } else if constexpr (ConstraintTrait<B>) {
      return Unwrap<A>(x.thing);
    } else if constexpr (WrapperTrait<B>) {
      return Unwrap<A>(x.v);
    } else if constexpr (UnionTrait<B>) {
      return Unwrap<A>(x.u);
    } else {
      return nullptr;
    }
  }
};

template <typename A, typename B> const A *Unwrap(const B &x) {
  return UnwrapperHelper::Unwrap<A>(x);
}
template <typename A, typename B> A *Unwrap(B &x) {
  return const_cast<A *>(Unwrap<A, B>(const_cast<const B &>(x)));
}

// Get the CoindexedNamedObject if the entity is a coindexed object.
const CoindexedNamedObject *GetCoindexedNamedObject(const AllocateObject &);
const CoindexedNamedObject *GetCoindexedNamedObject(const DataRef &);
const CoindexedNamedObject *GetCoindexedNamedObject(const Designator &);
const CoindexedNamedObject *GetCoindexedNamedObject(const Variable &);

// Detects parse tree nodes with "source" members.
template <typename A, typename = int> struct HasSource : std::false_type {};
template <typename A>
struct HasSource<A, decltype(static_cast<void>(A::source), 0)>
    : std::true_type {};

// Detects parse tree nodes with "typedExpr" members.
template <typename A, typename = int> struct HasTypedExpr : std::false_type {};
template <typename A>
struct HasTypedExpr<A, decltype(static_cast<void>(A::typedExpr), 0)>
    : std::true_type {};

// GetSource()

template <bool GET_FIRST> struct GetSourceHelper {

  using Result = std::optional<CharBlock>;

  template <typename A> static Result GetSource(A *p) {
    if (p) {
      return GetSource(*p);
    } else {
      return std::nullopt;
    }
  }
  template <typename A>
  static Result GetSource(const common::Indirection<A> &x) {
    return GetSource(x.value());
  }

  template <typename A, bool COPY>
  static Result GetSource(const common::Indirection<A, COPY> &x) {
    return GetSource(x.value());
  }

  template <typename... As>
  static Result GetSource(const std::variant<As...> &x) {
    return common::visit([](const auto &y) { return GetSource(y); }, x);
  }

  template <std::size_t J = 0, typename... As>
  static Result GetSource(const std::tuple<As...> &x) {
    if constexpr (J < sizeof...(As)) {
      constexpr std::size_t index{GET_FIRST ? J : sizeof...(As) - J - 1};
      if (auto result{GetSource(std::get<index>(x))}) {
        return result;
      }
      return GetSource<(J + 1)>(x);
    } else {
      return {};
    }
  }

  template <typename A> static Result GetSource(const std::optional<A> &o) {
    if (o) {
      return GetSource(*o);
    } else {
      return {};
    }
  }

  template <typename A> static Result GetSource(const std::list<A> &x) {
    if constexpr (GET_FIRST) {
      for (const A &y : x) {
        if (auto result{GetSource(y)}) {
          return result;
        }
      }
    } else {
      for (auto iter{x.rbegin()}; iter != x.rend(); ++iter) {
        if (auto result{GetSource(*iter)}) {
          return result;
        }
      }
    }
    return {};
  }

  template <typename A> static Result GetSource(const std::vector<A> &x) {
    if constexpr (GET_FIRST) {
      for (const A &y : x) {
        if (auto result{GetSource(y)}) {
          return result;
        }
      }
    } else {
      for (auto iter{x.rbegin()}; iter != x.rend(); ++iter) {
        if (auto result{GetSource(*iter)}) {
          return result;
        }
      }
    }
    return {};
  }

  template <typename A> static Result GetSource(A &x) {
    if constexpr (HasSource<A>::value) {
      return x.source;
    } else if constexpr (ConstraintTrait<A>) {
      return GetSource(x.thing);
    } else if constexpr (WrapperTrait<A>) {
      return GetSource(x.v);
    } else if constexpr (UnionTrait<A>) {
      return GetSource(x.u);
    } else if constexpr (TupleTrait<A>) {
      return GetSource(x.t);
    } else {
      return {};
    }
  }
};

template <typename A> std::optional<CharBlock> GetSource(const A &x) {
  return GetSourceHelper<true>::GetSource(x);
}
template <typename A> std::optional<CharBlock> GetSource(A &x) {
  return GetSourceHelper<true>::GetSource(const_cast<const A &>(x));
}

template <typename A> std::optional<CharBlock> GetLastSource(const A &x) {
  return GetSourceHelper<false>::GetSource(x);
}
template <typename A> std::optional<CharBlock> GetLastSource(A &x) {
  return GetSourceHelper<false>::GetSource(const_cast<const A &>(x));
}

} // namespace Fortran::parser
#endif // FORTRAN_PARSER_TOOLS_H_
