// Copyright 2014-2025 Jesper Larsson
//
// This file is part of Klipspringer, <https://klipspringer.eavadeaux.net/>
//
// Klipspringer is free software: you can redistribute it and/or modify it under the terms of the
// GNU General Public License as published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// Klipspringer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
// even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with Klipspringer. If
// not, see <https://www.gnu.org/licenses/>.

package net.avadeaux.klipspringer.codec;

import java.io.*;
import java.nio.ByteBuffer;

/** Representation of an audio device. */
public interface Device extends PcmFormat.Selector {
    /** Specification for where bits are placed when the bytes allocated for a sample are not
      * completely filled by the sample bits. This can be because the bit depth is not a muliple of
      * eight, or because four bytes are allocated for 24-bit samples. See <a
      * href="https://klipspringer.avadeaux.net/ten-standard-ways-of-representing-binary-numbers/">blog
      * post</a> for explanation of the formats. {@link PcmBuffer} can be used for converting
      * between layouts and other sample encoding discrepancies.
      */
    enum BitLayout {
        /** The lower bits are significant, and insignificant bits are set to zero, This is the
          * default, used where no bit layout is specified.
          */
        LSB,

        /** The higher bits are significant, and insignificant bits are zero. This corresponds to
          * scaling up the samples, so that they can be read as if all the bits are
          * significant. This is used by LAME and in the WAV format.
          */
        MSB,

        /** The lower bits are significant, and higher bit equal to the sign, the leftmost
          * significant bits. This can only be used when the encoding is signed. Values in this
          * format can be read as if all the bits are significant, producing signed values within
          * the range dictated by the number of bits per sample. It is used in FLAC encoding.
          */
        LSBX
    }

    /** PCM reader for input (from microphone, ADC, or whatever audio input is available). */
    interface Tuner extends PcmReader, Closeable {
        /** Tuner factory interface. */
        interface Factory extends PcmFormat.Selector {
            /** Creates a tuner for the format, or throws exception if not possible. */
            Tuner open(PcmFormat format) throws IOException;
        }

        /** Gets the audio format data read through the tuner. */
        PcmFormat format();

        /** See {@link Closeable#close()}. */
        void close() throws IOException;
    }

    /** PCM writer with optional methods to control real-time playing where applicanle. */
    interface Player extends PcmWriter {
        /** Player factory interface. */
        interface Factory extends PcmFormat.Selector {
            /** Creates a player for the format, or throws exception if not possible. */
            Player open(PcmFormat format) throws IOException;

            /** Gets the bit layout expected by players from this factory with the given format. If
              * the factory is associated with a device, this should match what is returned by
              * {@link Device#layout(PcmFormat)}. Default returns {@link BitLayout#LSB}.
              */
            default BitLayout layout(PcmFormat format) { return BitLayout.LSB; }
        }

        /** Gets the audio expected by the player. */
        PcmFormat format();

        /** Gets the bit layout expected by the player. If the player is produced by a {@link
          * Device.Player.Factory Factory}, the layout should match the value returned by {@link
          * Factory#layout(PcmFormat)}, and if the player is associated with a device, this should
          * match what is returned by {@link Device#layout(PcmFormat)}. Default returns {@link
          * BitLayout#LSB}. Default returns {@link BitLayout#LSB}.
          */
        default BitLayout layout() { return BitLayout.LSB; }

        /** Writes a block of audio data in {@link #layout() the bit layout expected} by the
          * player. For most players, it makes sense to always return true, since players do not
          * usually have a limit on the stream.
          */
        boolean write(ByteBuffer data) throws IOException;

        /** Gets an estimate of how many frames are currently buffered, waiting to be
          * played. Default returns zero.
          */
        default int bufferedFrames() throws IOException { return 0; }

        /** Checks if {@link #pause()} is supported. Default returns false. */
        default boolean canPause() { return false; }

        /** Pauses, if this optional operation is supported, audio playback at the
          * destination. Pausing during write may have the effect that write returns false, with the
          * buffer position set to where writing stopped. If already in paused state, this method
          * does nothing. Default implementation throws {@link UnsupportedOperationException}.
          */
        default void pause() throws IOException{ throw new UnsupportedOperationException(); }

        /** Resumes paused audio playback at the destination. If already in playback state, or if
          * pausing is not supported, this method does nothing (the default).
          */
        default void unpause() throws IOException { }

        /** Waits for player to send on any data it has buffered up and closes it. Default
          * implementation calls {@link #close()} immediately.
          */
        default void drain() throws IOException { close(); }
    }

    /** Gets the bit layout expected by players with the given format associated with this
      * device. Default returns {@link BitLayout#LSB}.
      */
    default BitLayout layout(PcmFormat format) { return BitLayout.LSB; }

    /** Gets the identifying name of the device. */
    String name();

    /** Gets a descriptive string of the device, which includes the {@link #name()}. */
    String toString();

    /** Writes a human-readable representation of available formats to the given stream. */
    void printAvailableFormats(PrintStream out);
}
