/*
 * Copyright (C) 2014 Indeed 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.indeed.imhotep;

import com.google.common.primitives.Longs;
import com.indeed.imhotep.protobuf.ShardInfoMessage;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.util.Collection;

/**
 * @author jsgroth
 */
public class ShardInfo implements Comparable<ShardInfo> {
    private static final Logger log = Logger.getLogger(ShardInfo.class);
    private static final DateTimeFormatter yyyymmdd = DateTimeFormat.forPattern("yyyyMMdd").withZone(DateTimeZone.forOffsetHours(-6));
    private static final DateTimeFormatter yyyymmddhh = DateTimeFormat.forPattern("yyyyMMdd.HH").withZone(DateTimeZone.forOffsetHours(-6));

    public final String dataset;
    public final String shardId;
    public final Collection<String> loadedMetrics;
    public final int numDocs;
    public final long version;

    private DateTimeRange range;    // lazily computed

    public ShardInfo(String dataset, String shardId, Collection<String> loadedMetrics, int numDocs, long version) {
        this.dataset = dataset;
        this.shardId = shardId;
        this.loadedMetrics = loadedMetrics;
        this.numDocs = numDocs;
        this.version = version;
    }

    public String getDataset() {
        return dataset;
    }

    public String getShardId() {
        return shardId;
    }

    public Collection<String> getLoadedMetrics() {
        return loadedMetrics;
    }

    public int getNumDocs() {
        return numDocs;
    }

    public long getVersion() {
        return version;
    }

    public DateTime getStart() {
        if(range == null) { // this is not thread safe but the operation should be deterministic with no side effects
            range = parseDateTime(shardId);
        }
        return range.start;
    }

    public DateTime getEnd() {
        if(range == null) {
            range = parseDateTime(shardId);
        }
        return range.end;
    }

    public DateTimeRange getRange() {
        if(range == null) {
            range = parseDateTime(shardId);
        }
        return range;
    }

    public static ShardInfo fromProto(ShardInfoMessage protoShard) {
        return new ShardInfo(
                protoShard.getDataset(),
                protoShard.getShardId(),
                protoShard.getLoadedMetricList(),
                protoShard.getNumDocs(),
                protoShard.getVersion()
        );
    }

    public ShardInfoMessage toProto() {
        return ShardInfoMessage.newBuilder()
                .setDataset(dataset)
                .setShardId(shardId)
                .addAllLoadedMetric(loadedMetrics)
                .setNumDocs(numDocs)
                .setVersion(version)
                .build();
    }

    @Override
    public int compareTo(ShardInfo o) {
        final int c = dataset.compareTo(o.dataset);
        if (c != 0) return c;
        final int c2 = shardId.compareTo(o.shardId);
        if (c2 != 0) return c2;
        return Longs.compare(version, o.version);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        ShardInfo shardInfo = (ShardInfo) o;

        if (numDocs != shardInfo.numDocs) return false;
        if (version != shardInfo.version) return false;
        if (dataset != null ? !dataset.equals(shardInfo.dataset) : shardInfo.dataset != null) return false;
        if (loadedMetrics != null ? !loadedMetrics.equals(shardInfo.loadedMetrics) : shardInfo.loadedMetrics != null)
            return false;
        if (shardId != null ? !shardId.equals(shardInfo.shardId) : shardInfo.shardId != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = dataset != null ? dataset.hashCode() : 0;
        result = 31 * result + (shardId != null ? shardId.hashCode() : 0);
        result = 31 * result + (loadedMetrics != null ? loadedMetrics.hashCode() : 0);
        result = 31 * result + numDocs;
        result = 31 * result + (int) (version ^ (version >>> 32));
        return result;
    }

    @Override
    public String toString() {
        return "["+dataset+":"+shardId+"]";
    }

    public static DateTimeRange parseDateTime(String shardId) {
        try {
            if (shardId.length() > 16) {
                final DateTime start = yyyymmddhh.parseDateTime(shardId.substring(5, 16));
                final DateTime end = yyyymmddhh.parseDateTime(shardId.substring(17, 28));
                return new DateTimeRange(start, end);
            } else if (shardId.length() > 13) {
                final DateTime start = yyyymmddhh.parseDateTime(shardId.substring(5, 16));
                final DateTime end = start.plusHours(1);
                return new DateTimeRange(start, end);
            } else {
                final DateTime start = yyyymmdd.parseDateTime(shardId.substring(5, 13));
                final DateTime end = start.plusDays(1);
                return new DateTimeRange(start, end);
            }
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

    public static final class DateTimeRange {
        public final DateTime start;
        public final DateTime end;

        public DateTimeRange(DateTime start, DateTime end) {
            this.start = start;
            this.end = end;
        }
    }
}
