/*
 * Copyright (C) 2014 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 com.android.internal.alsa;

import android.util.Slog;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;

/**
 * @hide
 * Retrieves information from an ALSA "devices" file.
 */
public class AlsaDevicesParser {
    private static final String TAG = "AlsaDevicesParser";
    protected static final boolean DEBUG = false;

    private static final String kDevicesFilePath = "/proc/asound/devices";

    private static final int kIndex_CardDeviceField = 5;
    private static final int kStartIndex_CardNum = 6;
    private static final int kEndIndex_CardNum = 8; // one past
    private static final int kStartIndex_DeviceNum = 9;
    private static final int kEndIndex_DeviceNum = 11; // one past
    private static final int kStartIndex_Type = 14;

    private static LineTokenizer mTokenizer = new LineTokenizer(" :[]-");

    private boolean mHasCaptureDevices = false;
    private boolean mHasPlaybackDevices = false;
    private boolean mHasMIDIDevices = false;

    public class AlsaDeviceRecord {
        public static final int kDeviceType_Unknown = -1;
        public static final int kDeviceType_Audio = 0;
        public static final int kDeviceType_Control = 1;
        public static final int kDeviceType_MIDI = 2;

        public static final int kDeviceDir_Unknown = -1;
        public static final int kDeviceDir_Capture = 0;
        public static final int kDeviceDir_Playback = 1;

        int mCardNum = -1;
        int mDeviceNum = -1;
        int mDeviceType = kDeviceType_Unknown;
        int mDeviceDir = kDeviceDir_Unknown;

        public AlsaDeviceRecord() {}

        public boolean parse(String line) {
            // "0123456789012345678901234567890"
            // "  2: [ 0-31]: digital audio playback"
            // "  3: [ 0-30]: digital audio capture"
            // " 35: [ 1]   : control"
            // " 36: [ 2- 0]: raw midi"

            final int kToken_LineNum = 0;
            final int kToken_CardNum = 1;
            final int kToken_DeviceNum = 2;
            final int kToken_Type0 = 3; // "digital", "control", "raw"
            final int kToken_Type1 = 4; // "audio", "midi"
            final int kToken_Type2 = 5; // "capture", "playback"

            int tokenOffset = 0;
            int delimOffset = 0;
            int tokenIndex = kToken_LineNum;
            while (true) {
                tokenOffset = mTokenizer.nextToken(line, delimOffset);
                if (tokenOffset == LineTokenizer.kTokenNotFound) {
                    break; // bail
                }
                delimOffset = mTokenizer.nextDelimiter(line, tokenOffset);
                if (delimOffset == LineTokenizer.kTokenNotFound) {
                    delimOffset = line.length();
                }
                String token = line.substring(tokenOffset, delimOffset);

                try {
                    switch (tokenIndex) {
                    case kToken_LineNum:
                        // ignore
                        break;

                    case kToken_CardNum:
                        mCardNum = Integer.parseInt(token);
                        if (line.charAt(delimOffset) != '-') {
                            tokenIndex++; // no device # in the token stream
                        }
                        break;

                    case kToken_DeviceNum:
                        mDeviceNum = Integer.parseInt(token);
                        break;

                    case kToken_Type0:
                        if (token.equals("digital")) {
                            // NOP
                        } else if (token.equals("control")) {
                            mDeviceType = kDeviceType_Control;
                        } else if (token.equals("raw")) {
                            // NOP
                        }
                        break;

                    case kToken_Type1:
                        if (token.equals("audio")) {
                            mDeviceType = kDeviceType_Audio;
                        } else if (token.equals("midi")) {
                            mDeviceType = kDeviceType_MIDI;
                            mHasMIDIDevices = true;
                        }
                        break;

                    case kToken_Type2:
                        if (token.equals("capture")) {
                            mDeviceDir = kDeviceDir_Capture;
                            mHasCaptureDevices = true;
                        } else if (token.equals("playback")) {
                            mDeviceDir = kDeviceDir_Playback;
                            mHasPlaybackDevices = true;
                        }
                        break;
                    } // switch (tokenIndex)
                } catch (NumberFormatException e) {
                    Slog.e(TAG, "Failed to parse token " + tokenIndex + " of " + kDevicesFilePath
                        + " token: " + token);
                    return false;
                }

                tokenIndex++;
            } // while (true)

            return true;
        } // parse()

        public String textFormat() {
            StringBuilder sb = new StringBuilder();
            sb.append("[" + mCardNum + ":" + mDeviceNum + "]");

            switch (mDeviceType) {
            case kDeviceType_Unknown:
                sb.append(" N/A");
                break;
            case kDeviceType_Audio:
                sb.append(" Audio");
                break;
            case kDeviceType_Control:
                sb.append(" Control");
                break;
            case kDeviceType_MIDI:
                sb.append(" MIDI");
                break;
            }

            switch (mDeviceDir) {
            case kDeviceDir_Unknown:
                sb.append(" N/A");
                break;
            case kDeviceDir_Capture:
                sb.append(" Capture");
                break;
            case kDeviceDir_Playback:
                sb.append(" Playback");
                break;
            }

            return sb.toString();
        }
    }

    private ArrayList<AlsaDeviceRecord> mDeviceRecords = new ArrayList<AlsaDeviceRecord>();

    public AlsaDevicesParser() {}

    //
    // Access
    //
    public int getDefaultDeviceNum(int card) {
        // TODO - This (obviously) isn't sufficient. Revisit.
        return 0;
    }

    //
    // Predicates
    //
   public boolean hasPlaybackDevices() {
        return mHasPlaybackDevices;
    }

    public boolean hasPlaybackDevices(int card) {
        for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
            if (deviceRecord.mCardNum == card &&
                deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
                deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Playback) {
                return true;
            }
        }
        return false;
    }

    public boolean hasCaptureDevices() {
        return mHasCaptureDevices;
    }

    public boolean hasCaptureDevices(int card) {
        for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
            if (deviceRecord.mCardNum == card &&
                deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_Audio &&
                deviceRecord.mDeviceDir == AlsaDeviceRecord.kDeviceDir_Capture) {
                return true;
            }
        }
        return false;
    }

    public boolean hasMIDIDevices() {
        return mHasMIDIDevices;
    }

    public boolean hasMIDIDevices(int card) {
        for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
            if (deviceRecord.mCardNum == card &&
                deviceRecord.mDeviceType == AlsaDeviceRecord.kDeviceType_MIDI) {
                return true;
            }
        }
        return false;
    }

    //
    // Process
    //
    private boolean isLineDeviceRecord(String line) {
        return line.charAt(kIndex_CardDeviceField) == '[';
    }

    public void scan() {
        mDeviceRecords.clear();

        File devicesFile = new File(kDevicesFilePath);
        try {
            FileReader reader = new FileReader(devicesFile);
            BufferedReader bufferedReader = new BufferedReader(reader);
            String line = "";
            while ((line = bufferedReader.readLine()) != null) {
                if (isLineDeviceRecord(line)) {
                    AlsaDeviceRecord deviceRecord = new AlsaDeviceRecord();
                    deviceRecord.parse(line);
                    mDeviceRecords.add(deviceRecord);
                }
            }
            reader.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //
    // Loging
    //
    public void Log(String heading) {
        if (DEBUG) {
            Slog.i(TAG, heading);
            for (AlsaDeviceRecord deviceRecord : mDeviceRecords) {
                Slog.i(TAG, deviceRecord.textFormat());
            }
        }
    }
} // class AlsaDevicesParser

