/*
 * Copyright 2010-2012, The Android Open Source Project
 *
 * 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.
 */

#ifndef _FRAMEWORKS_COMPILE_SLANG_SLANG_RS_REFLECTION_H_  // NOLINT
#define _FRAMEWORKS_COMPILE_SLANG_SLANG_RS_REFLECTION_H_

#include <fstream>
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <vector>

#include "llvm/ADT/StringExtras.h"

#include "slang_assert.h"
#include "slang_rs_export_type.h"

namespace slang {

  class RSContext;
  class RSExportVar;
  class RSExportFunc;
  class RSExportForEach;

class RSReflection {
 private:
  const RSContext *mRSContext;

  std::string mLastError;
  std::vector<std::string> *mGeneratedFileNames;

  inline void setError(const std::string &Error) { mLastError = Error; }

  class Context {
   private:
    static const char *const ApacheLicenseNote;

    bool mVerbose;

    std::string mOutputPathBase;

    std::string mInputRSFile;

    std::string mPackageName;
    std::string mRSPackageName;
    std::string mResourceId;
    std::string mPaddingPrefix;

    std::string mClassName;

    std::string mLicenseNote;

    std::string mIndent;

    int mPaddingFieldIndex;

    int mNextExportVarSlot;
    int mNextExportFuncSlot;
    int mNextExportForEachSlot;

    // A mapping from a field in a record type to its index in the rsType
    // instance. Only used when generates TypeClass (ScriptField_*).
    typedef std::map<const RSExportRecordType::Field*, unsigned>
        FieldIndexMapTy;
    FieldIndexMapTy mFieldIndexMap;
    // Field index of current processing TypeClass.
    unsigned mFieldIndex;

    inline void clear() {
      mClassName = "";
      mIndent = "";
      mPaddingFieldIndex = 1;
      mNextExportVarSlot = 0;
      mNextExportFuncSlot = 0;
      mNextExportForEachSlot = 0;
      return;
    }

    bool openClassFile(const std::string &ClassName,
                       std::string &ErrorMsg);

   public:
    typedef enum {
      AM_Public,
      AM_Protected,
      AM_Private,
      AM_PublicSynchronized
    } AccessModifier;

    bool mUseStdout;
    mutable std::ofstream mOF;

    // Generated RS Elements for type-checking code.
    std::set<std::string> mTypesToCheck;

    // Generated FieldPackers for unsigned setters/validation.
    std::set<std::string> mFieldPackerTypes;

    bool addTypeNameForElement(const std::string &TypeName);
    bool addTypeNameForFieldPacker(const std::string &TypeName);

    static const char *AccessModifierStr(AccessModifier AM);

    Context(const std::string &OutputPathBase,
            const std::string &InputRSFile,
            const std::string &PackageName,
            const std::string &RSPackageName,
            const std::string &ResourceId,
            const std::string &PaddingPrefix,
            bool UseStdout)
        : mVerbose(true),
          mOutputPathBase(OutputPathBase),
          mInputRSFile(InputRSFile),
          mPackageName(PackageName),
          mRSPackageName(RSPackageName),
          mResourceId(ResourceId),
          mPaddingPrefix(PaddingPrefix),
          mLicenseNote(ApacheLicenseNote),
          mUseStdout(UseStdout) {
      clear();
      resetFieldIndex();
      clearFieldIndexMap();
      return;
    }

    inline std::ostream &out() const {
      return ((mUseStdout) ? std::cout : mOF);
    }
    inline std::ostream &indent() const {
      out() << mIndent;
      return out();
    }

    inline void incIndentLevel() {
      mIndent.append(4, ' ');
      return;
    }

    inline void decIndentLevel() {
      slangAssert(getIndentLevel() > 0 && "No indent");
      mIndent.erase(0, 4);
      return;
    }

    inline int getIndentLevel() { return (mIndent.length() >> 2); }

    inline int getNextExportVarSlot() { return mNextExportVarSlot++; }

    inline int getNextExportFuncSlot() { return mNextExportFuncSlot++; }
    inline int getNextExportForEachSlot() { return mNextExportForEachSlot++; }

    // Will remove later due to field name information is not necessary for
    // C-reflect-to-Java
    inline std::string createPaddingField() {
      return mPaddingPrefix + llvm::itostr(mPaddingFieldIndex++);
    }

    inline void setLicenseNote(const std::string &LicenseNote) {
      mLicenseNote = LicenseNote;
    }

    bool startClass(AccessModifier AM,
                    bool IsStatic,
                    const std::string &ClassName,
                    const char *SuperClassName,
                    std::string &ErrorMsg);
    void endClass();

    void startFunction(AccessModifier AM,
                       bool IsStatic,
                       const char *ReturnType,
                       const std::string &FunctionName,
                       int Argc, ...);

    typedef std::vector<std::pair<std::string, std::string> > ArgTy;
    void startFunction(AccessModifier AM,
                       bool IsStatic,
                       const char *ReturnType,
                       const std::string &FunctionName,
                       const ArgTy &Args);
    void endFunction();

    void startBlock(bool ShouldIndent = false);
    void endBlock();

    inline const std::string &getPackageName() const { return mPackageName; }
    inline const std::string &getRSPackageName() const {
      return mRSPackageName;
    }
    inline const std::string &getClassName() const { return mClassName; }
    inline const std::string &getResourceId() const { return mResourceId; }

    void startTypeClass(const std::string &ClassName);
    void endTypeClass();

    inline void incFieldIndex() { mFieldIndex++; }

    inline void resetFieldIndex() { mFieldIndex = 0; }

    inline void addFieldIndexMapping(const RSExportRecordType::Field *F) {
      slangAssert((mFieldIndexMap.find(F) == mFieldIndexMap.end()) &&
                  "Nested structure never occurs in C language.");
      mFieldIndexMap.insert(std::make_pair(F, mFieldIndex));
    }

    inline unsigned getFieldIndex(const RSExportRecordType::Field *F) const {
      FieldIndexMapTy::const_iterator I = mFieldIndexMap.find(F);
      slangAssert((I != mFieldIndexMap.end()) &&
                  "Requesting field is out of scope.");
      return I->second;
    }

    inline void clearFieldIndexMap() { mFieldIndexMap.clear(); }
  };

  bool genScriptClass(Context &C,
                      const std::string &ClassName,
                      std::string &ErrorMsg);
  void genScriptClassConstructor(Context &C);

  static void genInitBoolExportVariable(Context &C,
                                        const std::string &VarName,
                                        const clang::APValue &Val);
  static void genInitPrimitiveExportVariable(Context &C,
                                             const std::string &VarName,
                                             const clang::APValue &Val);
  static void genInitExportVariable(Context &C,
                                    const RSExportType *ET,
                                    const std::string &VarName,
                                    const clang::APValue &Val);
  static void genInitValue(Context &C, const clang::APValue &Val);
  void genExportVariable(Context &C, const RSExportVar *EV);
  void genPrimitiveTypeExportVariable(Context &C, const RSExportVar *EV);
  void genPointerTypeExportVariable(Context &C, const RSExportVar *EV);
  void genVectorTypeExportVariable(Context &C, const RSExportVar *EV);
  void genMatrixTypeExportVariable(Context &C, const RSExportVar *EV);
  void genConstantArrayTypeExportVariable(Context &C, const RSExportVar *EV);
  void genRecordTypeExportVariable(Context &C, const RSExportVar *EV);
  void genPrivateExportVariable(Context &C,
                                const std::string &TypeName,
                                const std::string &VarName);
  void genSetExportVariable(Context &C,
                            const std::string &TypeName,
                            const RSExportVar *EV);
  void genGetExportVariable(Context &C,
                            const std::string &TypeName,
                            const std::string &VarName);
  void genGetFieldID(Context &C,
                     const std::string &VarName);

  void genExportFunction(Context &C,
                         const RSExportFunc *EF);

  void genExportForEach(Context &C,
                        const RSExportForEach *EF);

  static void genTypeCheck(Context &C,
                           const RSExportType *ET,
                           const char *VarName);

  static void genTypeInstanceFromPointer(Context &C,
                                         const RSExportType *ET);

  static void genTypeInstance(Context &C,
                              const RSExportType *ET);

  static void genFieldPackerInstance(Context &C,
                                     const RSExportType *ET);

  bool genTypeClass(Context &C,
                    const RSExportRecordType *ERT,
                    std::string &ErrorMsg);
  void genTypeItemClass(Context &C, const RSExportRecordType *ERT);
  void genTypeClassConstructor(Context &C, const RSExportRecordType *ERT);
  void genTypeClassCopyToArray(Context &C, const RSExportRecordType *ERT);
  void genTypeClassCopyToArrayLocal(Context &C, const RSExportRecordType *ERT);
  void genTypeClassItemSetter(Context &C, const RSExportRecordType *ERT);
  void genTypeClassItemGetter(Context &C, const RSExportRecordType *ERT);
  void genTypeClassComponentSetter(Context &C, const RSExportRecordType *ERT);
  void genTypeClassComponentGetter(Context &C, const RSExportRecordType *ERT);
  void genTypeClassCopyAll(Context &C, const RSExportRecordType *ERT);
  void genTypeClassResize(Context &C);

  void genBuildElement(Context &C,
                       const char *ElementBuilderName,
                       const RSExportRecordType *ERT,
                       const char *RenderScriptVar,
                       bool IsInline);
  void genAddElementToElementBuilder(Context &C,
                                     const RSExportType *ERT,
                                     const std::string &VarName,
                                     const char *ElementBuilderName,
                                     const char *RenderScriptVar,
                                     unsigned ArraySize);
  void genAddPaddingToElementBuiler(Context &C,
                                    int PaddingSize,
                                    const char *ElementBuilderName,
                                    const char *RenderScriptVar);

  bool genCreateFieldPacker(Context &C,
                            const RSExportType *T,
                            const char *FieldPackerName);
  void genPackVarOfType(Context &C,
                        const RSExportType *T,
                        const char *VarName,
                        const char *FieldPackerName);
  void genAllocateVarOfType(Context &C,
                            const RSExportType *T,
                            const std::string &VarName);
  void genNewItemBufferIfNull(Context &C, const char *Index);
  void genNewItemBufferPackerIfNull(Context &C);

 public:
  explicit RSReflection(const RSContext *Context,
      std::vector<std::string> *GeneratedFileNames)
      : mRSContext(Context),
        mLastError(""),
        mGeneratedFileNames(GeneratedFileNames) {
    slangAssert(mGeneratedFileNames && "Must supply GeneratedFileNames");
    return;
  }

  bool reflect(const std::string &OutputPathBase,
               const std::string &OutputPackageName,
               const std::string &RSPackageName,
               const std::string &InputFileName,
               const std::string &OutputBCFileName);

  inline const char *getLastError() const {
    if (mLastError.empty())
      return NULL;
    else
      return mLastError.c_str();
  }
};  // class RSReflection

}   // namespace slang

#endif  // _FRAMEWORKS_COMPILE_SLANG_SLANG_RS_REFLECTION_H_  NOLINT