/*
 * Copyright (c) 2013 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 */
package com.ibm.wala.cast.js.html;

import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position;
import com.ibm.wala.cast.tree.impl.AbstractSourcePosition;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;

public class RangeFileMapping implements FileMapping {

  public static final class Range {
    private final int rangeStart;
    private final int rangeEnd;
    private final int rangeStartingLine;
    private final int rangeEndingLine;

    public boolean includes(Position offset) {
      return offset.getFirstOffset() != -1
          ? rangeStart <= offset.getFirstOffset() && offset.getLastOffset() <= rangeEnd
          : rangeStartingLine <= offset.getFirstLine()
              && (offset.getLastLine() == -1 ? offset.getFirstLine() : offset.getLastLine())
                  <= rangeEndingLine;
    }

    public Range(int rangeStart, int rangeEnd, int rangeStartingLine, int rangeEndingLine) {
      super();
      this.rangeStart = rangeStart;
      this.rangeEnd = rangeEnd;
      this.rangeStartingLine = rangeStartingLine;
      this.rangeEndingLine = rangeEndingLine;
    }

    public int getStart() {
      return rangeStart;
    }

    public int getEnd() {
      return rangeEnd;
    }

    public int getStartingLine() {
      return rangeStartingLine;
    }

    public int getEndingLine() {
      return rangeEndingLine;
    }

    @Override
    public String toString() {
      return "{" + rangeStart + "->" + rangeEnd + '}';
    }
  }

  private final Range range;
  private final URL includedURL;
  private final Position includePosition;

  public RangeFileMapping(
      int rangeStart,
      int rangeEnd,
      int rangeStartingLine,
      int rangeEndingLine,
      Position parentPosition,
      URL url) {
    assert parentPosition != null;
    this.range = new Range(rangeStart, rangeEnd, rangeStartingLine, rangeEndingLine);
    this.includePosition = parentPosition;
    includedURL = url;
  }

  @Override
  public String toString() {
    return range + ":" + includePosition;
  }

  @Override
  public IncludedPosition getIncludedPosition(final Position offset) {
    if (range.includes(offset)) {
      class Pos extends AbstractSourcePosition implements IncludedPosition {
        @Override
        public int getFirstLine() {
          // line numbers are decreed to start at 1, rather than 0
          return offset.getFirstLine() - range.getStartingLine() + 1;
        }

        @Override
        public int getLastLine() {
          return offset.getLastLine() == -1
              ? -1
              : offset.getLastLine() - range.getStartingLine() + 1;
        }

        @Override
        public int getFirstCol() {
          return offset.getFirstCol();
        }

        @Override
        public int getLastCol() {
          return offset.getLastCol();
        }

        @Override
        public int getFirstOffset() {
          return offset.getFirstOffset() == -1 ? -1 : offset.getFirstOffset() - range.getStart();
        }

        @Override
        public int getLastOffset() {
          return offset.getLastOffset() == -1 ? -1 : offset.getLastOffset() - range.getStart();
        }

        @Override
        public URL getURL() {
          return includedURL;
        }

        @Override
        public Reader getReader() throws IOException {
          return RangeFileMapping.this.getInputStream();
        }

        @Override
        public Position getIncludePosition() {
          return includePosition;
        }

        @Override
        public String toString() {
          return "[include:" + includePosition + ']' + super.toString();
        }
      }

      return new Pos();
    } else {
      return null;
    }
  }

  public Reader getInputStream() throws IOException {
    return new InputStreamReader(includedURL.openStream());
  }
}
