/*
 * Copyright 2008 Google Inc.
 * 
 * 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.gwt.dev.jjs;

import com.google.gwt.dev.jjs.Correlation.Axis;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

/**
 * Describes where a SourceInfo's node came from. This class currently includes
 * only physical origin information, but could be extended to provide support
 * for source-Module and -Generators.
 * 
 * TODO: rename this class to make it parallel to {@link SourceInfoCorrelation}?
 * 
 * TODO: make this package-protected?
 */
public class SourceOrigin implements SourceInfo {

  private static class SourceOriginPos extends SourceOrigin {
    private final int endPos;
    private final int startPos;

    private SourceOriginPos(String location, int startLine, int startPos,
        int endPos) {
      super(location, startLine);
      this.startPos = startPos;
      this.endPos = endPos;
    }

    @Override
    public int getEndPos() {
      return endPos;
    }

    @Override
    public int getStartPos() {
      return startPos;
    }
    
    // super.equals and hashCode call getStartPos() and getEndPos(),
    // so there is no need to implement them in this subclass
  }

  public static final SourceInfo UNKNOWN = new SourceOrigin("Unknown", 0) {
    private Object readResolve() {
      return UNKNOWN;
    }
  };

  /**
   * Cache to reuse recently-created origins. This is very useful for JS nodes,
   * since {@link com.google.gwt.dev.js.JsParser} currently only provides line
   * numbers rather than character positions, so we get a lot of reuse there. We
   * get barely any reuse in the Java AST. Synchronized since several threads
   * could operate on it at once during parallel optimization phases.
   */
  private static final Map<SourceOrigin, SourceOrigin> CANONICAL_SOURCE_ORIGINS = Collections.synchronizedMap(new LinkedHashMap<SourceOrigin, SourceOrigin>(
      150, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Entry<SourceOrigin, SourceOrigin> eldest) {
      return size() > 100;
    }
  });

  /**
   * Creates SourceOrigin nodes.
   */
  public static SourceOrigin create(int startPos, int endPos, int startLine,
      String fileName) {
    if (startPos < 0 && endPos < 0) {
      return create(startLine, fileName);
    }

    return new SourceOriginPos(fileName, startLine, startPos, endPos);
  }

  /**
   * Creates SourceOrigin nodes. This factory method will attempt to provide
   * canonicalized instances of SourceOrigin objects.
   */
  public static SourceOrigin create(int startLine, String fileName) {

    SourceOrigin newInstance = new SourceOrigin(fileName, startLine);
    SourceOrigin canonical = CANONICAL_SOURCE_ORIGINS.get(newInstance);

    assert canonical == null
        || (newInstance != canonical && newInstance.equals(canonical));

    if (canonical != null) {
      return canonical;
    } else {
      CANONICAL_SOURCE_ORIGINS.put(newInstance, newInstance);
      return newInstance;
    }
  }

  // TODO: Add Module and Generator tracking
  private final String fileName;
  private final int startLine;

  private SourceOrigin(String location, int startLine) {
    this.fileName = location;
    this.startLine = startLine;
  }

  public void addCorrelation(Correlation c) {
  }

  public void copyMissingCorrelationsFrom(SourceInfo other) {
  }

  @Override
  public boolean equals(Object o) {
    if (!(o instanceof SourceOrigin)) {
      return false;
    }
    SourceOrigin other = (SourceOrigin) o;
    return startLine == other.startLine && getEndPos() == other.getEndPos()
        && getStartPos() == other.getStartPos()
        && fileName.equals(other.fileName);
  }

  public List<Correlation> getAllCorrelations() {
    return Collections.emptyList();
  }

  public List<Correlation> getAllCorrelations(Axis axis) {
    return Collections.emptyList();
  }

  public int getEndPos() {
    return -1;
  }

  public String getFileName() {
    return fileName;
  }

  public SourceOrigin getOrigin() {
    return this;
  }

  public Correlation getPrimaryCorrelation(Axis axis) {
    return null;
  }

  public Set<Correlation> getPrimaryCorrelations() {
    return Collections.emptySet();
  }
  
  public Correlation[] getPrimaryCorrelationsArray() {
    return new Correlation[0];
  }

  public int getStartLine() {
    return startLine;
  }

  public int getStartPos() {
    return -1;
  }

  @Override
  public int hashCode() {
    return 2 + 13 * fileName.hashCode() + 17 * startLine + 29 * getStartPos()
        + 31 * getEndPos();
  }

  public SourceInfo makeChild(Class<?> caller, String description) {
    return this;
  }

  public SourceInfo makeChild(Class<?> caller, String description,
      SourceInfo... merge) {
    return this;
  }

  public void merge(SourceInfo... sourceInfos) {
  }

  @Override
  public String toString() {
    return getFileName() + '(' + getStartLine() + ')';
  }
}