/*
 * Copyright 2000-2017 JetBrains s.r.o.
 *
 * 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.intellij.platform.templates;

import com.intellij.facet.ui.ValidationResult;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleType;
import com.intellij.openapi.module.ModuleTypeManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.io.StreamUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.JdomKt;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * @author Dmitry Avdeev
 *         Date: 10/1/12
 */
public class LocalArchivedTemplate extends ArchivedProjectTemplate {
  public static final String DESCRIPTION_PATH = Project.DIRECTORY_STORE_FOLDER + "/description.html";
  static final String TEMPLATE_DESCRIPTOR = Project.DIRECTORY_STORE_FOLDER + "/project-template.xml";
  static final String TEMPLATE_META_XML = "template-meta.xml";
  static final String META_TEMPLATE_DESCRIPTOR_PATH = Project.DIRECTORY_STORE_FOLDER + "/"+TEMPLATE_META_XML;
  public static final String UNENCODED_ATTRIBUTE = "unencoded";
  static final String ROOT_FILE_NAME = "root";

  private final URL myArchivePath;
  private final ModuleType myModuleType;
  @Nullable private final List<RootDescription> myModuleDescriptions;
  private boolean myEscaped = true;
  private Icon myIcon;

  public LocalArchivedTemplate(@NotNull URL archivePath,
                               @NotNull ClassLoader classLoader) {
    super(getTemplateName(archivePath), null);

    myArchivePath = archivePath;
    myModuleType = computeModuleType(this);
    String s = readEntry(TEMPLATE_DESCRIPTOR);
    if (s != null) {
      try {
        Element templateElement = JdomKt.loadElement(s);
        populateFromElement(templateElement);
        String iconPath = templateElement.getChildText("icon-path");
        if (iconPath != null) {
          myIcon = IconLoader.findIcon(iconPath, classLoader);
        }
      }
      catch (Exception e) {
        throw new RuntimeException(e);
      }
    }

    String meta = readEntry(META_TEMPLATE_DESCRIPTOR_PATH);
    if (meta != null) {
      try {
        Element templateElement = JdomKt.loadElement(meta);
        String unencoded = templateElement.getAttributeValue(UNENCODED_ATTRIBUTE);
        if (unencoded != null) {
          myEscaped = !Boolean.valueOf(unencoded);
        }

        myModuleDescriptions = RootDescription.readRoots(templateElement);
      }
      catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
    else {
      myModuleDescriptions = null;
    }
  }

  public ValidationResult validate(@NotNull String baseDirPath) {
    if (myModuleDescriptions != null && !myModuleDescriptions.isEmpty()) {
      File baseDirFile = new File(baseDirPath);
      for (RootDescription description : myModuleDescriptions) {
        File rootFile = new File(baseDirFile + "/" + description.myRelativePath);
        try {
          rootFile = rootFile.getCanonicalFile();
          if (rootFile.exists()) {
            String[] list = rootFile.list();
            if (list == null) {
              return new ValidationResult("<html>File '" + rootFile.getAbsolutePath() + "' already exists," +
                                          " so project root can't be created</html>");
            }
            if (list.length > 0) {
              return new ValidationResult("<html>Directory '" + rootFile.getAbsolutePath() + "' already exists and is not empty, " +
                                          "so project root can't be created</html>");
            }
          }
        }
        catch (IOException e) {
          throw new RuntimeException(e);
        }
      }
    }
    return null;
  }

  private static String getTemplateName(URL url) {
    String fileName = new File(url.getPath()).getName();
    return fileName.substring(0, fileName.length() - ArchivedTemplatesFactory.ZIP.length()).replace('_', ' ');
  }

  @Override
  public String getDescription() {
    return readEntry(DESCRIPTION_PATH);
  }

  @Override
  public Icon getIcon() {
    return myIcon == null ? super.getIcon() : myIcon;
  }

  public boolean isEscaped(){
    return myEscaped;
  }

  @Nullable
  String readEntry(@NotNull final String endsWith) {
    try {
      return processStream(new StreamProcessor<String>() {
        @Override
        public String consume(@NotNull ZipInputStream stream) throws IOException {
          ZipEntry entry;
          while ((entry = stream.getNextEntry()) != null) {
            if (entry.getName().endsWith(endsWith)) {
              return StreamUtil.readText(stream, CharsetToolkit.UTF8_CHARSET);
            }
          }
          return null;
        }
      });
    }
    catch (IOException ignored) {
      return null;
    }
  }

  @NotNull
  private static ModuleType computeModuleType(LocalArchivedTemplate template) {
    String iml = template.readEntry(".iml");
    if (iml == null) return ModuleType.EMPTY;
    try {
      String type = JdomKt.loadElement(iml).getAttributeValue(Module.ELEMENT_TYPE);
      return ModuleTypeManager.getInstance().findByID(type);
    }
    catch (Exception e) {
      return ModuleType.EMPTY;
    }
  }

  @Override
  protected ModuleType getModuleType() {
    return myModuleType;
  }

  @Override
  public <T> T processStream(@NotNull StreamProcessor<T> consumer) throws IOException {
    return consumeZipStream(consumer, new ZipInputStream(myArchivePath.openStream()));
  }

  public URL getArchivePath() {
    return myArchivePath;
  }

  @Override
  public void handleUnzippedDirectories(File dir, List<File> filesToRefresh) throws IOException {
    if (myModuleDescriptions == null) {
      filesToRefresh.add(dir);
      return;
    }

    for (RootDescription description : myModuleDescriptions) {
      File root = new File(dir, ROOT_FILE_NAME + description.myIndex);
      File target = new File(dir.getAbsolutePath() + "/" + description.myRelativePath);
      //noinspection ResultOfMethodCallIgnored
      target.mkdirs();
      FileUtil.copyDir(root, target);
      FileUtil.delete(root);
      filesToRefresh.add(target);
    }
  }

  static class RootDescription {
    private static final String ROOTS_ELEMENT = "roots";
    private static final String ROOT_ELEMENT = "root";
    private static final String INDEX_ATTRIBUTE = "index";
    private static final String PATH_ATTRIBUTE = "path";
    final VirtualFile myFile;
    final String myRelativePath;
    final int myIndex;

    public RootDescription(VirtualFile file, String path, int index) {
      myFile = file;
      myRelativePath = path;
      myIndex = index;
    }

    private void write(Element parent) {
      Element rootChild = new Element(ROOT_ELEMENT);
      rootChild.setAttribute(INDEX_ATTRIBUTE, String.valueOf(myIndex));
      rootChild.setAttribute(PATH_ATTRIBUTE, myRelativePath);
      parent.addContent(rootChild);
    }

    private static List<RootDescription> read(Element parent) {
      List<Element> children = parent.getChildren(ROOT_ELEMENT);
      List<RootDescription> result = new ArrayList<>(children.size());
      for (Element child : children) {
        int index = Integer.parseInt(child.getAttributeValue(INDEX_ATTRIBUTE));
        String path = child.getAttributeValue(PATH_ATTRIBUTE);
        result.add(index, new RootDescription(null, path, index));
      }
      return result;
    }

    static void writeRoots(Element element, List<RootDescription> rootDescriptions) {
      Element rootsElement = new Element(ROOTS_ELEMENT);
      for (LocalArchivedTemplate.RootDescription description : rootDescriptions) {
        description.write(rootsElement);
      }
      element.addContent(rootsElement);
    }

    @Nullable
    static List<RootDescription> readRoots(Element element) {
      Element modulesElement = element.getChild(ROOTS_ELEMENT);
      if (modulesElement != null) {
        return read(modulesElement);
      }
      else {
        return null;
      }
    }
  }
}
