/*
 * Copyright (C) 2015 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.commands.hid;

import android.util.JsonReader;
import android.util.JsonToken;
import android.util.Log;
import android.util.SparseArray;

import libcore.io.IoUtils;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;

public class Hid {
    private static final String TAG = "HID";

    private final Event.Reader mReader;
    private final SparseArray<Device> mDevices;

    private static void usage() {
        error("Usage: hid [FILE]");
    }

    public static void main(String[] args) {
        if (args.length != 1) {
            usage();
            System.exit(1);
        }

        InputStream stream = null;
        try {
            if (args[0].equals("-")) {
                stream = System.in;
            } else {
                File f = new File(args[0]);
                stream = new FileInputStream(f);
            }
            (new Hid(stream)).run();
        } catch (Exception e) {
            error("HID injection failed.", e);
            System.exit(1);
        } finally {
            IoUtils.closeQuietly(stream);
        }
    }

    private Hid(InputStream in) {
        mDevices = new SparseArray<Device>();
        try {
            mReader = new Event.Reader(new InputStreamReader(in, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    private void run() {
        try {
            Event e = null;
            while ((e = mReader.getNextEvent()) != null) {
                process(e);
            }
        } catch (IOException ex) {
            error("Error reading in events.", ex);
        }

        for (int i = 0; i < mDevices.size(); i++) {
            mDevices.valueAt(i).close();
        }
    }

    private void process(Event e) {
        final int index = mDevices.indexOfKey(e.getId());
        if (index >= 0) {
            Device d = mDevices.valueAt(index);
            if (Event.COMMAND_DELAY.equals(e.getCommand())) {
                d.addDelay(e.getDuration());
            } else if (Event.COMMAND_REPORT.equals(e.getCommand())) {
                d.sendReport(e.getReport());
            } else {
                if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
                    error("Device id=" + e.getId() + " is already registered. Ignoring event.");
                } else {
                    error("Unknown command \"" + e.getCommand() + "\". Ignoring event.");
                }
            }
        } else if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
            registerDevice(e);
        } else {
            Log.e(TAG, "Unknown device id specified. Ignoring event.");
        }
    }

    private void registerDevice(Event e) {
        if (!Event.COMMAND_REGISTER.equals(e.getCommand())) {
            throw new IllegalStateException(
                    "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!");
        }
        int id = e.getId();
        Device d = new Device(id, e.getName(), e.getVendorId(), e.getProductId(),
                e.getDescriptor(), e.getReport());
        mDevices.append(id, d);
    }

    private static void error(String msg) {
        error(msg, null);
    }

    private static void error(String msg, Exception e) {
        Log.e(TAG, msg);
        if (e != null) {
            Log.e(TAG, Log.getStackTraceString(e));
        }
    }
}
