/* Copyright Statement:
 *
 * This software/firmware and related documentation ("MediaTek Software") are
 * protected under relevant copyright laws. The information contained herein
 * is confidential and proprietary to MediaTek Inc. and/or its licensors.
 * Without the prior written permission of MediaTek inc. and/or its licensors,
 * any reproduction, modification, use or disclosure of MediaTek Software,
 * and information contained herein, in whole or in part, shall be strictly prohibited.
 *
 * MediaTek Inc. (C) 2010. All rights reserved.
 *
 * BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
 * THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
 * RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER ON
 * AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
 * NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
 * SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
 * SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES TO LOOK ONLY TO SUCH
 * THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. RECEIVER EXPRESSLY ACKNOWLEDGES
 * THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES
 * CONTAINED IN MEDIATEK SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK
 * SOFTWARE RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
 * STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND
 * CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
 * AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
 * OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY RECEIVER TO
 * MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
 *
 * The following software/firmware and/or related documentation ("MediaTek Software")
 * have been modified by MediaTek Inc. All revisions are subject to any receiver's
 * applicable license agreements with MediaTek Inc.
 */

package sto.android.app.manager;

import android.net.LocalSocket;
import android.net.LocalSocketAddress;
import android.util.Log;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

public class ShellExe {

    private static final String TAG = "EM/shellexe";
    public static final String ERROR = "ERROR";
    public static final int RESULT_SUCCESS = 0;
    public static final int RESULT_FAIL = -1;
    public static final int RESULT_EXCEPTION = -2;
    private static final String OPERATION_ERROR_PREFIX = "#$ERROR^&";
    private static StringBuilder sResultBuilder = new StringBuilder("");

    /**
     * Get shell command output
     *
     * @return Shell command output
     */
    public static String getOutput() {
        return sResultBuilder.toString();
    }

    /**
     * Execute shell command
     * @param command Command string need to execute
     * @return Result
     * @throws IOException Throws when occurs #IOException
     */
    public static int execCommand(String command) throws IOException {
        return execCommand(new String[] { "sh", "-c", command });
    }

    public static int execCommand(String[] command) throws IOException {
        return execCommandOnServer(command);
    }

    public static int execCommand(String command, boolean execOnLocal) throws IOException {
        return execCommand(new String[] {"sh", "-c", command}, execOnLocal);
    }

    public static int execCommand(String[] command, boolean execOnLocal) throws IOException {
        if (execOnLocal) {
            return execCommandLocal(command);
        } else {
            return execCommandOnServer(command);
        }
    }

    /**
     * Execute shell command
     * @param command Shell command array
     * @return Result
     * @throws IOException Throws when occurs #IOException
     */
    public static int execCommandLocal(String[] command) throws IOException {
        int result = RESULT_FAIL;
        Runtime runtime = Runtime.getRuntime();
        Process proc = runtime.exec(command);
        BufferedReader bufferedReader = null;
        sResultBuilder.delete(0, sResultBuilder.length());
        try {
            bufferedReader = new BufferedReader(new InputStreamReader(proc
                    .getInputStream(), Charset.defaultCharset()));
            if (proc.waitFor() == 0) {
                String line = bufferedReader.readLine();
                if (line != null) {
                    sResultBuilder.append(line);
                    while (true) {
                        line = bufferedReader.readLine();
                        if (line == null) {
                            break;
                        } else {
                            sResultBuilder.append('\n');
                            sResultBuilder.append(line);
                        }
                    }
                }
                result = RESULT_SUCCESS;
            } else {
                Log.i(TAG, "exit value = " + proc.exitValue());
                sResultBuilder.append(ERROR);
                result = RESULT_FAIL;
            }
        } catch (InterruptedException e) {
            Log.i(TAG, "exe shell command InterruptedException: "
                    + e.getMessage());
            sResultBuilder.append(ERROR);
            result = RESULT_EXCEPTION;
        } finally {
            if (null != bufferedReader) {
                try {
                    bufferedReader.close();
                } catch (IOException e) {
                    Log.w(TAG, "close reader in finally block exception: " + e.getMessage());
                }
            }
        }
        return result;
    }

    public static int execCommandOnServer(String[] command) throws IOException {
        if (command == null || command.length == 0) {
            throw new IllegalArgumentException("Invalid shell command to execute");
        }
        StringBuilder cmdBuilder = new StringBuilder();
        int i = 0;
        // ignore specified sh, use the server default sh
        if ("sh".equals(command[0]) || command[0].endsWith("/sh")) {
            if (command.length < 3) {
                throw new IllegalArgumentException("invalid or unknown cmd to execute");
            }
            i = 2;
        }
        for (; i < command.length; i++) {
            cmdBuilder.append(command[i]);
            if (i < command.length - 1) {
                cmdBuilder.append(" ");
            }
        }
        String cmd = cmdBuilder.toString();
        cmd = cmd.trim();
        sResultBuilder.delete(0, sResultBuilder.length());
        boolean ret = true;
        AFMFunctionCallEx functionCall = new AFMFunctionCallEx();
        ret = functionCall.startCallFunctionStringReturn(AFMFunctionCallEx.FUNCTION_EM_SHELL_CMD_EXECUTION);
        if (ret) {
            functionCall.writeParamNo(1);
            functionCall.writeParamString(cmd);
            FunctionReturn funcRet = null;
            do {
                funcRet = functionCall.getNextResult();
                if (funcRet.mReturnString != null && funcRet.mReturnString.length() > 0) {
                    sResultBuilder.append(funcRet.mReturnString);
                }
            } while (funcRet.mReturnCode == AFMFunctionCallEx.RESULT_CONTINUE);
            // trim the tail newline character
            while (true) {
                int len = sResultBuilder.length();
                if (len <= 0) {
                    break;
                }
                char c = sResultBuilder.charAt(len - 1);
                if (c == '\n') {
                    sResultBuilder.delete(len - 1, len);
                } else {
                    break;
                }
            }
            String output = sResultBuilder.toString();
            if (output != null && output.startsWith(OPERATION_ERROR_PREFIX)) {
                Log.d(TAG, "error operation:" + output);
                sResultBuilder.delete(0, OPERATION_ERROR_PREFIX.length() + 1);
                return RESULT_FAIL;
            }
            return RESULT_SUCCESS;
        }
        Log.d(TAG, "Function call start fail");
        sResultBuilder.append(ERROR);
        return RESULT_FAIL;
    }

    public static String getInfo(String cmd) {
        String result = null;
        try {
            String[] cmdx = { "/system/bin/sh", "-c", cmd }; // file must
            int ret = 0;
            String backup = cmd.trim();
            if (backup.startsWith("echo")) {
                ret = ShellExe.execCommand(cmdx);;
            } else {
                ret = ShellExe.execCommand(cmdx, true);
            }
            if (0 == ret) {
                result = ShellExe.getOutput();
            } else {
                // result = "ERROR";
                result = ShellExe.getOutput();
            }

        } catch (IOException e) {
            Log.i("EM-PMU", e.toString());
            result = "ERR.JE";
        }
        return result;
    }

    static class AFMFunctionCallEx {

        private static final String TAG = "EM/functioncall";

        public static final int FUNCTION_EM_BASEBAND = 10001;
        public static final int FUNCTION_EM_FB0_IOCTL = 30001;
        public static final int FUNCTION_EM_CPU_STRESS_TEST_APMCU = 40001;
        public static final int FUNCTION_EM_CPU_STRESS_TEST_SWCODEC = 40002;
        public static final int FUNCTION_EM_CPU_STRESS_TEST_BACKUP = 40003;
        public static final int FUNCTION_EM_CPU_STRESS_TEST_THERMAL = 40004;
        public static final int FUNCTION_EM_CPU_STRESS_TEST_INIT = 40005;
        public static final int FUNCTION_EM_SENSOR_DO_CALIBRATION = 50001;
        public static final int FUNCTION_EM_SENSOR_CLEAR_CALIBRATION = 50002;
        public static final int FUNCTION_EM_SENSOR_SET_THRESHOLD = 50003;
        public static final int FUNCTION_EM_SENSOR_DO_GSENSOR_CALIBRATION = 50004;
        public static final int FUNCTION_EM_SENSOR_GET_GSENSOR_CALIBRATION = 50005;
        public static final int FUNCTION_EM_SENSOR_CLEAR_GSENSOR_CALIBRATION = 50006;
        public static final int FUNCTION_EM_SENSOR_DO_GYROSCOPE_CALIBRATION = 50007;
        public static final int FUNCTION_EM_SENSOR_GET_GYROSCOPE_CALIBRATION = 50008;
        public static final int FUNCTION_EM_SENSOR_CLEAR_GYROSCOPE_CALIBRATION = 50009;
        public static final int FUNCTION_EM_MSDC_SET_CURRENT = 70001;
        public static final int FUNCTION_EM_MSDC_GET_CURRENT = 70002;
        public static final int FUNCTION_EM_MSDC_SET30_MODE = 70003;
        public static final int FUNCTION_EM_SHELL_CMD_EXECUTION = 80001;

        public static final int RESULT_FIN = 0;
        public static final int RESULT_CONTINUE = 1;
        public static final int RESULT_IO_ERR = -1;

        private static final String ERROR = "ERROR ";

        private Client mSocket = null;

        /**
         * Prepare to call function by EM native server
         *
         * @param functionId
         *            Functional ID
         * @return True if call function success
         */
        public boolean startCallFunctionStringReturn(int functionId) {
            boolean result = false;
            mSocket = new Client();
            mSocket.startClient();
            try {
                mSocket.writeFunctionNo(String.valueOf(functionId));
                result = true;
            } catch (IOException e) {
                Log.w(TAG, "StartCallFunctionStringReturnEXP " + e.getMessage());
                result = false;
            }
            return result;
        }

        /**
         * Send parameter number
         *
         * @param number
         *            Parameters number
         * @return True if send success to server
         */
        public boolean writeParamNo(int number) {
            boolean ret = false;
            try {
                mSocket.writeParamNo(number);
                ret = true;
            } catch (IOException e) {
                ret = false;
            }
            return ret;
        }

        /**
         * Send "int" parameter to server
         *
         * @param param
         *            Parameter
         * @return True if send success to server
         */
        public boolean writeParamInt(int param) {
            boolean ret = false;
            try {
                mSocket.writeParamInt(param);
                ret = true;

            } catch (IOException e) {
                ret = false;
            }
            return ret;
        }

        /**
         * Send string to server as parameter
         *
         * @param string
         *            Parameter
         * @return True if send success to server
         */
        public boolean writeParamString(String string) {
            boolean ret = false;
            try {
                mSocket.writeParamString(string);
                ret = true;
            } catch (IOException e) {
                ret = false;
            }
            return ret;
        }

        /**
         * Get response from EM server
         *
         * @return #FunctionReturn returnCode=0 if socket close normally
         *         returnCode=1 has more data to be read. returnCode = -1 close
         *         abnormally.
         */
        public FunctionReturn getNextResult() {
            FunctionReturn ret = new FunctionReturn();
            try {
                ret.mReturnString = mSocket.read();
                if (ret.mReturnString.isEmpty()) {
                    ret.mReturnCode = RESULT_FIN;
                    endCallFunction();
                } else {
                    ret.mReturnCode = RESULT_CONTINUE;
                }
            } catch (EOFException e) {
                endCallFunction();
                ret.mReturnCode = RESULT_FIN;
                ret.mReturnString = "";
            } catch (IOException e) {
                endCallFunction();
                ret.mReturnCode = RESULT_IO_ERR;
                ret.mReturnString = ERROR + e.getMessage();
            }
            return ret;
        }

        /**
         * Stop connection with EM server
         */
        private void endCallFunction() {
            mSocket.stopClient();
        }

    }

    static class Client {

        private static final String TAG = "EM/client";
        public static final int PARAM_TYPE_STRING = 1;
        public static final int PARAM_TYPE_INT = 2;
        private static final int PARAM_INT_LENGTH = 4;
        private static final String ERROR_NO_INIT = "NOT INIT";
        private static final String ERROR_PARAM_NUM = "param < 0";
        private static final int STATUS_SUCCESS = 0;
        private static final int STATUS_ERROR = -1;
        private static final int MAX_BUFFER_SIZE = 1024;
        private static final String EMPTY = "";
        private static final String EM_SERVER_NAME = "EngineerModeServer";

        private LocalSocket mSocket = null;
        private DataInputStream mInputStream = null;
        private DataOutputStream mOutputStream = null;
        private int mStatus = STATUS_SUCCESS;

        /**
         * Connect to EM native server
         */
        public void startClient() {
            try {
                // mSocket = new Socket("127.0.0.1", 37121);
                // mSocket(10000);
                mSocket = new LocalSocket();
                // LocalSocketAddress.Namespace.FILESYSTEM
                mSocket.connect(new LocalSocketAddress(EM_SERVER_NAME));
                mInputStream = new DataInputStream(mSocket.getInputStream());
                mOutputStream = new DataOutputStream(mSocket.getOutputStream());
                mStatus = STATUS_SUCCESS;
            } catch (IOException e) {
                Log.w(TAG, "startclient IOException " + e.getMessage());
                mStatus = STATUS_ERROR;
            }
        }

        /**
         * Read response string from EM server
         *
         * @return Response string
         * @throws IOException
         *             Input stream exception
         */
        synchronized String read() throws IOException {
            if (STATUS_ERROR == mStatus || null == mInputStream) {
                throw new IOException(ERROR_NO_INIT);
            }
            String result = null;
            int len = mInputStream.readInt();
            if (len > MAX_BUFFER_SIZE) {
                len = MAX_BUFFER_SIZE;
            }
            byte bb[] = new byte[len];
            int x = mInputStream.read(bb, 0, len);
            if (-1 == x) {
                result = EMPTY;
            } else {
                result = new String(bb, Charset.defaultCharset());
            }
            return result;
        }

        /**
         * Send function number to EM server
         *
         * @param functionNo
         *            Function ID
         * @throws IOException
         *             Output stream exception
         */
        synchronized void writeFunctionNo(String functionNo) throws IOException {
            if (STATUS_ERROR == mStatus || null == mOutputStream) {
                throw new IOException(ERROR_NO_INIT);
            }
            if (functionNo == null || functionNo.length() == 0) {
                return;
            }
            mOutputStream.writeInt(functionNo.length());
            mOutputStream.write(functionNo.getBytes(Charset.defaultCharset()), 0, functionNo.length());
            return;
        }

        /**
         * Send parameter number to EM server
         *
         * @param paramNum
         *            Parameter total count
         * @throws IOException
         *             Output stream exception
         */
        synchronized void writeParamNo(int paramNum) throws IOException {
            if (STATUS_ERROR == mStatus || null == mOutputStream) {
                throw new IOException(ERROR_NO_INIT);
            }
            if (paramNum < 0) {
                throw new IOException(ERROR_PARAM_NUM);
            }
            mOutputStream.writeInt(paramNum);
        }

        /**
         * Send parameter to EM server
         *
         * @param param
         *            Parameter
         * @throws IOException
         *             Output stream exception
         */
        synchronized void writeParamInt(int param) throws IOException {
            if (STATUS_ERROR == mStatus || null == mOutputStream) {
                throw new IOException(ERROR_NO_INIT);
            }
            mOutputStream.writeInt(PARAM_TYPE_INT);
            mOutputStream.writeInt(PARAM_INT_LENGTH);
            mOutputStream.writeInt(param);
        }

        /**
         * Send string to EM server as parameter
         *
         * @param param
         *            Parameter
         * @throws IOException
         *             Output stream exception
         */
        synchronized void writeParamString(String param) throws IOException {
            if (STATUS_ERROR == mStatus || null == mOutputStream) {
                throw new IOException(ERROR_NO_INIT);
            }
            mOutputStream.writeInt(PARAM_TYPE_STRING);
            mOutputStream.writeInt(param.length());
            mOutputStream.write(param.getBytes(Charset.defaultCharset()), 0, param.length());
        }

        /**
         * Stop connection with EM server
         */
        public void stopClient() {
            if (null == mInputStream || null == mOutputStream || null == mSocket) {
                return;
            }
            try {
                mOutputStream.close();
                mInputStream.close();
                mSocket.close();
            } catch (IOException e) {
                Log.w(TAG, "stop client IOException: " + e.getMessage());
            }
        }

    }

    static class FunctionReturn {
        public int mReturnCode = -1;
        public String mReturnString = "";
    }

}
