public class StringConstants {

    //--BASE URLS--
    public static final String BASE_SERVER_URL = "sagecell.sagemath.org";

    //--PATHS--
    public static final String PATH_KERNEL = "kernel";
    public static final String PATH_IOPUB = "iopub";
    public static final String PATH_SHELL = "shell";
    public static final String PATH_PERMALINK = "permalink";

    //--SCHEMES--
    public static final String SCHEME_HTTPS = "https";
    public static final String SCHEME_HTTP = "http";

    public static final String ARG_PROGRESS_START = "progress-start";
    public static final String ARG_PROGRESS_END = "progress-end";

    public static final String FILE_IMAGE_OUTPUT = "imageOutput.png";

    public static final String ID = "id";
    public static final String FLAG_INPUT_EMPTY = "inputEmptyFlag";

    public static final String MATHJAX_CONFIG = "<script type=\"text/x-mathjax-config\">\n" +
            "  MathJax.Hub.Config({tex2jax: {inlineMath: [['$','$'], ['\\\\(','\\\\)']]}});\n" +
            "</script>";
    public static final String MATHJAX_CDN = "<script type=\"text/javascript\"\n" +
            "  src=\"https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML\">\n" +
            "</script>";
    public static final String IMAGE_STYLE = "<style>img{display: inline; height: auto; max-width: 100%;}</style>";

} public class SageSQLiteOpenHelper extends SQLiteOpenHelper {
    private static final String TAG = "SageDroid:SageSQLiteOpenHelper";

    private static SageSQLiteOpenHelper instance = null;

    private static final int DATABASE_VERSION = 1;
    private static final String DATABASE_NAME = "sagedroid.db";

    private Context context;

    private List<Group> currentGroups;

    static {
        cupboard().register(Cell.class);
        cupboard().register(Group.class);
        cupboard().register(Insert.class);
    }

    public static SageSQLiteOpenHelper getInstance(Context context) {

        if (instance == null) {
            instance = new SageSQLiteOpenHelper(context.getApplicationContext());
        }
        return instance;
    }

    private SageSQLiteOpenHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
        this.context = context.getApplicationContext();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.d(TAG, "Creating tables");
        cupboard().withDatabase(db).createTables();
        InputStream inputStream = context.getResources().openRawResource(R.raw.cell_collection);
        FileXMLParser parser = new FileXMLParser();
        parser.parse(inputStream);
        List<Cell> initialCells = parser.getIntitalCells();
        List<Group> initialGroups = parser.getInitialGroups();
        addInitialGroups(initialGroups, db);
        addInitialCells(initialCells, db);
        addInitialInserts(db);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //Not sure if this is alright...
        //cupboard().withDatabase(getWritableDatabase()).dropAllTables();
        Log.i(TAG, "In onUpgrade");
        onCreate(getWritableDatabase());
    }

    public Long addCell(Cell cell) {
        Log.i(TAG, "Adding cell: " + cell);
        try {
            currentGroups = getGroups();
            if (currentGroups.contains(cell.getGroup())) {
                cell.setGroup(currentGroups.get(currentGroups.indexOf(cell.getGroup())));
            }
            return cupboard().withDatabase(getWritableDatabase()).put(cell);
        } catch (Exception e) {
            Log.e(TAG, "Unable to add cell: " + e);
        }
        return null;
    }

    private void addInitialInserts(SQLiteDatabase db) {

        Insert insert1 = new Insert();
        insert1.setInsertDescription("List Comprehension");
        insert1.setInsertText("[ i for i in range(0,10) ]");
        insert1.setFavorite(false);

        Insert insert2 = new Insert();
        insert2.setInsertDescription("For Loop");
        insert2.setInsertText("for i in range(0,10):");
        insert2.setFavorite(false);
        cupboard().withDatabase(db).put(insert1, insert2);
    }

    public void addInitialGroups(List<Group> groups, SQLiteDatabase db) {
        Log.i(TAG, "Adding Initial Groups" + groups.toString());
        try {

            db.beginTransaction();
            for (Group group : groups) {
                cupboard().withDatabase(db).put(group);
            }
            db.setTransactionSuccessful();
        } catch (Exception e) {
            Log.e(TAG, e + "");
        } finally {
            if (db != null) {
                db.endTransaction();
            }
        }
    }

    public void addInitialCells(List<Cell> cells, SQLiteDatabase db) {
        try {
            currentGroups = getGroups(db);
            db.beginTransaction();
            for (Cell cell : cells) {
                if (!currentGroups.contains(cell.getGroup())) {
                    cupboard().withDatabase(db).put(cell);
                } else {
                    Group group = currentGroups.get(currentGroups.indexOf(cell.getGroup()));
                    cell.setGroup(group);
                    cupboard().withDatabase(db).put(cell);
                }
            }
            db.setTransactionSuccessful();
        } catch (Exception e) {
            Log.e(TAG, e + "");
        } finally {
            if (db != null) {
                db.endTransaction();
            }
        }
    }

    public List<Cell> getCellsWithGroup(Group group) {

        List<Cell> list = null;
        try {
            list = cupboard()
                    .withDatabase(getReadableDatabase())
                    .query(Cell.class)
                    .withSelection("cellGroup = ?", String.valueOf(group.getId()))
                    .orderBy("title asc")
                    .query()
                    .list();
        } catch (Exception e) {
            Log.e(TAG, e + "");
        }
        list = sortCellsByFavorite(list);
        return list;
    }

    public List<Cell> getQueryCells(Group group, String titleQuery) {
        String queryFormat = "%" + titleQuery + "%";
        List<Cell> list = null;
        try {
            list = cupboard()
                    .withDatabase(getReadableDatabase())
                    .query(Cell.class)
                    .withSelection("cellGroup = ? AND title LIKE ?", String.valueOf(group.getId()), queryFormat)
                    .orderBy("title asc")
                    .query()
                    .list();
        } catch (Exception e) {
            Log.e(TAG, e + "");
        }
        list = sortCellsByFavorite(list);
        return list;
    }

    public List<Group> getGroups(SQLiteDatabase db) {
        List<Group> list = null;
        try {
            list = cupboard()
                    .withDatabase(db)
                    .query(Group.class)
                    .orderBy("cellGroup asc")
                    .distinct()
                    .query()
                    .list();
        } catch (Exception e) {
            Log.e(TAG, e + "");
        }
        return list;
    }

    public List<Group> getGroups() {

        List<Group> list = null;
        try {
            list = cupboard()
                    .withDatabase(getReadableDatabase())
                    .query(Group.class)
                    .orderBy("cellGroup asc")
                    .distinct()
                    .query()
                    .list();
        } catch (Exception e) {
            Log.e(TAG, e + "");
        }
        return list;
    }

    public void addGroup(Group group) {
        try {
            //If the group is duplicate, discard
            List<Group> currentGroups = getGroups();
            if (!currentGroups.contains(group))
                cupboard().withDatabase(getWritableDatabase()).put(group);
        } catch (Exception e) {
            Log.e(TAG, e + "");
        }
    }

    public void deleteGroup(Group group) {
        try {
            //Delete all cells with this group

            List<Cell> cells = getCellsWithGroup(group);
            Log.i(TAG, "Deleting " + cells.size() + "cells and group: " + group.getCellGroup());
            deleteCells(cells);

            //Delete the group
            cupboard().withDatabase(getWritableDatabase()).delete(group);
        } catch (Exception e) {
            Log.e(TAG, e + "");
        }
    }

    public void saveGroup(Group group) {
        try {
            cupboard().withDatabase(getWritableDatabase()).put(group);
        } catch (Exception e) {
            Log.e(TAG, e + "");
        }
    }

    public List<Insert> getQueryInserts(String query) {
        String queryInsert = "%" + query + "%";
        List<Insert> inserts = null;

        try {
            inserts = cupboard()
                    .withDatabase(getReadableDatabase())
                    .query(Insert.class)
                    .withSelection("insertDescription LIKE ?", queryInsert)
                    .orderBy("insertDescription asc")
                    .query()
                    .list();
        } catch (Exception e) {
            Log.e(TAG, e + "");
        }
        return inserts;
    }

    public List<Insert> getInserts() {

        List<Insert> inserts = null;

        try {
            inserts = cupboard()
                    .withDatabase(getReadableDatabase())
                    .query(Insert.class)
                    .orderBy("insertDescription asc")
                    .query()
                    .list();
        } catch (Exception e) {
            Log.e(TAG, e + "");
        }

        if (inserts != null) {
            inserts = sortInsertsByFavorite(inserts);
        }

        return inserts;
    }

    public void addInsert(Insert insert) {
        try {
            cupboard().withDatabase(getWritableDatabase()).put(insert);
        } catch (Exception e) {
            Log.e(TAG, e + "");
        }
    }

    public boolean addInsert(List<Insert> inserts) {
        SQLiteDatabase db = getWritableDatabase();
        try {
            db.beginTransaction();
            for (Insert insert : inserts) {
                cupboard().withDatabase(db).put(inserts);
            }
            db.setTransactionSuccessful();
        } catch (Exception e) {
            Log.e(TAG, e + "");
            return false;
        } finally {
            db.endTransaction();
            return true;
        }
    }

    public void deleteInsert(Insert insert) {
        try {
            cupboard().withDatabase(getWritableDatabase()).delete(insert);
        } catch (Exception e) {
            Log.e(TAG, e + "");
        }
    }

    public boolean deleteInsert(List<Insert> inserts) {
        SQLiteDatabase db = getWritableDatabase();

        try {
            db.beginTransaction();
            for (Insert insert : inserts) {
                Log.i(TAG, "Deleting:" + insert);
                cupboard().withDatabase(db).delete(insert);
            }
            db.setTransactionSuccessful();
        } catch (Exception e) {
            Log.e(TAG, e + "");
            return false;
        } finally {
            db.endTransaction();
        }

        return true;
    }

    private List<Cell> sortCellsByFavorite(List<Cell> cells) {
        ArrayList<Cell> favs = new ArrayList<>();
        ArrayList<Cell> others = new ArrayList<>();

        for (Cell cell : cells) {
            if (cell.isFavorite())
                favs.add(cell);
            else
                others.add(cell);
        }

        favs.addAll(others);
        return favs;
    }

    private List<Insert> sortInsertsByFavorite(List<Insert> inserts) {
        ArrayList<Insert> favs = new ArrayList<>();
        ArrayList<Insert> others = new ArrayList<>();

        for (Insert insert : inserts) {
            if (insert.isFavorite()) {
                favs.add(insert);
            } else {
                others.add(insert);
            }
        }

        favs.addAll(others);
        return favs;
    }

    public void saveEditedCell(Cell cell) {

        currentGroups = getGroups();
        if (currentGroups.contains(cell.getGroup())) {
            cell.setGroup(currentGroups.get(currentGroups.indexOf(cell.getGroup())));
        }
        cupboard().withDatabase(getWritableDatabase()).put(cell);

    }

    public void saveEditedCells(List<Cell> cells) {
        for (Cell cell : cells) {
            saveEditedCell(cell);
        }
    }

    public Cell getCellbyID(Long id) {
        return cupboard().withDatabase(getReadableDatabase()).get(Cell.class, id);
    }

    public void deleteCell(Cell cell) {
        cupboard().withDatabase(getWritableDatabase()).delete(cell);
    }

    public boolean deleteCells(List<Cell> cells) {
        SQLiteDatabase db = getWritableDatabase();

        try {
            db.beginTransaction();
            for (Cell cell : cells) {
                cupboard().withDatabase(getWritableDatabase()).delete(cell);
            }
            db.setTransactionSuccessful();
        } catch (Exception e) {
            Log.e(TAG, e + "");
            return false;
        } finally {
            db.endTransaction();
        }

        return true;
    }

} public class DeleteCellDialogFragment extends BaseDeleteDialogFragment {

    private static final String TAG = "SageDroid:DeleteCellDialogFragment";

    private static final String ARG_CELL = "cell";

    public static DeleteCellDialogFragment newInstance(ArrayList<Cell> cells) {

        DeleteCellDialogFragment fragment = new DeleteCellDialogFragment();
        Bundle args = new Bundle();
        args.putParcelableArrayList(ARG_CELL, cells);

        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        final ArrayList<Cell> cells = getArguments().getParcelableArrayList(ARG_CELL);

        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setIcon(getResources().getDrawable(R.drawable.ic_alert_red));
        builder.setTitle(getString(R.string.delete_dialog_title));
        builder.setMessage(getResources().getQuantityString(R.plurals.delete_n_cells, cells.size(), cells.size())
                + "\n\n" + getString(R.string.delete_warning));
        builder.setPositiveButton(R.string.discard, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                listener.onDelete();
            }
        });
        builder.setNegativeButton(R.string.cancel, null);
        return builder.create();
    }
} public class InsertDialogFragment extends DialogFragment {
    private static final String TAG = "SageDroid:NewInsertDialogFragment";

    private static final String ARG_INSERT = "insert";

    public interface OnInsertCreateListener {
        public void onInsertCreated();
    }

    private OnInsertCreateListener listener;

    public void setOnInsertCreateListener(OnInsertCreateListener listener) {
        this.listener = listener;
    }

    private View dialogView;
    private Insert insert;
    private EditText insertText, insertDescription;
    private CheckBox favoriteCheck;

    public static InsertDialogFragment newInstance(Insert insert) {
        InsertDialogFragment fragment = new InsertDialogFragment();

        Bundle args = new Bundle();
        args.putParcelable(ARG_INSERT, insert);

        fragment.setArguments(args);

        return fragment;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        insert = getArguments().getParcelable(ARG_INSERT);

        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        dialogView = getActivity().getLayoutInflater().inflate(R.layout.dialog_new_insert, null);
        builder.setView(dialogView);

        insertText = (EditText) dialogView.findViewById(R.id.insertText);
        insertDescription = (EditText) dialogView.findViewById(R.id.insertDesc);
        favoriteCheck = (CheckBox) dialogView.findViewById(R.id.insertFavoriteCheck);

        if (insert != null) {
            builder.setTitle(R.string.dialog_edit_insert_title);
            insertText.setText(insert.getInsertText());
            insertDescription.setText(insert.getInsertDescription());
            favoriteCheck.setChecked(insert.isFavorite());
        } else {
            builder.setTitle(R.string.dialog_new_insert_title);
        }

        builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {

            }
        });
        builder.setNegativeButton(R.string.cancel, null);
        return builder.create();
    }

    private boolean areFieldsEmpty() {
        if (TextUtils.isEmpty(insertText.getText()) || TextUtils.isEmpty(insertDescription.getText())) {
            return true;
        }
        return false;
    }

    @Override
    public void onStart() {
        super.onStart();
        AlertDialog d = (AlertDialog) getDialog();

        if (d != null) {
            Button positiveButton = d.getButton(Dialog.BUTTON_POSITIVE);
            positiveButton.setOnClickListener(new View.OnClickListener() {
                @TargetApi(Build.VERSION_CODES.HONEYCOMB)
                @Override
                public void onClick(View v) {
                    if (areFieldsEmpty()) {
                        if (AnimationHelper.isIcsOrAbove()) {
                            AnimationHelper.Nope(dialogView).start();
                        } else {
                            AnimationHelper.SupportNope(dialogView).start();
                        }
                    } else {
                        if (insert == null) {
                            insert = new Insert();
                        } else {
                            insert = new Insert(insert.getId());
                        }
                        insert.setFavorite(favoriteCheck.isChecked());
                        insert.setInsertText(insertText.getText().toString());
                        insert.setInsertDescription(insertDescription.getText().toString());
                        SageSQLiteOpenHelper.getInstance(getActivity()).addInsert(insert);
                        if (listener != null) {
                            listener.onInsertCreated();
                        }
                        dismiss();
                    }
                }
            });
        }
    }
} public class InsertSpinnerDialogFragment extends DialogFragment {
    private static final String TAG = "SageDroid:InsertSpinnerDialogFragment";

    public interface OnInsertSelectedListener {
        public void onInsertSelected(Insert insert);
    }

    private OnInsertSelectedListener listener;

    public void setOnInsertSelectedListener(OnInsertSelectedListener listener) {
        this.listener = listener;
    }

    public static InsertSpinnerDialogFragment newInstance() {
        return new InsertSpinnerDialogFragment();
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        final InsertsAdapter adapter = new InsertsAdapter(getActivity(), false);

        builder.setTitle(R.string.dialog_insert_title);
        builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (listener != null) {
                    listener.onInsertSelected((Insert) adapter.getItem(which));
                }
                dismiss();
            }
        });
        builder.setPositiveButton(R.string.ok, null);
        builder.setNegativeButton(R.string.cancel, null);

        return builder.create();

    }
} public class ShareDialogFragment extends DialogFragment {
    private static final String TAG = "SageDroid:ShareDialogFragment";

    public static interface OnRequestOutputListener {
        public void onRequestOutput();
    }

    private OnRequestOutputListener listener;

    private String shareUrl;
    private int selected = -1;

    private static final String ARG_URL = "shareURL";

    public static ShareDialogFragment getInstance(String permalinkUrl) {
        ShareDialogFragment fragment = new ShareDialogFragment();
        Bundle args = new Bundle();
        args.putString(ARG_URL, permalinkUrl);

        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onPause() {
        super.onPause();
        BusProvider.getInstance().register(this);
    }

    @Override
    public void onResume() {
        super.onResume();
        BusProvider.getInstance().register(this);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        listener = (OnRequestOutputListener) activity;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        listener = null;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        shareUrl = getArguments().getString(ARG_URL);

        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setTitle(R.string.dialog_share_title);
        builder.setSingleChoiceItems(getResources().getStringArray(R.array.dialog_share_choices), -1, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                selected = which;
            }
        });
        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
            @TargetApi(Build.VERSION_CODES.HONEYCOMB)
            @SuppressWarnings("deprecation")
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Intent intent;
                switch (selected) {
                    case 0:
                        intent = new Intent(Intent.ACTION_VIEW, Uri.parse(shareUrl));
                        startActivity(intent);
                        dismiss();
                        break;

                    case 1:
                        intent = new Intent(Intent.ACTION_SEND);
                        intent.setType("text/plain");
                        intent.putExtra(Intent.EXTRA_TEXT, shareUrl);
                        startActivity(intent);
                        dismiss();
                        break;

                    case 2:

                        int sdk = Build.VERSION.SDK_INT;
                        if (sdk >= Build.VERSION_CODES.HONEYCOMB) {
                            ClipboardManager manager = (ClipboardManager) getActivity()
                                    .getSystemService(Context.CLIPBOARD_SERVICE);
                            ClipData clipData = ClipData.newPlainText("shareUrl", shareUrl);
                            manager.setPrimaryClip(clipData);
                        } else {
                            android.text.ClipboardManager clipboard = (android.text.ClipboardManager) getActivity()
                                    .getSystemService(Context.CLIPBOARD_SERVICE);
                            clipboard.setText(shareUrl);
                        }
                        Toast.makeText(getActivity(), R.string.toast_link_copied, Toast.LENGTH_SHORT).show();
                        dismiss();
                        break;

                    case 3:
                        listener.onRequestOutput();
                        break;

                }
            }
        });
        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dismiss();
            }
        });
        return builder.create();
    }
} public class AsyncTaskFragment extends Fragment {
    private static final String TAG = "SageDroid:AsyncTaskFragment";

    public static interface ServerCallbacks {
        public void onReply(BaseReply reply);

        public void onComputationFinished();
    }

    private ServerCallbacks callBacks;

    private static final String HEADER_ACCEPT_ENCODING = "Accept_Encoding";
    private static final String HEADER_TOS = "accepted_tos";
    private static final String VALUE_IDENTITY = "identity";
    private static final String VALUE_CODE = "code";

    private OkHttpClient httpClient;
    private CookieManager cookieManager;
    private Gson gson;
    private SageAsyncTask asyncTask;

    private WebSocketClient shellClient, ioPubClient;

    private String queryCode;
    private String kernelID;
    private String permalinkURL;

    private boolean isInteractInput = false;

    Request currentExecuteRequest;

    public static AsyncTaskFragment getInstance() {
        return new AsyncTaskFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
        init();
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        BusProvider.getInstance().register(this);
        callBacks = (ServerCallbacks) activity;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        BusProvider.getInstance().unregister(this);
        callBacks = null;
    }

    public void query(String sageInput) {
        //Initialize a new ExecuteRequest object
        currentExecuteRequest = new Request(sageInput);
        Log.i(TAG, "Creating new ExecuteRequest: " + gson.toJson(currentExecuteRequest));
        queryCode = gson.toJson(currentExecuteRequest);
        asyncTask = new SageAsyncTask();
        asyncTask.execute(currentExecuteRequest);

    }

    private void init() {
        httpClient = new OkHttpClient();
        cookieManager = CookieManagerProvider.getInstance();
        httpClient.setCookieHandler(cookieManager);
        gson = new Gson();
    }

    public void closeWebSockets() {
        if (shellClient != null && ioPubClient != null) {
            shellClient.disconnect();
            ioPubClient.disconnect();
        }
        Log.i(TAG, "Sockets closed");
    }

    public void cancel() {
        if (asyncTask != null) {
            asyncTask.cancel(true);
        }
        closeWebSockets();
    }

    public URI getShareURI() {
        URI shareURI = URI.create(permalinkURL);

        if (shareURI != null)
            return shareURI;

        //TODO Error Handler msg here
        return null;
    }

    private PermalinkResponse sendPermalinkRequest(Request request) throws IOException {

        PermalinkResponse permalinkResponse;

        String url = UrlUtils.getPermalinkURL();

        RequestBody formBody = new FormEncodingBuilder()
                .add(VALUE_CODE, request.getContent().getCode())
                .build();

        com.squareup.okhttp.Request permalinkRequest = new com.squareup.okhttp.Request.Builder()
                .url(url)
                .post(formBody)
                .build();

        Response response = httpClient.newCall(permalinkRequest).execute();

        if (!response.isSuccessful())
            BusProvider.getInstance().post(new ServerDisconnectEvent(DISCONNECT_HTTP_ERROR));

        permalinkResponse = gson.fromJson(response.body().charStream(), PermalinkResponse.class);

        Log.i(TAG, "Permalink Response" + gson.toJson(permalinkResponse));

        return permalinkResponse;
    }

    private WebSocketResponse sendInitialRequest() throws IOException {

        WebSocketResponse webSocketResponse;

        String url = UrlUtils.getInitialKernelURL();

        RequestBody formBody = new FormEncodingBuilder()
                .add(HEADER_ACCEPT_ENCODING, VALUE_IDENTITY)
                .add(HEADER_TOS, "true")
                .build();

        com.squareup.okhttp.Request initialRequest = new com.squareup.okhttp.Request.Builder()
                .url(url)
                .post(formBody)
                .build();

        Response response = httpClient.newCall(initialRequest).execute();

        if(!response.isSuccessful())
            BusProvider.getInstance().post(new ServerDisconnectEvent(DISCONNECT_HTTP_ERROR));

        webSocketResponse = gson.fromJson(response.body().charStream(), WebSocketResponse.class);

        Log.i(TAG, "Received Websocket Response: " + gson.toJson(webSocketResponse));

        Log.i(TAG, "Cookies" + Arrays.asList(cookieManager.getCookieStore().getCookies()));

        return webSocketResponse;

    }

    public void addReply(BaseReply reply) {

        Log.i(TAG, "Adding Reply:" + reply.getStringMessageType());

        if (reply instanceof ImageReply) {
            ImageReply imageReply = (ImageReply) reply;
            imageReply.setKernelID(kernelID);
            if (callBacks != null)
                callBacks.onReply(imageReply);
        } else if (reply instanceof InteractReply) {
            Log.i(TAG, "Reply is Interact, calling onSageInteractListener");
            isInteractInput = true;
            if (callBacks != null)
                callBacks.onReply(reply);
        } else if (reply instanceof StatusReply) {
            //If the reply is a status with idle/dead execution state, a computation
            //has finished or terminated, inform the SageActivity
            StatusReply statusReply = (StatusReply) reply;
            if (statusReply.getContent().getExecutionState() == ExecutionState.IDLE
                    || statusReply.getContent().getExecutionState() == ExecutionState.DEAD) {
                if (callBacks != null) {
                    callBacks.onReply(reply);
                    callBacks.onComputationFinished();
                }
            }
        } else if (reply instanceof PythonInputReply) {
            if (((PythonInputReply) reply).isInteractUpdateReply()) {
                BusProvider.getInstance().post(new InteractUpdateEvent(null, null, null));
            }
        } else {
            Log.i(TAG, "Reply to current execute request");
            callBacks.onReply(reply);
        }
    }

    public String formatInteractUpdate(String interactID, String name, String value) {
        String template = "sys._sage_.update_interact(\"%s\",\"%s\",%s)";
        return String.format(template, interactID, name, value);
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onInteractUpdate(InteractUpdateEvent event) {
        if (event.getReply() != null) {
            Log.i(TAG, "UPDATING INTERACT VARIABLE: " + event.getVarName());
            Log.i(TAG, "UPDATED INTERACT VALUE: " + event.getValue().toString());

            String interactID = event.getReply().getContent().getData().getInteract().getNewInteractID();
            String sageInput = formatInteractUpdate(interactID, event.getVarName(), event.getValue().toString());
            Log.i(TAG, "Updating Interact: " + sageInput);

            currentExecuteRequest = new Request(sageInput, event.getReply().getHeader().getSession());

            Log.i(TAG, "Sending Interact Update Request:" + gson.toJson(currentExecuteRequest));
            shellClient.send(gson.toJson(currentExecuteRequest));
        }
    }

    private void parseResponses(Pair<BaseResponse, BaseResponse> responses) {
        BaseResponse firstResponse = responses.first;
        BaseResponse secondResponse = responses.second;
        if (firstResponse instanceof PermalinkResponse) {
            permalinkURL = ((PermalinkResponse) firstResponse).getQueryURL();
            BusProvider.getInstance().post(new ShareAvailableEvent(permalinkURL));
        }
        if (secondResponse instanceof WebSocketResponse) {
            if (((WebSocketResponse) secondResponse).isValidResponse()) {
                Log.d(TAG, "Response is valid");
                //Cache to avoid huge lengths
                WebSocketResponse response = (WebSocketResponse) secondResponse;
                kernelID = response.getKernelID();
                String shellURL, ioPubURL;

                shellURL = UrlUtils.getShellURL(response.getKernelID(), response.getWebSocketURL());
                ioPubURL = UrlUtils.getIoPubURL(response.getKernelID(), response.getWebSocketURL());
                setupWebSockets(shellURL, ioPubURL, shellCallback, ioPubCallback);
            }
        }
    }

    private void setupWebSockets(String shellURL, String ioPubURL
            , WebSocketClient.Listener shellCallback
            , WebSocketClient.Listener ioPubCallback) {

        List<HttpCookie> cookies = cookieManager.getCookieStore().getCookies();
        ArrayList<BasicNameValuePair> headers = new ArrayList<>();
        StringBuilder builder = new StringBuilder();

        //Add the cookies to websocket request

        CookieSyncManager.createInstance(getActivity());
        android.webkit.CookieManager manager = android.webkit.CookieManager.getInstance();

        manager.setAcceptCookie(true);
        manager.removeSessionCookie();
        CookieSyncManager.getInstance().sync();

        for (int i = 0; i < cookies.size(); i++) {

            //Add the same cookies to WebKit so WebViews can use it
            manager.setCookie(cookies.get(i).getDomain(),cookies.get(i).getName() + "=" + cookies.get(i).getValue());

            if (i < cookies.size() - 1)
                builder.append(cookies.get(i).getName() + "=" + cookies.get(i).getValue() + ";");
            else
                builder.append(cookies.get(i).getName() + "=" + cookies.get(i).getValue());
        }

        //Sync the Cookies for WebViews
        CookieSyncManager.getInstance().sync();

        Log.i(TAG, "Adding Cookies: " + builder.toString());
        headers.add(new BasicNameValuePair("Cookie", builder.toString()));

        shellClient = new WebSocketClient(URI.create(shellURL), shellCallback, headers);
        ioPubClient = new WebSocketClient(URI.create(ioPubURL), ioPubCallback, headers);

        shellClient.connect();
        ioPubClient.connect();
    }

    private WebSocketClient.Listener shellCallback = new WebSocketClient.Listener() {
        @Override
        public void onConnect() {
            Log.i(TAG, "Shell Connected, Sending " + queryCode);
            shellClient.send(queryCode);
        }

        @Override
        public void onMessage(String message) {
            Log.i(TAG, "Shell Received Message" + message);
        }

        @Override
        public void onMessage(byte[] data) {

        }

        @Override
        public void onDisconnect(int code, String reason) {
            Log.i(TAG, "Shell Closed" + reason);
        }

        @Override
        public void onError(Exception error) {
            Log.i(TAG, "Shell Error: " + error.getMessage());
        }
    };

    private WebSocketClient.Listener ioPubCallback = new WebSocketClient.Listener() {
        @Override
        public void onConnect() {

        }

        @Override
        public void onMessage(String message) {
            try {
                Log.i(TAG, "Got Shell Message: " + message);
                final BaseReply reply = BaseReply.parse(message);
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        if (reply != null)
                            addReply(reply);
                    }
                });

            } catch (Exception e) {
                Log.i(TAG, e.getMessage());
                e.printStackTrace();
            }
        }


        @Override
        public void onMessage(byte[] data) {

        }

        @Override
        public void onDisconnect(int code, String reason) {

            //If Activity does not exist, i.e Fragment is detached, early breakout
            if(getActivity()==null)
                return;

            if (isInteractInput) {
                Log.i(TAG, "Executing Disconnect Callback");
                getActivity().runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        BusProvider.getInstance().post(new ServerDisconnectEvent(DISCONNECT_INTERACT));
                    }
                });
            }

        }

        @Override
        public void onError(Exception error) {
            Log.i(TAG, "IOPub Error:" + error);
        }
    };

    private class SageAsyncTask extends AsyncTask<Request, Void, Pair<BaseResponse, BaseResponse>> {

        @Override
        protected void onPreExecute() {
            BusProvider.getInstance().post(new ProgressEvent(StringConstants.ARG_PROGRESS_START));
        }

        @Override
        protected Pair<BaseResponse, BaseResponse> doInBackground(Request... params) {

            try {
                BaseResponse permalinkResponse, webSocketResponse;
                permalinkResponse = sendPermalinkRequest(params[0]);
                webSocketResponse = sendInitialRequest();

                return Pair.create(permalinkResponse, webSocketResponse);
            } catch (ConnectTimeoutException timeoutException) {
                BusProvider.getInstance().post(new ServerDisconnectEvent(DISCONNECT_TIMEOUT));
            } catch (Exception e) {
                Log.e(TAG, "Could not send request: " + e.getMessage());
            }
            return null;

        }

        @Override
        protected void onPostExecute(Pair<BaseResponse, BaseResponse> responses) {
            parseResponses(responses);
        }
    }
} public class CellGroupsFragment extends ListFragment {
    private static final String TAG = "SageDroid:CellGroupsFragment";

    private static final String ARG_EDIT_GROUP_DIALOG = "groupDialog";
    private static final String ARG_DELETE_GROUP_DIALOG = "deleteGroupDialog";
    private static final String DIALOG_NEW_GROUP = "newGroup";

    public static final String KEY_GROUP_PLAYGROUND = "playgroundGroup";

    private SageSQLiteOpenHelper helper;

    private TextView playgroundItem;
    private ImageButton playgroundInfoButton;

    public interface OnGroupSelectedListener {
        public void onGroupSelected(Group group);
    }

    private OnGroupSelectedListener listener;

    public void setOnGroupSelected(OnGroupSelectedListener listener) {
        this.listener = listener;
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    public void onListItemClick(ListView parent, View view, int position, long id) {
        Group group = groups.get(position);
        if (isLandscape()) {
            //Set the current item as checked in landscape orientation.
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                getListView().setItemChecked(position, true);
            }
        }
        listener.onGroupSelected(group);
    }

    protected List<Group> groups;

    protected CellGroupsAdapter adapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        helper = SageSQLiteOpenHelper.getInstance(getActivity());
        groups = helper.getGroups();
        adapter = new CellGroupsAdapter(getActivity().getApplicationContext(), groups);
        setListAdapter(adapter);
    }

    @Override
    public void onResume() {
        super.onResume();
        groups = helper.getGroups();
        adapter.refreshAdapter(groups);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.layout_cell_group, container, false);
        playgroundItem = (TextView) view.findViewById(R.id.playgroundItem);
        playgroundInfoButton = (ImageButton) view.findViewById(R.id.playgroundInfo);

        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getListView().setChoiceMode(AbsListView.CHOICE_MODE_SINGLE);
        if (!isLandscape())
            setHasOptionsMenu(true);
        registerForContextMenu(getListView());
        playgroundItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getActivity(), SageActivity.class);
                intent.putExtra(KEY_GROUP_PLAYGROUND, true);
                startActivity(intent);
            }
        });
        playgroundInfoButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showPlaygroundInfoToast();
            }
        });
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        MenuInflater menuInflater = getActivity().getMenuInflater();
        menuInflater.inflate(R.menu.menu_activity_cell, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_add: {
                FragmentManager fm = getActivity().getSupportFragmentManager();
                GroupDialogFragment dialog = GroupDialogFragment.newInstance(null);
                dialog.setOnActionCompleteListener(new GroupDialogFragment.OnActionCompleteListener() {
                    @Override
                    public void onActionCompleted() {
                        groups = helper.getGroups();
                        adapter.refreshAdapter(groups);
                    }
                });
                dialog.show(fm, DIALOG_NEW_GROUP);
                return true;
            }
            case R.id.menu_help:
                startActivity(new Intent(getActivity(), HelpActivity.class));
                return true;
            case R.id.menu_settings:
                startActivity(new Intent(getActivity(), SettingsActivity.class));
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        getActivity().getMenuInflater().inflate(R.menu.menu_group_context, menu);
    }

    @Override
    public boolean onContextItemSelected(MenuItem item) {
        AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
        FragmentManager fm = getActivity().getSupportFragmentManager();
        Group group = (Group) adapter.getItem(info.position);
        switch (item.getItemId()) {
            case R.id.menu_group_edit:
                GroupDialogFragment dialog = GroupDialogFragment.newInstance(group);
                dialog.show(fm, ARG_EDIT_GROUP_DIALOG);
                return true;

            case R.id.menu_group_delete:
                DeleteGroupDialogFragment deleteDialog = DeleteGroupDialogFragment.newInstance(group);
                deleteDialog.setOnDeleteListener(new BaseDeleteDialogFragment.OnDeleteListener() {
                    @Override
                    public void onDelete() {
                        groups = helper.getGroups();
                        adapter.refreshAdapter(groups);
                    }
                });
                deleteDialog.show(fm, ARG_DELETE_GROUP_DIALOG);
                return true;

            default:
                return super.onContextItemSelected(item);
        }

    }

    //For external access
    public void updateGroups() {
        groups = helper.getGroups();
        adapter.refreshAdapter(groups);
    }

    private void showPlaygroundInfoToast() {
        SuperCardToast toast = new SuperCardToast(getActivity());
        toast.setText(getString(R.string.toast_playground_info));
        toast.setIcon(R.drawable.ic_action_about, SuperToast.IconPosition.LEFT);
        toast.setBackground(SuperToast.Background.GREEN);
        toast.setTextColor(Color.BLACK);
        toast.setDuration(3000);
        toast.show();
    }

    private boolean isLandscape() {
        return Configuration.ORIENTATION_LANDSCAPE == getResources().getConfiguration().orientation;
    }
} public class CodeEditorFragment extends BaseFragment {
    private static final String TAG = "SageDroid:CodeEditorFragment";

    private static final String ARG_CURRENT_INPUT = "currentInput";

    private CodeView codeView;
    private ToggleButton codeViewToggleButton;
    private String currentInput = null;

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
    }

    @Override
    public void onResume() {
        super.onResume();
        BusProvider.getInstance().register(this);
    }

    @Override
    public void onPause() {
        super.onPause();
        BusProvider.getInstance().unregister(this);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_code_editor, container);
        codeView = (CodeView) view.findViewById(R.id.codeView);
        codeViewToggleButton = (ToggleButton) view.findViewById(R.id.codeViewStateToggleButton);
        codeViewToggleButton.setTypeface(fontAwesome);
        return view;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (savedInstanceState != null && savedInstanceState.containsKey(ARG_CURRENT_INPUT)) {
            currentInput = savedInstanceState.getString(ARG_CURRENT_INPUT);
            setAndFocusEditor(currentInput);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (currentInput != null)
            outState.putString(ARG_CURRENT_INPUT, currentInput);
    }

    public CodeView getCodeView() {
        return codeView;
    }

    public void saveCurrentInput() {
        codeView.getEditorText(false);
    }

    @Override
    public void setCell(Cell cell) {
        super.setCell(cell);
        if (cell.getInput() != null && (!cell.getInput().equals(""))) {
            setAndFocusEditor(cell.getInput());
            currentInput = cell.getInput();
        }
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void codeReceived(CodeReceivedEvent event) {
        Log.d(TAG, "Received code from editor: " + event.getReceivedCode());
        String receivedCode = event.getReceivedCode();
        if (cell != null) {
            cell.setInput(receivedCode);
            SageSQLiteOpenHelper.getInstance(getActivity()).saveEditedCell(cell);
        }
        if (!event.isForRun()) {
            Toast.makeText(getActivity(), R.string.toast_cell_saved, Toast.LENGTH_SHORT).show();
        }
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onComputationFinished(InteractFinishEvent event) {
        codeViewToggleButton.requestFocus();
        codeViewToggleButton.setEnabled(true);
    }

    private void setAndFocusEditor(String text) {
        Log.i(TAG, "Setting Text:" + text);
        codeView.setEditorText(text);
    }

    @SuppressWarnings("unused")
    @Subscribe
    public void onProgressUpdate(ProgressEvent event) {
        if (event.getProgressState().equals(StringConstants.ARG_PROGRESS_START)) {
            if (codeViewToggleButton.isEnabled()) {
                codeViewToggleButton.setEnabled(false);
            }
        } else if (event.getProgressState().equals(StringConstants.ARG_PROGRESS_END)) {
            codeViewToggleButton.setEnabled(true);
        }
    }

    public ToggleButton getCodeViewToggleButton() {
        return codeViewToggleButton;
    }

    public void setSavedInput(String savedInput) {
        codeView.setEditorText(savedInput);
    }

} public class OutputViewFragment extends BaseFragment {
    private static String TAG = "SageDroid:OutputViewFragment";
    private OutputView outputView;
    private ToggleButton outputViewToggleButton;


    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
    }

    @Override
    public void onResume() {
        super.onResume();
        BusProvider.getInstance().register(this);
        outputView.register();
    }

    @Override
    public void onPause() {
        super.onPause();
        BusProvider.getInstance().unregister(this);
        outputView.unregisterComponentViews();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_output_view, container);
        outputView = (OutputView) view.findViewById(R.id.outputView);
        outputViewToggleButton = (ToggleButton) view.findViewById(R.id.outputViewStateToggleButton);
        outputViewToggleButton.setTypeface(fontAwesome);
        return view;
    }

    @Override
    public void setCell(Cell cell) {
        super.setCell(cell);
        outputView.setCell(cell);
    }

    public void saveOutputToImage() {
        Toast.makeText(getActivity(), getString(R.string.toast_image_generating), Toast.LENGTH_SHORT).show();
        FileOutputStream fos;
        File file;
        try {
            if (isExternalStorageMounted()) {
                file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
                        , StringConstants.FILE_IMAGE_OUTPUT);
                fos = new FileOutputStream(file);
            } else {
                Toast.makeText(getActivity()
                        , getString(R.string.toast_external_storage_unmounted)
                        , Toast.LENGTH_SHORT).show();
                return;
            }
            OutputWebView webView = outputView.getOutputBlock();
            webView.buildDrawingCache(true);
            Bitmap outputBitmap = webView.getDrawingCache(true);
            try {
                if (outputBitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)) {
                    fos.close();
                    final Uri outputFileUri = Uri.fromFile(file);
                    Log.i(TAG, "Uri constructed:" + outputFileUri.toString());
                    Intent imageIntent = new Intent(Intent.ACTION_SEND);
                    imageIntent.setType("image/png");
                    imageIntent.putExtra(Intent.EXTRA_STREAM, outputFileUri);
                    startActivity(imageIntent);
                }
            } finally {
                webView.destroyDrawingCache();
            }
        } catch (Exception e) {
            Log.e(TAG, e + "");
        }
    }

    private boolean isExternalStorageMounted() {
        String state = Environment.getExternalStorageState();
        return Environment.MEDIA_MOUNTED.equals(state);
    }

    public OutputView getOutputView() {
        return outputView;
    }

    public ToggleButton getOutputViewToggleButton() {
        return outputViewToggleButton;
    }

    @Subscribe
    public void onComputationFinished(InteractFinishEvent event) {
        outputView.enableInteractViews();
        outputViewToggleButton.requestFocus();
        outputViewToggleButton.setEnabled(true);
    }

    @Subscribe
    public void onServerDisconnect(ServerDisconnectEvent event) {
        outputView.disableInteractViews();
    }

    @Subscribe
    public void onProgressUpdate(ProgressEvent event) {
        Log.i(TAG, "Received Progress Update" + event.getProgressState());
        if (event.getProgressState().equals(StringConstants.ARG_PROGRESS_START)) {
            if (!outputViewToggleButton.isEnabled()) {
                outputViewToggleButton.setEnabled(false);
            }
            outputView.disableInteractViews();
        } else if (event.getProgressState().equals(StringConstants.ARG_PROGRESS_END)) {
            outputView.enableInteractViews();
        }
    }

} public class Cell implements Parcelable {

    Long _id;
    String uuid;
    Group cellGroup;
    String title;
    String description;
    String input;
    int rank;
    boolean favorite;

    public Cell() {
        setUUID(UUID.randomUUID());
        //This id should be unique since UUID is inherently unique.
        _id = Long.valueOf(uuid.hashCode());
        favorite = false;
    }

    //---For Parcelable---

    private Cell(Parcel in) {
        _id = in.readLong();
        uuid = in.readString();
        cellGroup = in.readParcelable(Group.class.getClassLoader());
        title = in.readString();
        description = in.readString();
        input = in.readString();
        rank = in.readInt();
        favorite = in.readByte() != 0;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(_id);
        dest.writeString(uuid);
        dest.writeParcelable(cellGroup, flags);
        dest.writeString(title);
        dest.writeString(description);
        dest.writeString(input);
        dest.writeInt(rank);
        dest.writeByte((byte) (favorite ? 1 : 0));
    }

    public static final Creator<Cell> CREATOR = new Creator<Cell>() {
        @Override
        public Cell createFromParcel(Parcel source) {
            return new Cell(source);
        }

        @Override
        public Cell[] newArray(int size) {
            return new Cell[size];
        }
    };

    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("ID" + _id);
        builder.append("\t");
        builder.append("UUID:" + uuid + "\t");
        builder.append("Group:" + cellGroup + "\t");
        builder.append("Title" + title + "\t");
        builder.append("Description" + description + "\t");
        builder.append("Input" + input + "\t");
        builder.append("Rank" + rank);
        builder.append("\t");
        builder.append(favorite);

        return builder.toString();
    }

    //--Setters & Getters

    public long getID() {
        return _id;
    }

    public void setID(long _id) {
        this._id = _id;
    }

    public UUID getUUID() {
        return UUID.fromString(uuid);
    }

    public void setUUID(UUID uuid) {
        this.uuid = uuid.toString();
    }

    public Group getGroup() {
        return cellGroup;
    }

    public void setGroup(Group cellGroup) {
        this.cellGroup = cellGroup;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getInput() {
        return input;
    }

    public void setInput(String input) {
        this.input = input;
    }

    public int getRank() {
        return rank;
    }

    public void setRank(int rank) {
        this.rank = rank;
    }

    public boolean isFavorite() {
        return favorite;
    }

    public void setFavorite(boolean favorite) {
        this.favorite = favorite;
    }

    public static class CellComparator implements Comparator<Cell> {
        @Override
        public int compare(Cell lhs, Cell rhs) {
            int cmp = Boolean.valueOf(lhs.isFavorite())
                    .compareTo(rhs.isFavorite());
            if (cmp != 0)
                return cmp;
            cmp = Integer.valueOf(rhs.getRank()).compareTo(lhs.getRank());
            if (cmp != 0) {
                return cmp;
            }
            return lhs.getTitle().compareToIgnoreCase(rhs.getTitle());
        }
    }
} public class Insert implements Parcelable {
    private static final String TAG = "SageDroid:Inserts";

    Long _id;
    String insertDescription;
    String insertText;
    boolean isFavorite;

    public Insert() {

    }

    public Insert(long _id) {
        this._id = _id;
    }

    public Long getId() {
        return _id;
    }

    public String getInsertDescription() {
        return insertDescription;
    }

    public void setInsertDescription(String insertDescription) {
        this.insertDescription = insertDescription;
    }

    public String getInsertText() {
        return insertText;
    }

    public void setInsertText(String insertText) {
        this.insertText = insertText;
    }

    public boolean isFavorite() {
        return isFavorite;
    }

    public void setFavorite(boolean isFavorite) {
        this.isFavorite = isFavorite;
    }

    @Override
    public String toString() {
        //IntelliJ bugs -_-
        return ((Object) this).getClass().getName() + "[" +
                "_id=" + _id + ", " +
                "insertDescription=" + insertDescription + ", " +
                "insertText=" + insertText + ", " +
                "isFavorite=" + isFavorite + "," + "]";
    }

    private Insert(Parcel in) {
        _id = in.readLong();
        insertDescription = in.readString();
        insertText = in.readString();
        isFavorite = in.readInt() == 1;
    }

    public static final Creator<Insert> CREATOR = new Creator<Insert>() {
        @Override
        public Insert createFromParcel(Parcel source) {
            return new Insert(source);
        }

        @Override
        public Insert[] newArray(int size) {
            return new Insert[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeLong(_id);
        dest.writeString(insertDescription);
        dest.writeString(insertText);
        dest.writeInt(isFavorite ? 1 : 0);
    }
} public class BaseReply {
    private static final String TAG = "SageDroid:BaseReply";

    protected Header header;
    protected Header parent_header;
    protected MetaData metadata;
    protected String msg_type;
    protected String msg_id;

    //To better determine type of reply received from the server
    protected transient int messageType;
    protected transient String mimeType;
    //The json, which is actually received, for use in toString()
    protected transient String jsonData;
    protected transient boolean isInteract;
    protected transient static BaseReply reply;
    protected transient static String jsonString;

    public transient Gson gson;

    public static class MetaData {

    }

    public BaseReply() {
        gson = new GsonBuilder().setPrettyPrinting().create();
    }

    //For nested classes wishing to use the base class gson
    protected Gson getGsonInstance() {
        return gson;
    }

    //---GETTERS & SETTERS---
    public boolean isInteract() {
        return isInteract;
    }

    public void setInteract(boolean isInteract) {
        this.isInteract = isInteract;
    }

    public String getMimeType() {
        return mimeType;
    }

    public void setMimeType(String mimeType) {
        this.mimeType = mimeType;
    }

    public Header getHeader() {
        return header;
    }

    public void setHeader(Header header) {
        this.header = header;
    }

    public Header getParentHeader() {
        return parent_header;
    }

    public void setParentHeader(Header parent_header) {
        this.parent_header = parent_header;
    }

    public MetaData getMetadata() {
        return metadata;
    }

    public void setMetadata(MetaData metadata) {
        this.metadata = metadata;
    }

    public String getStringMessageType() {
        return msg_type;
    }

    public void setMsg_type(String msg_type) {
        this.msg_type = msg_type;
    }

    public String getMessageID() {
        return msg_id;
    }

    public void setMessageID(String msg_id) {
        this.msg_id = msg_id;
    }

    public int getMessageType() {
        return messageType;
    }

    public void setMessageType(int messageType) {
        this.messageType = messageType;
    }

    public String getJsonData() {
        return jsonData;
    }

    public void setJsonData(String jsonData) {
        this.jsonData = jsonData;
    }

    //---UTILITY METHODS---

    public boolean isReplyTo(Request request) {
        return (request != null) && getHeader().getSession().equals(request.getHeader().getSession());
    }

    public String toString() {
        return gson.toJson(this);
    }

    public static BaseReply parse(String jsonString) throws Exception {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(BaseReply.class, new BaseReplyDeserializer())
                .registerTypeAdapter(InteractReply.InteractContent.class, new InteractContentDeserializer())
                .registerTypeAdapter(InteractReply.InteractData.class, new InteractDataDeserializer())
                .registerTypeAdapter(InteractReply.SageInteract.class, new SageInteractDeserializer())
                .registerTypeAdapter(Values.class, new ValueDeserializer())
                .create();

        //Return the appropriate BaseReply
        BaseReply baseReply = gson.fromJson(jsonString, BaseReply.class);

        reply = baseReply;
        switch (baseReply.getMessageType()) {
            case MessageType.PYIN:
                Log.i(TAG, "Returning pyin");
                return gson.fromJson(jsonString, PythonInputReply.class);
            case MessageType.PYOUT:
                Log.i(TAG, "Returning pyout");
                return gson.fromJson(jsonString, PythonOutputReply.class);
            case MessageType.STATUS:
                Log.i(TAG, "Returning status");
                StatusReply statusReply = gson.fromJson(jsonString, StatusReply.class);
                saveStatus(statusReply);
                return statusReply;
            case MessageType.STREAM:
                Log.i(TAG, "Returning Stream");
                return gson.fromJson(jsonString, StreamReply.class);
            case MessageType.PYERR:
                Log.i(TAG, "Returning Pyerr");
                return gson.fromJson(jsonString, PythonErrorReply.class);
            case MessageType.EXECUTE_REPLY:
                Log.i(TAG, "Returning execute reply");
                return gson.fromJson(jsonString, SageExecuteReply.class);
            case MessageType.INTERACT:
                Log.i(TAG, "Returning Interact");
                return gson.fromJson(jsonString, InteractReply.class);
            case MessageType.SAGE_CLEAR:
                Log.i(TAG, "Returning Sage Clear");
                return gson.fromJson(jsonString, SageClearReply.class);
            case MessageType.HTML_FILES:
                Log.i(TAG, "Returning an HTML Reply");
                return gson.fromJson(jsonString, HtmlReply.class);
            case MessageType.IMAGE_FILENAME:
                Log.i(TAG, "Returning Image Reply");
                return gson.fromJson(jsonString, ImageReply.class);
            default:
                throw new Exception("Unknown Message Type");
        }
    }

    private static void saveStatus(StatusReply statusReply) {
        if (statusReply.getContent().getExecutionState() == ExecutionState.DEAD) {
            reply.getHeader().setSession(statusReply.getHeader().getSession());
            reply.getHeader().setMessageID(statusReply.getHeader().getMessageID());
        }
    }

} public class BusProvider {

    private static Bus bus;

    public static Bus getInstance() {
        if (bus == null) {
            bus = new Bus();
        }

        return bus;
    }
} public class ToastUtils {

    public static SuperToast getAlertToast(Context context, int resId, int duration) {
        SuperToast toast = new SuperToast(context);
        toast.setIcon(android.R.drawable.ic_dialog_alert, SuperToast.IconPosition.LEFT);
        toast.setText(context.getString(resId));
        toast.setDuration(duration);
        return toast;
    }

}
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.PopupMenu;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ProgressBar;
import android.widget.ToggleButton;
import com.github.johnpersano.supertoasts.SuperCardToast;
import com.github.johnpersano.supertoasts.SuperToast;
import com.squareup.otto.Subscribe;
import org.sagemath.droid.R;
import org.sagemath.droid.constants.StringConstants;
import org.sagemath.droid.database.SageSQLiteOpenHelper;
import org.sagemath.droid.dialogs.DeleteCellDialogFragment;
import org.sagemath.droid.dialogs.InsertDialogFragment;
import org.sagemath.droid.dialogs.InsertSpinnerDialogFragment;
import org.sagemath.droid.dialogs.ShareDialogFragment;
import org.sagemath.droid.events.*;
import org.sagemath.droid.fragments.AsyncTaskFragment;
import org.sagemath.droid.fragments.CellGroupsFragment;
import org.sagemath.droid.fragments.CodeEditorFragment;
import org.sagemath.droid.fragments.OutputViewFragment;
import org.sagemath.droid.models.database.Cell;
import org.sagemath.droid.models.database.Insert;
import org.sagemath.droid.models.gson.BaseReply;
import org.sagemath.droid.utils.BusProvider;
import org.sagemath.droid.utils.ToastUtils;
package org.sagemath.droid.activities;


/**
 * All calculations are performed and displayed here
 *
 * @author vbraun
 * @author Rasmi.Elasmar
 * @author Ralf.Stephan
 * @author Nikhil Peter Raj
 */
public class SageActivity
        extends
        ActionBarActivity
        implements
        DeleteCellDialogFragment.OnDeleteListener,
        ToggleButton.OnCheckedChangeListener,
        AsyncTaskFragment.ServerCallbacks
        , ShareDialogFragment.OnRequestOutputListener
        , PopupMenu.OnMenuItemClickListener
        , InsertSpinnerDialogFragment.OnInsertSelectedListener {
    private static final String TAG = "SageDroid:SageActivity";

    private static final String DIALOG_SHARE = "shareDialog";
    private static final String FLAG_SERVER_STATE = "serverState";

    private static final String ARG_ADD_INSERT = "newInsert";
    private static final String ARG_PUT_INSERT = "addInsert";

    private static final String TASK_FRAGMENT_TAG = "taskFragment";

    private ProgressBar cellProgressBar;
    private SuperCardToast toast;

    private Drawable playIcon, stopIcon;
    private Drawable shareIcon, shareEnableIcon;

    private CodeEditorFragment codeEditorFragment;
    private OutputViewFragment outputViewFragment;
    private AsyncTaskFragment taskFragment;

    private View dividerView;

    private SageSQLiteOpenHelper helper;

    private String permalinkURL = null;

    private boolean isServerRunning = false;
    private boolean isShareAvailable = false;
    private boolean isPlayground = false;


    private Cell cell;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        helper = SageSQLiteOpenHelper.getInstance(this);
        BusProvider.getInstance().register(this);

        setContentView(R.layout.activity_sage);

        cellProgressBar = (ProgressBar) findViewById(R.id.cell_progress);
        cellProgressBar.setVisibility(View.INVISIBLE);

        dividerView = findViewById(R.id.dividerView);
        codeEditorFragment = (CodeEditorFragment) getSupportFragmentManager()
                .findFragmentById(R.id.codeFragment);
        outputViewFragment = (OutputViewFragment) getSupportFragmentManager()
                .findFragmentById(R.id.outputFragment);

        codeEditorFragment.getCodeViewToggleButton().setOnCheckedChangeListener(this);
        outputViewFragment.getOutputViewToggleButton().setOnCheckedChangeListener(this);

        if (getSupportFragmentManager().findFragmentByTag(TASK_FRAGMENT_TAG) == null) {
            taskFragment = AsyncTaskFragment.getInstance();
            getSupportFragmentManager()
                    .beginTransaction()
                    .add(taskFragment, TASK_FRAGMENT_TAG)
                    .commit();
        } else {
            taskFragment = (AsyncTaskFragment) getSupportFragmentManager().findFragmentByTag(TASK_FRAGMENT_TAG);
        }

        Intent intent = getIntent();
        if (intent.hasExtra(CellGroupsFragment.KEY_GROUP_PLAYGROUND)) {