/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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 org.apache.qpid.tools.messagestore.commands;

import org.apache.commons.codec.binary.Hex;
import org.apache.mina.common.ByteBuffer;
import org.apache.qpid.framing.abstraction.ContentChunk;
import org.apache.qpid.server.queue.AMQMessage;
import org.apache.qpid.server.queue.QueueEntryImpl;
import org.apache.qpid.server.queue.QueueEntry;
import org.apache.qpid.server.message.ServerMessage;
import org.apache.qpid.tools.messagestore.MessageStoreTool;
import org.apache.qpid.tools.utils.Console;

import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class Dump extends Show
{
    private static final int LINE_SIZE = 8;
    private static final String DEFAULT_ENCODING = "utf-8";
    private static final boolean SPACE_BYTES = true;
    private static final String BYTE_SPACER = " ";
    private static final String NON_PRINTING_ASCII_CHAR = "?";

    protected boolean _content = true;

    public Dump(MessageStoreTool tool)
    {
        super(tool);
    }

    public String help()
    {
        return "Dump selected message content. Default: show=content";
    }

    public String usage()
    {
        return getCommand() + " [show=[all],[msgheaders],[_amqHeaders],[routing],[content]] [id=<msgid e.g. 1,2,4-10>]";
    }

    public String getCommand()
    {
        return "dump";
    }

    public void execute(String... args)
    {
        assert args.length > 0;
        assert args[0].equals(getCommand());


        if (args.length >= 2)
        {
            for (String arg : args)
            {
                if (arg.startsWith("show="))
                {
                    _content = arg.contains("content") || arg.contains("all");
                }
            }

            parseArgs(args);
        }

        performShow();
    }


    protected List<List> createMessageData(java.util.List<Long> msgids, List<QueueEntry> messages, boolean showHeaders, boolean showRouting,
                                           boolean showMessageHeaders)
    {

        List<List> display = new LinkedList<List>();

        List<String> hex = new LinkedList<String>();
        List<String> ascii = new LinkedList<String>();
        display.add(hex);
        display.add(ascii);

        for (QueueEntry entry : messages)
        {
            ServerMessage msg = entry.getMessage();
            if (!includeMsg(msg, msgids))
            {
                continue;
            }

            //Add divider between messages
            hex.add(Console.ROW_DIVIDER);
            ascii.add(Console.ROW_DIVIDER);

            // Show general message information
            hex.add(Show.Columns.ID.name());
            ascii.add(msg.getMessageNumber().toString());

            hex.add(Console.ROW_DIVIDER);
            ascii.add(Console.ROW_DIVIDER);

            if (showRouting)
            {
                addShowInformation(hex, ascii, msg, "Routing Details", true, false, false);
            }
            if (showHeaders)
            {
                addShowInformation(hex, ascii, msg, "Headers", false, true, false);
            }
            if (showMessageHeaders)
            {
                addShowInformation(hex, ascii, msg, null, false, false, true);
            }

            // Add Content Body seciont
            hex.add("Content Body");
            ascii.add("");
            hex.add(Console.ROW_DIVIDER);
            ascii.add(Console.ROW_DIVIDER);

            if(msg instanceof AMQMessage)
            {

                Iterator bodies = ((AMQMessage)msg).getContentBodyIterator();
                if (bodies.hasNext())
                {

                    hex.add("Hex");
                    hex.add(Console.ROW_DIVIDER);


                    ascii.add("ASCII");
                    ascii.add(Console.ROW_DIVIDER);

                    while (bodies.hasNext())
                    {
                        ContentChunk chunk = (ContentChunk) bodies.next();

                        //Duplicate so we don't destroy original data :)
                        ByteBuffer hexBuffer = chunk.getData().duplicate();

                        ByteBuffer charBuffer = hexBuffer.duplicate();

                        Hex hexencoder = new Hex();

                        while (hexBuffer.hasRemaining())
                        {
                            byte[] line = new byte[LINE_SIZE];

                            int bufsize = hexBuffer.remaining();
                            if (bufsize < LINE_SIZE)
                            {
                                hexBuffer.get(line, 0, bufsize);
                            }
                            else
                            {
                                bufsize = line.length;
                                hexBuffer.get(line);
                            }

                            byte[] encoded = hexencoder.encode(line);

                            try
                            {
                                String encStr = new String(encoded, 0, bufsize * 2, DEFAULT_ENCODING);
                                String hexLine = "";

                                int strKength = encStr.length();
                                for (int c = 0; c < strKength; c++)
                                {
                                    hexLine += encStr.charAt(c);

                                    if (c % 2 == 1 && SPACE_BYTES)
                                    {
                                        hexLine += BYTE_SPACER;
                                    }
                                }

                                hex.add(hexLine);
                            }
                            catch (UnsupportedEncodingException e)
                            {
                                _console.println(e.getMessage());
                                return null;
                            }
                        }

                        while (charBuffer.hasRemaining())
                        {
                            String asciiLine = "";

                            for (int pos = 0; pos < LINE_SIZE; pos++)
                            {
                                if (charBuffer.hasRemaining())
                                {
                                    byte ch = charBuffer.get();

                                    if (isPrintable(ch))
                                    {
                                        asciiLine += (char) ch;
                                    }
                                    else
                                    {
                                        asciiLine += NON_PRINTING_ASCII_CHAR;
                                    }

                                    if (SPACE_BYTES)
                                    {
                                        asciiLine += BYTE_SPACER;
                                    }
                                }
                                else
                                {
                                    break;
                                }
                            }

                            ascii.add(asciiLine);
                        }
                    }
                }
                else
                {
                    List<String> result = new LinkedList<String>();

                    display.add(result);
                    result.add("No ContentBodies");
                }
            }
        }

        // if hex is empty then we have no data to display
        if (hex.size() == 0)
        {
            return null;
        }

        return display;
    }

    private void addShowInformation(List<String> column1, List<String> column2, ServerMessage msg,
                                    String title, boolean routing, boolean headers, boolean messageHeaders)
    {
        List<QueueEntry> single = new LinkedList<QueueEntry>();
        single.add(new QueueEntryImpl(null,msg, Long.MIN_VALUE));

        List<List> routingData = super.createMessageData(null, single, headers, routing, messageHeaders);

        //Reformat data
        if (title != null)
        {
            column1.add(title);
            column2.add("");
            column1.add(Console.ROW_DIVIDER);
            column2.add(Console.ROW_DIVIDER);
        }

        // look at all columns in the routing Data
        for (List item : routingData)
        {
            // the item should be:
            //   Title
            //   *divider
            //   value
            // otherwise we can't reason about the correct value
            if (item.size() == 3)
            {
                //Filter out the columns we are not interested in.

                String columnName = item.get(0).toString();

                if (!(columnName.equals(Show.Columns.ID.name())
                      || columnName.equals(Show.Columns.Size.name())))
                {
                    column1.add(columnName);
                    column2.add(item.get(2).toString());
                }
            }
        }
        column1.add(Console.ROW_DIVIDER);
        column2.add(Console.ROW_DIVIDER);
    }

    private boolean isPrintable(byte c)
    {
        return c > 31 && c < 127;
    }
}
