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

package com.google.gerrit.httpd.template;

import static com.google.gerrit.common.FileUtil.lastModified;

import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.httpd.HtmlDomUtil;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePaths;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import java.nio.file.Path;
import org.eclipse.jgit.lib.Config;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

@Singleton
public class SiteHeaderFooter {
  private static final FluentLogger logger = FluentLogger.forEnclosingClass();

  private final boolean refreshHeaderFooter;
  private final SitePaths sitePaths;
  private volatile Template template;

  @Inject
  SiteHeaderFooter(@GerritServerConfig Config cfg, SitePaths sitePaths) {
    this.refreshHeaderFooter = cfg.getBoolean("site", "refreshHeaderFooter", true);
    this.sitePaths = sitePaths;

    try {
      Template t = new Template(sitePaths);
      t.load();
      template = t;
    } catch (IOException e) {
      logger.atWarning().withCause(e).log("Cannot load site header or footer");
    }
  }

  public Document parse(Class<?> clazz, String name) throws IOException {
    Template t = template;
    if (refreshHeaderFooter && t.isStale()) {
      t = new Template(sitePaths);
      try {
        t.load();
        template = t;
      } catch (IOException e) {
        logger.atWarning().withCause(e).log("Cannot refresh site header or footer");
        t = template;
      }
    }

    Document doc = HtmlDomUtil.parseFile(clazz, name);
    injectCss(doc, "gerrit_sitecss", t.css);
    injectXml(doc, "gerrit_header", t.header);
    injectXml(doc, "gerrit_footer", t.footer);
    return doc;
  }

  private void injectCss(Document doc, String id, String content) {
    Element e = HtmlDomUtil.find(doc, id);
    if (e != null) {
      if (!Strings.isNullOrEmpty(content)) {
        while (e.getFirstChild() != null) {
          e.removeChild(e.getFirstChild());
        }
        e.removeAttribute("id");
        e.appendChild(doc.createCDATASection("\n" + content + "\n"));
      } else {
        e.getParentNode().removeChild(e);
      }
    }
  }

  private void injectXml(Document doc, String id, Element d) {
    Element e = HtmlDomUtil.find(doc, id);
    if (e != null) {
      if (d != null) {
        while (e.getFirstChild() != null) {
          e.removeChild(e.getFirstChild());
        }
        e.appendChild(doc.importNode(d, true));
      } else {
        e.getParentNode().removeChild(e);
      }
    }
  }

  private static class Template {
    private final FileInfo cssFile;
    private final FileInfo headerFile;
    private final FileInfo footerFile;

    String css;
    Element header;
    Element footer;

    Template(SitePaths site) {
      cssFile = new FileInfo(site.site_css);
      headerFile = new FileInfo(site.site_header);
      footerFile = new FileInfo(site.site_footer);
    }

    void load() throws IOException {
      css = HtmlDomUtil.readFile(cssFile.path.getParent(), cssFile.path.getFileName().toString());
      header = readXml(headerFile);
      footer = readXml(footerFile);
    }

    boolean isStale() {
      return cssFile.isStale() || headerFile.isStale() || footerFile.isStale();
    }

    private static Element readXml(FileInfo src) throws IOException {
      Document d = HtmlDomUtil.parseFile(src.path);
      return d != null ? d.getDocumentElement() : null;
    }
  }

  private static class FileInfo {
    final Path path;
    final long time;

    FileInfo(Path p) {
      path = p;
      time = lastModified(p);
    }

    boolean isStale() {
      return time != lastModified(path);
    }
  }
}
