/*
 * Copyright 2003-2016 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 jetbrains.mps.ide.ui.dialogs.properties.tables.models;

import jetbrains.mps.ide.ui.dialogs.properties.tables.items.DependenciesTableItem;
import jetbrains.mps.ide.ui.dialogs.properties.tables.items.DependenciesTableItem.ModuleType;
import jetbrains.mps.project.structure.modules.Dependency;
import jetbrains.mps.project.structure.modules.DevkitDescriptor;
import jetbrains.mps.project.structure.modules.GeneratorDescriptor;
import jetbrains.mps.project.structure.modules.LanguageDescriptor;
import jetbrains.mps.project.structure.modules.ModuleDescriptor;
import jetbrains.mps.project.structure.modules.SolutionDescriptor;
import jetbrains.mps.smodel.Generator;
import jetbrains.mps.smodel.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.module.SDependencyScope;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.module.SRepository;

import java.util.LinkedHashSet;
import java.util.Set;

public class ModuleDependTableModel extends DependTableModel<ModuleDescriptor> {
  private final SRepository myRepository;

  public ModuleDependTableModel(@NotNull SRepository repository, ModuleDescriptor descriptor) {
    super(descriptor);
    myRepository = repository;
  }

  @Override
  public void init() {
    if(!(myItem instanceof DevkitDescriptor)) {
      myRepository.getModelAccess().runReadAction(new Runnable() {
        @Override
        public void run() {
          for(Dependency dependency : myItem.getDependencies()) {
            SModuleReference moduleReference = dependency.getModuleRef();
            final SModule module = moduleReference.resolve(myRepository);
            if(module instanceof Language) {
              addLanguageItem(dependency);
            } else if(module instanceof Generator) {
              addGeneratorItem(dependency);
            } else {
              // XXX why not checked for Solution?
              addUnspecifiedItem(dependency);
            }
          }
        }
      });
    }

    if(myItem instanceof LanguageDescriptor) {
      LanguageDescriptor languageDescriptor = (LanguageDescriptor) myItem;

      for(SModuleReference moduleReference : languageDescriptor.getExtendedLanguages()) {
        addLanguageItem(new Dependency(moduleReference, SDependencyScope.EXTENDS));
      }
    }
    else if(myItem instanceof GeneratorDescriptor) {
      GeneratorDescriptor generatorDescriptor = (GeneratorDescriptor) myItem;

      for(SModuleReference moduleReference : generatorDescriptor.getDepGenerators()) {
        addGeneratorItem(new Dependency(moduleReference, SDependencyScope.EXTENDS));
      }
    }
    else if(myItem instanceof DevkitDescriptor) {
      DevkitDescriptor devkitDescriptor = (DevkitDescriptor) myItem;

      for(SModuleReference moduleReference : devkitDescriptor.getExtendedDevkits()) {
        addDevkitItem(new Dependency(moduleReference, SDependencyScope.EXTENDS));
      }

      for(SModuleReference lang : devkitDescriptor.getExportedLanguages()) {
        addLanguageItem(new Dependency(lang, SDependencyScope.EXTENDS));
      }

      for(SModuleReference moduleReference : devkitDescriptor.getExportedSolutions()) {
        addSolutionItem(new Dependency(moduleReference, SDependencyScope.EXTENDS));
      }
    }
  }

  @Override
  public boolean isModified() {
    boolean equals = true;

    if(!(myItem instanceof DevkitDescriptor))
      equals = myItem.getDependencies().containsAll(getDependencies()) && getDependencies().containsAll(myItem.getDependencies());

    if(myItem instanceof LanguageDescriptor) {
      LanguageDescriptor languageDescriptor = (LanguageDescriptor) myItem;
      equals = equals && languageDescriptor.getExtendedLanguages().containsAll(getExtendedModules()) && getExtendedModules().containsAll(languageDescriptor.getExtendedLanguages());
    }
    else if(myItem instanceof SolutionDescriptor) {
    }
    else if(myItem instanceof GeneratorDescriptor) {
      GeneratorDescriptor generatorDescriptor = (GeneratorDescriptor) myItem;
      equals = equals && generatorDescriptor.getDepGenerators().containsAll(getExtendedModules()) && getExtendedModules().containsAll(generatorDescriptor.getDepGenerators());
    }
    else if(myItem instanceof DevkitDescriptor) {
      DevkitDescriptor devkitDescriptor = (DevkitDescriptor) myItem;

      equals &= devkitDescriptor.getExtendedDevkits().containsAll(getModulesByType(ModuleType.DEVKIT));
      equals &= getModulesByType(ModuleType.DEVKIT).containsAll(devkitDescriptor.getExtendedDevkits());

      equals &= devkitDescriptor.getExportedLanguages().containsAll(getModulesByType(ModuleType.LANGUAGE));
      equals &= getModulesByType(ModuleType.LANGUAGE).containsAll(devkitDescriptor.getExportedLanguages());

      equals &= devkitDescriptor.getExportedSolutions().containsAll(getModulesByType(ModuleType.SOLUTION));
      equals &= getModulesByType(ModuleType.SOLUTION).containsAll(devkitDescriptor.getExportedSolutions());
    }

    return !equals;
  }

  @Override
  public void apply() {

    if(!(myItem instanceof DevkitDescriptor)) {
      myItem.getDependencies().clear();
      myItem.getDependencies().addAll(getDependencies());
    }

    if(myItem instanceof LanguageDescriptor) {
      LanguageDescriptor languageDescriptor = (LanguageDescriptor) myItem;

      languageDescriptor.getExtendedLanguages().clear();
      languageDescriptor.getExtendedLanguages().addAll(getExtendedModules());
    }
    else if(myItem instanceof SolutionDescriptor) {
    }
    else if(myItem instanceof GeneratorDescriptor) {
      GeneratorDescriptor generatorDescriptor = (GeneratorDescriptor) myItem;

      generatorDescriptor.getDepGenerators().clear();
      generatorDescriptor.getDepGenerators().addAll(getExtendedModules());
    }
    else if(myItem instanceof DevkitDescriptor) {
      DevkitDescriptor devkitDescriptor = (DevkitDescriptor) myItem;

      devkitDescriptor.getExtendedDevkits().clear();
      devkitDescriptor.getExtendedDevkits().addAll(getModulesByType(ModuleType.DEVKIT));

      devkitDescriptor.getExportedLanguages().clear();
      devkitDescriptor.getExportedLanguages().addAll(getModulesByType(ModuleType.LANGUAGE));

      devkitDescriptor.getExportedSolutions().clear();
      devkitDescriptor.getExportedSolutions().addAll(getModulesByType(ModuleType.SOLUTION));
    }
  }

  private Set<Dependency> getDependencies() {
    Set<Dependency> dependencies = new LinkedHashSet<Dependency>();
    for(DependenciesTableItem tableItem : myTableItems) {
      // FIXME here's comes a hack. We used to save only 'DEFAULT' SDependency with Dependency,
      // FIXME        and 'EXTENDS' as SModuleReference (@see getExtendedModules, below).
      // FIXME        However, with support for other dependency scopes introduced, we are going to transit
      // FIXME        to single Dependency presentation. Meanwhile (as there no scopes but EXTENDS and DEFAULT in legacy descriptors)
      // FIXME        this code simply leaves EXTENDS processing as it was, but saves all other dependencies with Dependency object
      if (tableItem.getItem().getScope() != SDependencyScope.EXTENDS) {
        dependencies.add(tableItem.getItem().copy()); // XXX not sure copy is needed here
      }
    }
    return dependencies;
  }

  /**
   * Public solely for use from condition of ModulePropertiesConfigurable.ModuleDependenciesTab that needs
   * to tell actual state of modules picked as 'extends'
   * @return
   */
  public Set<SModuleReference> getExtendedModules() {
    Set<SModuleReference> set = new LinkedHashSet<SModuleReference>();
    for(DependenciesTableItem tableItem : myTableItems)
      if(tableItem.getItem().getScope() == SDependencyScope.EXTENDS) // XXX see getDependencies() above
        set.add(tableItem.getItem().getModuleRef());

    return set;
  }

  private Set<SModuleReference> getModulesByType(ModuleType type) {
    Set<SModuleReference> set = new LinkedHashSet<SModuleReference>();
    for(DependenciesTableItem tableItem : myTableItems)
      if(tableItem.getModuleType().equals(type))
        set.add(tableItem.getItem().getModuleRef());

    return set;
  }

  @Override
  public String getColumnName(int column) {
    if (getItemColumnIndex() == column) {
      return "Module";
    }
    return super.getColumnName(column);
  }

  @Override
  public int getColumnCount() {
    if(myItem instanceof DevkitDescriptor) return 2;
    return super.getColumnCount();
  }

  @Override
  public int getRoleColumnIndex() {
    if(myItem instanceof DevkitDescriptor) return -1;
    return super.getRoleColumnIndex();
  }

  @Override
  public int getItemColumnIndex() {
    if(myItem instanceof DevkitDescriptor) return 0;
    return super.getItemColumnIndex();
  }

  @Override
  public int getExportColumnIndex() {
    if(myItem instanceof DevkitDescriptor) return -1;
    return super.getExportColumnIndex();
  }

  @Override
  public boolean isCellEditable(int rowIndex, int columnIndex) {
    if(columnIndex == getRoleColumnIndex())
      return true;
    return super.isCellEditable(rowIndex, columnIndex);
  }
}
