/*
 * Copyright (C) 2013 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.launcher3;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;

public class WeightWatcher extends LinearLayout {
    private static final int RAM_GRAPH_RSS_COLOR = 0xFF990000;
    private static final int RAM_GRAPH_PSS_COLOR = 0xFF99CC00;
    private static final int TEXT_COLOR = 0xFFFFFFFF;
    private static final int BACKGROUND_COLOR = 0xc0000000;

    private static final int UPDATE_RATE = 5000;

    private static final int MSG_START = 1;
    private static final int MSG_STOP = 2;
    private static final int MSG_UPDATE = 3;

    static int indexOf(int[] a, int x) {
        for (int i=0; i<a.length; i++) {
            if (a[i] == x) return i;
        }
        return -1;
    }

    Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message m) {
            switch (m.what) {
                case MSG_START:
                    mHandler.sendEmptyMessage(MSG_UPDATE);
                    break;
                case MSG_STOP:
                    mHandler.removeMessages(MSG_UPDATE);
                    break;
                case MSG_UPDATE:
                    int[] pids = mMemoryService.getTrackedProcesses();

                    final int N = getChildCount();
                    if (pids.length != N) initViews();
                    else for (int i=0; i<N; i++) {
                        ProcessWatcher pw = ((ProcessWatcher) getChildAt(i));
                        if (indexOf(pids, pw.getPid()) < 0) {
                            initViews();
                            break;
                        }
                        pw.update();
                    }
                    mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
                    break;
            }
        }
    };
    private MemoryTracker mMemoryService;

    public WeightWatcher(Context context, AttributeSet attrs) {
        super(context, attrs);

        ServiceConnection connection = new ServiceConnection() {
            public void onServiceConnected(ComponentName className, IBinder service) {
                mMemoryService = ((MemoryTracker.MemoryTrackerInterface)service).getService();
                initViews();
            }

            public void onServiceDisconnected(ComponentName className) {
                mMemoryService = null;
            }
        };
        context.bindService(new Intent(context, MemoryTracker.class),
                connection, Context.BIND_AUTO_CREATE);

        setOrientation(LinearLayout.VERTICAL);

        setBackgroundColor(BACKGROUND_COLOR);
    }

    public void initViews() {
        removeAllViews();
        int[] processes = mMemoryService.getTrackedProcesses();
        for (int i=0; i<processes.length; i++) {
            final ProcessWatcher v = new ProcessWatcher(getContext());
            v.setPid(processes[i]);
            addView(v);
        }
    }

    public WeightWatcher(Context context) {
        this(context, null);
    }

    @Override
    public void onAttachedToWindow() {
        super.onAttachedToWindow();
        mHandler.sendEmptyMessage(MSG_START);
    }

    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mHandler.sendEmptyMessage(MSG_STOP);
    }

    public class ProcessWatcher extends LinearLayout {
        GraphView mRamGraph;
        TextView mText;
        int mPid;
        private MemoryTracker.ProcessMemInfo mMemInfo;

        public ProcessWatcher(Context context) {
            this(context, null);
        }

        public ProcessWatcher(Context context, AttributeSet attrs) {
            super(context, attrs);

            final float dp = getResources().getDisplayMetrics().density;

            mText = new TextView(getContext());
            mText.setTextColor(TEXT_COLOR);
            mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, 10 * dp);
            mText.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);

            final int p = (int)(2*dp);
            setPadding(p, 0, p, 0);

            mRamGraph = new GraphView(getContext());

            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                    0,
                    (int)(14 * dp),
                    1f
            );

            addView(mText, params);
            params.leftMargin = (int)(4*dp);
            params.weight = 0f;
            params.width = (int)(200 * dp);
            addView(mRamGraph, params);
        }

        public void setPid(int pid) {
            mPid = pid;
            mMemInfo = mMemoryService.getMemInfo(mPid);
            if (mMemInfo == null) {
                Log.v("WeightWatcher", "Missing info for pid " + mPid + ", removing view: " + this);
                initViews();
            }
        }

        public int getPid() {
            return mPid;
        }

        public String getUptimeString() {
            long sec = mMemInfo.getUptime() / 1000;
            StringBuilder sb = new StringBuilder();
            long days = sec / 86400;
            if (days > 0) {
                sec -= days * 86400;
                sb.append(days);
                sb.append("d");
            }

            long hours = sec / 3600;
            if (hours > 0) {
                sec -= hours * 3600;
                sb.append(hours);
                sb.append("h");
            }

            long mins = sec / 60;
            if (mins > 0) {
                sec -= mins * 60;
                sb.append(mins);
                sb.append("m");
            }

            sb.append(sec);
            sb.append("s");
            return sb.toString();
        }

        public void update() {
            //Log.v("WeightWatcher.ProcessWatcher",
            //        "MSG_UPDATE pss=" + mMemInfo.currentPss);
            mText.setText("(" + mPid
                          + (mPid == android.os.Process.myPid()
                                ? "/A"  // app
                                : "/S") // service
                          + ") up " + getUptimeString()
                          + " P=" + mMemInfo.currentPss
                          + " U=" + mMemInfo.currentUss
                          );
            mRamGraph.invalidate();
        }

        public class GraphView extends View {
            Paint pssPaint, ussPaint, headPaint;

            public GraphView(Context context, AttributeSet attrs) {
                super(context, attrs);

                pssPaint = new Paint();
                pssPaint.setColor(RAM_GRAPH_PSS_COLOR);
                ussPaint = new Paint();
                ussPaint.setColor(RAM_GRAPH_RSS_COLOR);
                headPaint = new Paint();
                headPaint.setColor(Color.WHITE);
            }

            public GraphView(Context context) {
                this(context, null);
            }

            @Override
            public void onDraw(Canvas c) {
                int w = c.getWidth();
                int h = c.getHeight();

                if (mMemInfo == null) return;

                final int N = mMemInfo.pss.length;
                final float barStep = (float) w / N;
                final float barWidth = Math.max(1, barStep);
                final float scale = (float) h / mMemInfo.max;

                int i;
                float x;
                for (i=0; i<N; i++) {
                    x = i * barStep;
                    c.drawRect(x, h - scale * mMemInfo.pss[i], x + barWidth, h, pssPaint);
                    c.drawRect(x, h - scale * mMemInfo.uss[i], x + barWidth, h, ussPaint);
                }
                x = mMemInfo.head * barStep;
                c.drawRect(x, 0, x + barWidth, h, headPaint);
            }
        }
    }
}
