// Copyright 2017 The Bazel Authors. All rights reserved.
//
// 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.devtools.build.lib.vfs;

import com.google.common.base.Preconditions;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;

/**
 * Abstract base class for {@link PathFragment} instances that will be allocated when Blaze is run
 * on a non-Windows platform.
 */
abstract class UnixPathFragment extends PathFragment {
  static final Helper HELPER = new Helper();
  /**
    * We have two concrete subclasses with zero per-instance additional memory overhead. Do not add
    * any fields. See the comment on memory use in PathFragment for details on the current
    * per-instance memory usage.
    */

  protected UnixPathFragment(String[] segments) {
    super(segments);
  }

  @Override
  protected int computeHashCode() {
    int h = 0;
    for (String segment : segments) {
      int segmentHash = segment.hashCode();
      h = h * 31 + segmentHash;
    }
    return h;
  }

  @Override
  public String windowsVolume() {
    return "";
  }

  @Override
  public char getDriveLetter() {
    return '\0';
  }

  private static class Helper extends PathFragment.Helper {
    private static final char SEPARATOR_CHAR = '/';

    @Override
    PathFragment create(String path) {
      boolean isAbsolute = path.length() > 0 && isSeparator(path.charAt(0));
      return isAbsolute
          ? new AbsoluteUnixPathFragment(segment(path, 1))
          : new RelativeUnixPathFragment(segment(path, 0));
    }

    @Override
    PathFragment createAlreadyInterned(char driveLetter, boolean isAbsolute, String[] segments) {
      Preconditions.checkState(driveLetter == '\0', driveLetter);
      return isAbsolute
          ? new AbsoluteUnixPathFragment(segments)
          : new RelativeUnixPathFragment(segments);
    }

    @Override
    char getPrimarySeparatorChar() {
      return SEPARATOR_CHAR;
    }

    @Override
    boolean isSeparator(char c) {
      return c == SEPARATOR_CHAR;
    }

    @Override
    boolean containsSeparatorChar(String path) {
      return path.indexOf(SEPARATOR_CHAR) != -1;
    }

    @Override
    boolean segmentsEqual(int length, String[] segments1, int offset1, String[] segments2) {
      if ((segments1.length - offset1) < length || segments2.length < length) {
        return false;
      }
      for (int i = 0; i < length; ++i) {
        String seg1 = segments1[i + offset1];
        String seg2 = segments2[i];
        if ((seg1 == null) != (seg2 == null)) {
          return false;
        }
        if (seg1 == null) {
          continue;
        }
        if (!seg1.equals(seg2)) {
          return false;
        }
      }
      return true;
    }

    @Override
    protected int compare(PathFragment pathFragment1, PathFragment pathFragment2) {
      if (pathFragment1.isAbsolute() != pathFragment2.isAbsolute()) {
        return pathFragment1.isAbsolute() ? -1 : 1;
      }
      String[] segments1 = pathFragment1.segments();
      String[] segments2 = pathFragment2.segments();
      int len1 = segments1.length;
      int len2 = segments2.length;
      int n = Math.min(len1, len2);
      for (int i = 0; i < n; i++) {
        String seg1 = segments1[i];
        String seg2 = segments2[i];
        int cmp = seg1.compareTo(seg2);
        if (cmp != 0) {
          return cmp;
        }
      }
      return len1 - len2;
    }
  }

  private static final class AbsoluteUnixPathFragment extends UnixPathFragment {
    private AbsoluteUnixPathFragment(String[] segments) {
      super(segments);
    }

    @Override
    public boolean isAbsolute() {
      return true;
    }

    @Override
    protected int computeHashCode() {
      int h = Boolean.TRUE.hashCode();
      h = h * 31 + super.computeHashCode();
      return h;
    }

    @Override
    public boolean equals(Object other) {
      if (!(other instanceof AbsoluteUnixPathFragment)) {
        return false;
      }
      if (this == other) {
        return true;
      }
      AbsoluteUnixPathFragment otherAbsoluteUnixPathFragment = (AbsoluteUnixPathFragment) other;
      return HELPER.segmentsEqual(this.segments, otherAbsoluteUnixPathFragment.segments);
    }

    // Java serialization looks for the presence of this method in the concrete class. It is not
    // inherited from the parent class.
    @Override
    protected Object writeReplace() {
      return super.writeReplace();
    }

    // Java serialization looks for the presence of this method in the concrete class. It is not
    // inherited from the parent class.
    @Override
    protected void readObject(ObjectInputStream stream) throws InvalidObjectException {
      super.readObject(stream);
    }
  }

  private static final class RelativeUnixPathFragment extends UnixPathFragment {
    private RelativeUnixPathFragment(String[] segments) {
      super(segments);
    }

    @Override
    public boolean isAbsolute() {
      return false;
    }

    @Override
    protected int computeHashCode() {
      int h = Boolean.FALSE.hashCode();
      h = h * 31 + super.computeHashCode();
      return h;
    }

    @Override
    public boolean equals(Object other) {
      if (!(other instanceof RelativeUnixPathFragment)) {
        return false;
      }
      if (this == other) {
        return true;
      }
      RelativeUnixPathFragment otherRelativeUnixPathFragment = (RelativeUnixPathFragment) other;
      return HELPER.segmentsEqual(this.segments, otherRelativeUnixPathFragment.segments);
    }

    // Java serialization looks for the presence of this method in the concrete class. It is not
    // inherited from the parent class.
    @Override
    protected Object writeReplace() {
      return super.writeReplace();
    }

    // Java serialization looks for the presence of this method in the concrete class. It is not
    // inherited from the parent class.
    @Override
    protected void readObject(ObjectInputStream stream) throws InvalidObjectException {
      super.readObject(stream);
    }
  }
}
