package org.jetbrains.android.compiler;

import com.android.resources.ResourceType;
import com.android.sdklib.IAndroidTarget;
import com.intellij.openapi.compiler.ValidityState;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.HashMap;
import org.jetbrains.android.dom.resources.DeclareStyleable;
import org.jetbrains.android.dom.resources.ResourceElement;
import org.jetbrains.android.dom.resources.Resources;
import org.jetbrains.android.facet.AndroidFacet;
import org.jetbrains.android.facet.AndroidRootUtil;
import org.jetbrains.android.resourceManagers.FileResourceProcessor;
import org.jetbrains.android.resourceManagers.LocalResourceManager;
import org.jetbrains.android.sdk.AndroidPlatform;
import org.jetbrains.android.util.AndroidCommonUtils;
import org.jetbrains.android.util.AndroidResourceUtil;
import org.jetbrains.android.util.ResourceEntry;
import org.jetbrains.android.util.ResourceFileData;
import org.jetbrains.annotations.NotNull;
import org.must.android.module.extension.AndroidModuleExtension;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * @author Eugene.Kudelevsky
 */
public class ResourceNamesValidityState implements ValidityState {
  private final Map<String, ResourceFileData> myResources = new HashMap<String, ResourceFileData>();

  private final String myAndroidTargetHashString;
  private final long myManifestTimestamp;

  public ResourceNamesValidityState(@NotNull Module module) {
    final AndroidFacet facet = ModuleUtilCore.getExtension(module, AndroidModuleExtension.class);
    assert facet != null;

    final AndroidPlatform platform = facet.getConfiguration().getAndroidPlatform();
    final IAndroidTarget target = platform != null ? platform.getTarget() : null;
    myAndroidTargetHashString = target != null ? target.hashString() : "";

    final VirtualFile manifestFile = AndroidRootUtil.getManifestFile(facet);
    myManifestTimestamp = manifestFile != null ? manifestFile.getTimeStamp() : -1;

    final LocalResourceManager manager = facet.getLocalResourceManager();

    for (final Pair<Resources, VirtualFile> pair : manager.getResourceElements()) {
      final Resources resources = pair.getFirst();
      final VirtualFile file = pair.getSecond();

      for (final ResourceType resType : AndroidResourceUtil.VALUE_RESOURCE_TYPES) {
        addValueResources(file, resType, AndroidResourceUtil.getValueResourcesFromElement(resType.getName(), resources), myResources, "");
      }
      addValueResources(file, ResourceType.ATTR, resources.getAttrs(), myResources, "");
      final List<DeclareStyleable> styleables = resources.getDeclareStyleables();
      addValueResources(file, ResourceType.DECLARE_STYLEABLE, styleables, myResources, "");

      for (DeclareStyleable styleable : styleables) {
        final String styleableName = styleable.getName().getValue();

        if (styleableName != null) {
          addValueResources(file, ResourceType.DECLARE_STYLEABLE, styleable.getAttrs(), myResources, styleableName);
        }
      }
    }

    manager.processFileResources(null, new FileResourceProcessor() {
      @Override
      public boolean process(@NotNull VirtualFile resFile, @NotNull String resName, @NotNull String resFolderType) {
        final ResourceType type = ResourceType.getEnum(resFolderType);
        final boolean idProvidingResource = type != null && ArrayUtil.find(AndroidCommonUtils.ID_PROVIDING_RESOURCE_TYPES, type) >= 0;

        ResourceFileData data = myResources.get(resFile.getPath());
        if (data == null) {
          data = new ResourceFileData();
          myResources.put(resFile.getPath(), data);
        }

        if (idProvidingResource) {
          data.setTimestamp(resFile.getTimeStamp());
        }
        return true;
      }
    });
  }

  private static void addValueResources(VirtualFile file,
                                        ResourceType resType,
                                        Collection<? extends ResourceElement> resourceElements,
                                        Map<String, ResourceFileData> result,
                                        String context) {
    for (ResourceElement element : resourceElements) {
      final String name = element.getName().getValue();

      if (name != null) {
        ResourceFileData data = result.get(file.getPath());
        if (data == null) {
          data = new ResourceFileData();
          result.put(file.getPath(), data);
        }
        data.addValueResource(new ResourceEntry(resType.getName(), name, context));
      }
    }
  }

  public ResourceNamesValidityState(@NotNull DataInput in) throws IOException {
    myAndroidTargetHashString = in.readUTF();
    myManifestTimestamp = in.readLong();

    final int resourcesCount = in.readInt();

    for (int i = 0; i < resourcesCount; i++) {
      final String filePath = in.readUTF();

      final int valueResourcesCount = in.readInt();
      final List<ResourceEntry> valueResources = new ArrayList<ResourceEntry>(valueResourcesCount);

      for (int j = 0; j < valueResourcesCount; j++) {
        final String resType = in.readUTF();
        final String resName = in.readUTF();
        final String resContext = in.readUTF();
        valueResources.add(new ResourceEntry(resType, resName, resContext));
      }
      final long fileTimestamp = in.readLong();
      myResources.put(filePath, new ResourceFileData(valueResources, fileTimestamp));
    }
  }

  @Override
  public boolean equalsTo(ValidityState otherState) {
    if (!(otherState instanceof ResourceNamesValidityState)) {
      return false;
    }

    final ResourceNamesValidityState other = (ResourceNamesValidityState)otherState;

    return other.myAndroidTargetHashString.equals(myAndroidTargetHashString) &&
           other.myManifestTimestamp == myManifestTimestamp &&
           other.myResources.equals(myResources);
  }

  @Override
  public void save(DataOutput out) throws IOException {
    out.writeUTF(myAndroidTargetHashString);
    out.writeLong(myManifestTimestamp);

    out.writeInt(myResources.size());

    for (Map.Entry<String, ResourceFileData> entry : myResources.entrySet()) {
      out.writeUTF(entry.getKey());

      final ResourceFileData data = entry.getValue();
      final List<ResourceEntry> valueResources = data.getValueResources();
      out.writeInt(valueResources.size());

      for (ResourceEntry resource : valueResources) {
        out.writeUTF(resource.getType());
        out.writeUTF(resource.getName());
        out.writeUTF(resource.getContext());
      }
      out.writeLong(data.getTimestamp());
    }
  }
}
