/*
 * Copyright 2003-2014 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.editor.runtime.style;

import jetbrains.mps.editor.runtime.style.StyleAttributeMap.DiscardValue;
import jetbrains.mps.openapi.editor.style.Style.IntPair;
import jetbrains.mps.openapi.editor.style.StyleAttribute;

import java.util.ArrayList;
import java.util.Collection;

public class TopLevelStyleMap extends StyleMap<Object> {

  public int getSize() {
    return values.length;
  }

  public <T> Object getValue(StyleAttribute<T> attribute, int index) {
    int topPointer = this.search(attribute.getIndex());
    return getValue(attribute, topPointer, index);
  }

  public int set(int attributeIndex, int topPointer, Object value) {
    if (!(value instanceof StyleAttributeMap)) {
      return super.set(attributeIndex, topPointer, value);
    }
    StyleAttributeMap attributes = (StyleAttributeMap) value;
    if (attributes.indexes.length == 0) {
      return super.set(attributeIndex, topPointer, null);
    } else if (attributes.indexes.length == 1 && attributes.indexes[0] == 0) {
      return super.set(attributeIndex, topPointer, attributes.values[0]);
    } else {
      return super.set(attributeIndex, topPointer, attributes);
    }
  }

  public <T> Object getValue(StyleAttribute<T> attribute, int topPointer, int index) {
    Object styleMapOrValue = get(topPointer);
    int pointer = StyleAttributeMap.search(styleMapOrValue, index);
    return StyleAttributeMap.get(styleMapOrValue, this, topPointer, pointer);
  }

  public <T> void setValue(StyleAttribute<T> attribute, int index, Object value) {
    int topPointer = this.search(attribute.getIndex());
    setValue(attribute, topPointer, index, value);
  }

  /**
   * @return new value of topPointer
   */
  public <T> int setValue(StyleAttribute<T> attribute, int topPointer, int index, Object value) {
    Object styleMapOrValue = get(topPointer);
    int pointer = StyleAttributeMap.search(styleMapOrValue, index);
    return setValue(attribute, topPointer, index, pointer, value);
  }

  private <T> int setValue(StyleAttribute<T> attribute, int topPointer, int index, int pointer, Object value) {
    Object styleMapOrValue = get(topPointer);
    if (value == null) {
      if (!StyleAttributeMap.isStyleAttributeMapEmpty(topPointer, pointer, index)) {
        return deleteValue(styleMapOrValue, attribute, topPointer, index, pointer);
      } else {
        return topPointer;
      }
    } else {
      if (StyleAttributeMap.isStyleAttributeMapEmpty(topPointer, pointer, index)) {
        return insertValue(styleMapOrValue, attribute, topPointer, index, pointer, value);
      } else {
        return replaceValue(styleMapOrValue, attribute, topPointer, index, pointer, value);
      }
    }
  }

  private <T> int deleteValue(Object styleMapOrValue, StyleAttribute<T> attribute, int topPointer, int index, int pointer) {
    assert !StyleAttributeMap.isStyleAttributeMapEmpty(topPointer, pointer, index);
    if (!(styleMapOrValue instanceof StyleAttributeMap)) {
      return set(attribute.getIndex(), topPointer, null);
    } else {
      StyleAttributeMap styleMap = (StyleAttributeMap) styleMapOrValue;
      styleMap.delete(pointer);

      if (styleMap.indexes.length == 1 && styleMap.indexes[0] == 0) {
        return set(attribute.getIndex(), topPointer, styleMap.values[0]);
      }
      if (styleMap.indexes.length == 0) {
        return set(attribute.getIndex(), topPointer, null);
      }
      return topPointer;
    }
  }

  private <T> int replaceValue(Object styleMapOrValue, StyleAttribute<T> attribute, int topPointer, int index, int pointer, Object value) {
    assert !StyleAttributeMap.isStyleAttributeMapEmpty(topPointer, pointer, index);
    if (!(styleMapOrValue instanceof StyleAttributeMap)) {
      assert index == 0;
      return set(attribute.getIndex(), topPointer, value);
    } else {
      StyleAttributeMap styleMap = (StyleAttributeMap) styleMapOrValue;
      styleMap.values[pointer] = value;
      return topPointer;
    }
  }

  private <T> int insertValue(Object styleMapOrValue, StyleAttribute<T> attribute, int topPointer, int index, int pointer, Object value) {
    assert StyleAttributeMap.isStyleAttributeMapEmpty(topPointer, pointer, index);
    if (!(styleMapOrValue instanceof StyleAttributeMap)) {
      if (index == 0) {
        return set(attribute.getIndex(), topPointer, value);
      } else {
        StyleAttributeMap<T> result = new StyleAttributeMap<T>();
        result.setValue(index, value);
        if (!TopLevelStyleMap.isEmpty(topPointer)) {
          result.setValue(0, get(topPointer));
        }
        return set(attribute.getIndex(), topPointer, result);
      }
    } else {
      StyleAttributeMap styleMap = (StyleAttributeMap) styleMapOrValue;
      styleMap.insert(index, pointer, value);
      return topPointer;
    }
  }

  public <T> Collection<IntPair<Object>> getAll(StyleAttribute<T> attribute) {
    int topPointer = this.search(attribute.getIndex());
    return getAll(attribute, topPointer);
  }

  public <T> Collection<IntPair<Object>> getAll(StyleAttribute<T> attribute, int topPointer) {
    Object styleMapOrValue = get(topPointer);
    if (!(styleMapOrValue instanceof StyleAttributeMap)) {
      ArrayList<IntPair<Object>> result = new ArrayList<IntPair<Object>>(1);
      if (!TopLevelStyleMap.isEmpty(topPointer)) {
        result.add(new IntPair<Object>(0, get(topPointer)));
      }
      return result;
    } else {
      StyleAttributeMap<T> styleMap = (StyleAttributeMap<T>) styleMapOrValue;
      return styleMap.getAll();
    }
  }

  public <T> Collection<IntPair<T>> getDiscardNullReplaced(StyleAttribute<T> attribute) {
    int topPointer = this.search(attribute.getIndex());
    return getDiscardNullReplaced(attribute, topPointer);
  }

  public <T> Collection<IntPair<T>> getDiscardNullReplaced(StyleAttribute<T> attribute, int topPointer) {
    Object styleMapOrValue = get(topPointer);
    if (!(styleMapOrValue instanceof StyleAttributeMap)) {
      ArrayList<IntPair<T>> result = new ArrayList<IntPair<T>>(1);
      if (!TopLevelStyleMap.isEmpty(topPointer)) {
        result.add(new IntPair<T>(0, get(topPointer) instanceof DiscardValue ? null : (T) get(topPointer)));
      }
      return result;
    } else {
      StyleAttributeMap<T> styleMap = (StyleAttributeMap<T>) styleMapOrValue;
      return styleMap.getDiscardNullReplaced();
    }
  }

  public <T> IntPair<T> getTopPair(StyleAttribute<T> attribute) {
    int topPointer = this.search(attribute.getIndex());
    return getTopPair(attribute, topPointer);
  }

  public <T> IntPair<T> getTopPair(StyleAttribute<T> attribute, int topPointer) {
    Object styleMapOrValue = get(topPointer);
    if (!(styleMapOrValue instanceof StyleAttributeMap)) {
      if (TopLevelStyleMap.isEmpty(topPointer) || get(topPointer) instanceof DiscardValue) {
        return null;
      } else {
        return new IntPair<T>(0, (T) get(topPointer));
      }
    } else {
      StyleAttributeMap styleMap = (StyleAttributeMap) styleMapOrValue;
      for (int i = styleMap.indexes.length - 1; i >= 0; i--) {
        if (!(styleMap.values[i] instanceof DiscardValue)) {
          return new IntPair<T>(styleMap.indexes[i], (T) styleMap.values[i]);
        }
      }
      return null;
    }
  }

}
