/*
 * Copyright (C) 2018 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.
 */

#include "gflags/gflags.h"

#include "android-base/stringprintf.h"
#include "apk_layout_compiler.h"
#include "dex_builder.h"
#include "dex_layout_compiler.h"
#include "java_lang_builder.h"
#include "layout_validation.h"
#include "tinyxml_layout_parser.h"
#include "util.h"

#include "tinyxml2.h"

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

namespace {

using namespace tinyxml2;
using android::base::StringPrintf;
using startop::dex::ClassBuilder;
using startop::dex::DexBuilder;
using startop::dex::MethodBuilder;
using startop::dex::Prototype;
using startop::dex::TypeDescriptor;
using namespace startop::util;
using std::string;

constexpr char kStdoutFilename[]{"stdout"};

DEFINE_bool(apk, false, "Compile layouts in an APK");
DEFINE_bool(dex, false, "Generate a DEX file instead of Java");
DEFINE_int32(infd, -1, "Read input from the given file descriptor");
DEFINE_string(out, kStdoutFilename, "Where to write the generated class");
DEFINE_string(package, "", "The package name for the generated class (required)");

template <typename Visitor>
class XmlVisitorAdapter : public XMLVisitor {
 public:
  explicit XmlVisitorAdapter(Visitor* visitor) : visitor_{visitor} {}

  bool VisitEnter(const XMLDocument& /*doc*/) override {
    visitor_->VisitStartDocument();
    return true;
  }

  bool VisitExit(const XMLDocument& /*doc*/) override {
    visitor_->VisitEndDocument();
    return true;
  }

  bool VisitEnter(const XMLElement& element, const XMLAttribute* /*firstAttribute*/) override {
    visitor_->VisitStartTag(
        std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(
            element.Name()));
    return true;
  }

  bool VisitExit(const XMLElement& /*element*/) override {
    visitor_->VisitEndTag();
    return true;
  }

 private:
  Visitor* visitor_;
};

template <typename Builder>
void CompileLayout(XMLDocument* xml, Builder* builder) {
  startop::LayoutCompilerVisitor visitor{builder};
  XmlVisitorAdapter<decltype(visitor)> adapter{&visitor};
  xml->Accept(&adapter);
}

}  // end namespace

int main(int argc, char** argv) {
  constexpr size_t kProgramName = 0;
  constexpr size_t kFileNameParam = 1;
  constexpr size_t kNumRequiredArgs = 1;

  gflags::SetUsageMessage(
      "Compile XML layout files into equivalent Java language code\n"
      "\n"
      "  example usage:  viewcompiler layout.xml --package com.example.androidapp");
  gflags::ParseCommandLineFlags(&argc, &argv, /*remove_flags*/ true);

  gflags::CommandLineFlagInfo cmd = gflags::GetCommandLineFlagInfoOrDie("package");
  if (argc < kNumRequiredArgs || cmd.is_default) {
    gflags::ShowUsageWithFlags(argv[kProgramName]);
    return 1;
  }

  const bool is_stdout = FLAGS_out == kStdoutFilename;

  std::ofstream outfile;
  if (!is_stdout) {
    outfile.open(FLAGS_out);
  }

  if (FLAGS_apk) {
    const startop::CompilationTarget target =
        FLAGS_dex ? startop::CompilationTarget::kDex : startop::CompilationTarget::kJavaLanguage;
    if (FLAGS_infd >= 0) {
      startop::CompileApkLayoutsFd(
          android::base::unique_fd{FLAGS_infd}, target, is_stdout ? std::cout : outfile);
    } else {
      if (argc < 2) {
        gflags::ShowUsageWithFlags(argv[kProgramName]);
        return 1;
      }
      const char* const filename = argv[kFileNameParam];
      startop::CompileApkLayouts(filename, target, is_stdout ? std::cout : outfile);
    }
    return 0;
  }

  const char* const filename = argv[kFileNameParam];
  const string layout_name = startop::util::FindLayoutNameFromFilename(filename);

  XMLDocument xml;
  xml.LoadFile(filename);

  string message{};
  if (!startop::CanCompileLayout(xml, &message)) {
    LOG(ERROR) << "Layout not supported: " << message;
    return 1;
  }

  if (FLAGS_dex) {
    DexBuilder dex_file;
    string class_name = StringPrintf("%s.CompiledView", FLAGS_package.c_str());
    ClassBuilder compiled_view{dex_file.MakeClass(class_name)};
    MethodBuilder method{compiled_view.CreateMethod(
        layout_name,
        Prototype{TypeDescriptor::FromClassname("android.view.View"),
                  TypeDescriptor::FromClassname("android.content.Context"),
                  TypeDescriptor::Int()})};
    startop::DexViewBuilder builder{&method};
    CompileLayout(&xml, &builder);
    method.Encode();

    slicer::MemView image{dex_file.CreateImage()};

    (is_stdout ? std::cout : outfile).write(image.ptr<const char>(), image.size());
  } else {
    // Generate Java language output.
    JavaLangViewBuilder builder{FLAGS_package, layout_name, is_stdout ? std::cout : outfile};

    CompileLayout(&xml, &builder);
  }
  return 0;
}