/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * 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 android.location;

import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.location.GlonassSatelliteEphemeris.GlonassHealthStatus;
import android.location.flags.Flags;
import android.os.Parcel;
import android.os.Parcelable;

import com.android.internal.util.Preconditions;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * A class contains Glonass almanac data.
 *
 * <p>This is defined in Glonass ICD v5.1 section 4.5.
 *
 * @hide
 */
@FlaggedApi(Flags.FLAG_GNSS_ASSISTANCE_INTERFACE)
@SystemApi
public final class GlonassAlmanac implements Parcelable {

    /** Almanac issue date in milliseconds (UTC) */
    private final long mIssueDateMillis;

    /** List of GlonassSatelliteAlmanacs. */
    @NonNull private final List<GlonassSatelliteAlmanac> mSatelliteAlmanacs;

    /**
     * Constructor for GlonassAlmanac.
     *
     * @param issueDateMillis The almanac issue date in milliseconds (UTC).
     * @param satelliteAlmanacs The list of GlonassSatelliteAlmanac.
     */
    public GlonassAlmanac(
            @IntRange(from = 0) long issueDateMillis,
            @NonNull List<GlonassSatelliteAlmanac> satelliteAlmanacs) {
        Preconditions.checkArgument(issueDateMillis >= 0);
        Preconditions.checkNotNull(satelliteAlmanacs, "satelliteAlmanacs cannot be null");
        mIssueDateMillis = issueDateMillis;
        mSatelliteAlmanacs = Collections.unmodifiableList(new ArrayList<>(satelliteAlmanacs));
    }

    /** Returns the almanac issue date in milliseconds (UTC). */
    @IntRange(from = 0)
    public long getIssueDateMillis() {
        return mIssueDateMillis;
    }

    /** Returns the list of GlonassSatelliteAlmanacs. */
    @NonNull
    public List<GlonassSatelliteAlmanac> getSatelliteAlmanacs() {
        return mSatelliteAlmanacs;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeLong(mIssueDateMillis);
        dest.writeTypedList(mSatelliteAlmanacs);
    }

    public static final @NonNull Parcelable.Creator<GlonassAlmanac> CREATOR =
            new Parcelable.Creator<GlonassAlmanac>() {
                @Override
                public GlonassAlmanac createFromParcel(@NonNull Parcel in) {
                    long issueDateMillis = in.readLong();
                    List<GlonassSatelliteAlmanac> satelliteAlmanacs = new ArrayList<>();
                    in.readTypedList(satelliteAlmanacs, GlonassSatelliteAlmanac.CREATOR);
                    return new GlonassAlmanac(issueDateMillis, satelliteAlmanacs);
                }

                @Override
                public GlonassAlmanac[] newArray(int size) {
                    return new GlonassAlmanac[size];
                }
            };

    @Override
    @NonNull
    public String toString() {
        StringBuilder builder = new StringBuilder("GlonassAlmanac[");
        builder.append("issueDateMillis = ").append(mIssueDateMillis);
        builder.append(", satelliteAlmanacs = ").append(mSatelliteAlmanacs);
        builder.append("]");
        return builder.toString();
    }

    /**
     * A class contains Glonass satellite almanac data.
     *
     * <p>This is defined in Glonass ICD v5.1 section 4.5.
     */
    public static final class GlonassSatelliteAlmanac implements Parcelable {
        /** Slot number. */
        private final int mSlotNumber;

        /** Satellite health status. */
        private final @GlonassHealthStatus int mHealthState;

        /** Frequency channel number. */
        private final int mFrequencyChannelNumber;

        /** Calendar day number within the four-year period beginning since the leap year. */
        private final int mCalendarDayNumber;

        /** Flag to indicates if the satellite is a GLONASS-M satellite. */
        private final boolean mGlonassM;

        /** Coarse value of satellite time correction to GLONASS time in seconds. */
        private final double mTau;

        /** Time of first ascending node passage of satellite in seconds. */
        private final double mTLambda;

        /** Longitude of the first ascending node in semi-circles. */
        private final double mLambda;

        /** Correction to the mean value of inclination in semi-circles. */
        private final double mDeltaI;

        /** Correction to the mean value of the draconian period in seconds per orbital period */
        private final double mDeltaT;

        /** Rate of change of draconian period in seconds per orbital period squared. */
        private final double mDeltaTDot;

        /** Eccentricity. */
        private final double mEccentricity;

        /** Argument of perigee in semi-circles. */
        private final double mOmega;

        private GlonassSatelliteAlmanac(Builder builder) {
            // Allow slotNumber beyond the range to support potential future extensibility.
            Preconditions.checkArgument(builder.mSlotNumber >= 1);
            // Allow healthState beyond the range to support potential future extensibility.
            Preconditions.checkArgument(builder.mHealthState >= 0);
            Preconditions.checkArgumentInRange(
                    builder.mFrequencyChannelNumber, -7, 6, "FrequencyChannelNumber");
            Preconditions.checkArgumentInRange(
                    builder.mCalendarDayNumber, 1, 1461, "CalendarDayNumber");
            Preconditions.checkArgumentInRange(builder.mTau, -1.9e-3f, 1.9e-3f, "Tau");
            Preconditions.checkArgumentInRange(builder.mTLambda, 0.0f, 44100.0f, "TLambda");
            Preconditions.checkArgumentInRange(builder.mLambda, -1.0f, 1.0f, "Lambda");
            Preconditions.checkArgumentInRange(builder.mDeltaI, -0.067f, 0.067f, "DeltaI");
            Preconditions.checkArgumentInRange(builder.mDeltaT, -3600.0f, 3600.0f, "DeltaT");
            Preconditions.checkArgumentInRange(builder.mDeltaTDot, -0.004f, 0.004f, "DeltaTDot");
            Preconditions.checkArgumentInRange(builder.mEccentricity, 0.0f, 0.03f, "Eccentricity");
            Preconditions.checkArgumentInRange(builder.mOmega, -1.0f, 1.0f, "Omega");
            mSlotNumber = builder.mSlotNumber;
            mHealthState = builder.mHealthState;
            mFrequencyChannelNumber = builder.mFrequencyChannelNumber;
            mCalendarDayNumber = builder.mCalendarDayNumber;
            mGlonassM = builder.mGlonassM;
            mTau = builder.mTau;
            mTLambda = builder.mTLambda;
            mLambda = builder.mLambda;
            mDeltaI = builder.mDeltaI;
            mDeltaT = builder.mDeltaT;
            mDeltaTDot = builder.mDeltaTDot;
            mEccentricity = builder.mEccentricity;
            mOmega = builder.mOmega;
        }

        /** Returns the slot number. */
        @IntRange(from = 1, to = 25)
        public int getSlotNumber() {
            return mSlotNumber;
        }

        /** Returns the satellite health status. */
        public @GlonassHealthStatus int getHealthState() {
            return mHealthState;
        }

        /** Returns the frequency channel number. */
        @IntRange(from = -7, to = 6)
        public int getFrequencyChannelNumber() {
            return mFrequencyChannelNumber;
        }

        /**
         * Returns the calendar day number within the four-year period beginning since the leap
         * year.
         */
        @IntRange(from = 1, to = 1461)
        public int getCalendarDayNumber() {
            return mCalendarDayNumber;
        }

        /** Returns true if the satellite is a GLONASS-M satellite, false otherwise. */
        public boolean isGlonassM() {
            return mGlonassM;
        }

        /** Returns the coarse value of satellite time correction to GLONASS time in seconds. */
        @FloatRange(from = -1.9e-3f, to = 1.9e-3f)
        public double getTau() {
            return mTau;
        }

        /** Returns the time of first ascending node passage of satellite in seconds. */
        @FloatRange(from = 0.0f, to = 44100.0f)
        public double getTLambda() {
            return mTLambda;
        }

        /** Returns the longitude of the first ascending node in semi-circles. */
        @FloatRange(from = -1.0f, to = 1.0f)
        public double getLambda() {
            return mLambda;
        }

        /** Returns the correction to the mean value of inclination in semi-circles. */
        @FloatRange(from = -0.067f, to = 0.067f)
        public double getDeltaI() {
            return mDeltaI;
        }

        /**
         * Returns the correction to the mean value of the draconian period in seconds per orbital
         * period
         */
        @FloatRange(from = -3600.0f, to = 3600.0f)
        public double getDeltaT() {
            return mDeltaT;
        }

        /** Returns the rate of change of draconian period in seconds per orbital period squared. */
        @FloatRange(from = -0.004f, to = 0.004f)
        public double getDeltaTDot() {
            return mDeltaTDot;
        }

        /** Returns the eccentricity. */
        @FloatRange(from = 0.0f, to = 0.03f)
        public double getEccentricity() {
            return mEccentricity;
        }

        /** Returns the Argument of perigee in semi-circles. */
        @FloatRange(from = -1.0f, to = 1.0f)
        public double getOmega() {
            return mOmega;
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(@NonNull Parcel dest, int flags) {
            dest.writeInt(mSlotNumber);
            dest.writeInt(mHealthState);
            dest.writeInt(mFrequencyChannelNumber);
            dest.writeInt(mCalendarDayNumber);
            dest.writeBoolean(mGlonassM);
            dest.writeDouble(mTau);
            dest.writeDouble(mTLambda);
            dest.writeDouble(mLambda);
            dest.writeDouble(mDeltaI);
            dest.writeDouble(mDeltaT);
            dest.writeDouble(mDeltaTDot);
            dest.writeDouble(mEccentricity);
            dest.writeDouble(mOmega);
        }

        public static final @NonNull Parcelable.Creator<GlonassSatelliteAlmanac> CREATOR =
                new Parcelable.Creator<GlonassSatelliteAlmanac>() {
                    @Override
                    public GlonassSatelliteAlmanac createFromParcel(@NonNull Parcel source) {
                        return new GlonassSatelliteAlmanac.Builder()
                                .setSlotNumber(source.readInt())
                                .setHealthState(source.readInt())
                                .setFrequencyChannelNumber(source.readInt())
                                .setCalendarDayNumber(source.readInt())
                                .setGlonassM(source.readBoolean())
                                .setTau(source.readDouble())
                                .setTLambda(source.readDouble())
                                .setLambda(source.readDouble())
                                .setDeltaI(source.readDouble())
                                .setDeltaT(source.readDouble())
                                .setDeltaTDot(source.readDouble())
                                .setEccentricity(source.readDouble())
                                .setOmega(source.readDouble())
                                .build();
                    }

                    @Override
                    public GlonassSatelliteAlmanac[] newArray(int size) {
                        return new GlonassSatelliteAlmanac[size];
                    }
                };

        @Override
        @NonNull
        public String toString() {
            StringBuilder builder = new StringBuilder("GlonassSatelliteAlmanac[");
            builder.append("slotNumber = ").append(mSlotNumber);
            builder.append(", healthState = ").append(mHealthState);
            builder.append(", frequencyChannelNumber = ").append(mFrequencyChannelNumber);
            builder.append(", calendarDayNumber = ").append(mCalendarDayNumber);
            builder.append(", glonassM = ").append(mGlonassM);
            builder.append(", tau = ").append(mTau);
            builder.append(", tLambda = ").append(mTLambda);
            builder.append(", lambda = ").append(mLambda);
            builder.append(", deltaI = ").append(mDeltaI);
            builder.append(", deltaT = ").append(mDeltaT);
            builder.append(", deltaTDot = ").append(mDeltaTDot);
            builder.append(", eccentricity = ").append(mEccentricity);
            builder.append(", omega = ").append(mOmega);
            builder.append("]");
            return builder.toString();
        }

        /** Builder for {@link GlonassSatelliteAlmanac}. */
        public static final class Builder {
            private int mSlotNumber;
            private int mHealthState;
            private int mFrequencyChannelNumber;
            private int mCalendarDayNumber;
            private boolean mGlonassM;
            private double mTau;
            private double mTLambda;
            private double mLambda;
            private double mDeltaI;
            private double mDeltaT;
            private double mDeltaTDot;
            private double mEccentricity;
            private double mOmega;

            /** Sets the slot number. */
            @NonNull
            public Builder setSlotNumber(@IntRange(from = 1, to = 25) int slotNumber) {
                mSlotNumber = slotNumber;
                return this;
            }

            /** Sets the satellite health status. */
            @NonNull
            public Builder setHealthState(@GlonassHealthStatus int healthState) {
                mHealthState = healthState;
                return this;
            }

            /** Sets the frequency channel number. */
            @NonNull
            public Builder setFrequencyChannelNumber(
                    @IntRange(from = -7, to = 6) int frequencyChannelNumber) {
                mFrequencyChannelNumber = frequencyChannelNumber;
                return this;
            }

            /**
             * Sets the calendar day number within the four-year period beginning since the leap
             * year.
             */
            @NonNull
            public Builder setCalendarDayNumber(
                    @IntRange(from = 1, to = 1461) int calendarDayNumber) {
                mCalendarDayNumber = calendarDayNumber;
                return this;
            }

            /** Sets to true if the satellite is a GLONASS-M satellite, false otherwise. */
            @NonNull
            public Builder setGlonassM(boolean isGlonassM) {
                this.mGlonassM = isGlonassM;
                return this;
            }

            /** Sets the coarse value of satellite time correction to GLONASS time in seconds. */
            @NonNull
            public Builder setTau(@FloatRange(from = -1.9e-3f, to = 1.9e-3f) double tau) {
                mTau = tau;
                return this;
            }

            /** Sets the time of first ascending node passage of satellite in seconds. */
            @NonNull
            public Builder setTLambda(@FloatRange(from = 0.0f, to = 44100.0f) double tLambda) {
                mTLambda = tLambda;
                return this;
            }

            /** Sets the longitude of the first ascending node in semi-circles. */
            @NonNull
            public Builder setLambda(@FloatRange(from = -1.0f, to = 1.0f) double lambda) {
                mLambda = lambda;
                return this;
            }

            /** Sets the correction to the mean value of inclination in semi-circles. */
            @NonNull
            public Builder setDeltaI(@FloatRange(from = -0.067f, to = 0.067f) double deltaI) {
                mDeltaI = deltaI;
                return this;
            }

            /**
             * Sets the correction to the mean value of the draconian period in seconds per orbital
             * period.
             */
            @NonNull
            public Builder setDeltaT(@FloatRange(from = -3600.0f, to = 3600.0f) double deltaT) {
                mDeltaT = deltaT;
                return this;
            }

            /**
             * Sets the rate of change of draconian period in seconds per orbital period squared.
             */
            @NonNull
            public Builder setDeltaTDot(@FloatRange(from = -0.004f, to = 0.004f) double deltaTDot) {
                mDeltaTDot = deltaTDot;
                return this;
            }

            /** Sets the eccentricity. */
            @NonNull
            public Builder setEccentricity(
                    @FloatRange(from = 0.0f, to = 0.03f) double eccentricity) {
                mEccentricity = eccentricity;
                return this;
            }

            /** Sets the Argument of perigee in semi-circles. */
            @NonNull
            public Builder setOmega(@FloatRange(from = -1.0f, to = 1.0f) double omega) {
                mOmega = omega;
                return this;
            }

            /** Builds a {@link GlonassSatelliteAlmanac}. */
            @NonNull
            public GlonassSatelliteAlmanac build() {
                return new GlonassSatelliteAlmanac(this);
            }
        }
    }
}
