/* Copyright (C) 2002 by Peter Eastman

   This program is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
   Foundation; either version 2 of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
   PARTICULAR PURPOSE.  See the GNU General Public License for more details. */

package artofillusion.ui;

import artofillusion.*;

import buoy.widget.*;
import java.util.*;
import java.io.*;


/** This class loads and stores the internal structure of a menu or menubar.
  It allows to manipulate it and creates the appropriate menu(bar)s.

   @author Michael Butscher
 */

// TODO(MB) Rewrite class to use MenuDescription where possible

public class MenuStructure
{
  /* The itemnames array
    is a list of names for the menu items. The special name "separator"
    creates a separating line between groups of menu items.
    <P>The method tests automatically if the item is allowed and if
    it should be a checkbox item. Multiple subsequent separators and
    separators at the end of the menu are automatically removed.
  */

  /** The actual structure data of the object. The elements are of type
    MenuDescription or Object[]. The first element of the array and its subarrays
    is always the name of the (sub)menu. 
   */
  protected Object[] structure;


  public MenuStructure()
  {
  }

  /** Create MenuStructure and call loadStructure(menuname, internal)
  */

  public MenuStructure(String menuname, boolean internal)
  {
    this();
    loadStructure(menuname, internal);
  }
  
  /** Returns the actual structure data. */
  public Object[] getData()
  {
    return structure;
  }

  /** Create a menu. This is the static version, needed for recursion. */

  protected static MenuWidget createMenu(Object[] itemnames, MenuItemFactory factory)
  {
    boolean separator = false;
    MenuWidget mi;
    MenuDescription itemdesc;

    itemdesc = (MenuDescription)itemnames[0];
    BMenu result = Translate.menu(itemdesc.internalName);
    result.setText(itemdesc.getLabel());

    for(int i = 1; i < itemnames.length; ++i)
    {
      mi = null;

      if (itemnames[i] instanceof Object[])
	mi = createMenu((Object[])itemnames[i], factory);

      else if (itemnames[i] instanceof MenuDescription)
      {
        itemdesc = (MenuDescription)itemnames[i];

        if ("separator".equals(itemdesc.internalName))
        {
          separator = true;  // Actual creation of separators is delayed
          continue;
        }
        else
        {
          mi = factory.createMenuItem(itemdesc);
        }
      }

      if (mi == null)
	continue;

      if (separator)
      {
        result.addSeparator();
        separator = false;
      }

//      if (mi instanceof Menu)   //Menu.add() will not work otherwise
//        result.add((Menu)mi);
//      else
      result.add(mi);
    }

    return result;
  }

  /** Create a menu.*/

  public BMenu createMenu(MenuItemFactory factory)
  {
    return (BMenu)createMenu(structure, factory);
  }

  /** Create a menubar. */

  public BMenuBar createMenuBar(MenuItemFactory factory)
  {
    BMenuBar result = new BMenuBar();

    for(int i = 1; i < structure.length; ++i)
    {
      if (structure[i] instanceof Object[])
        result.add((BMenu)createMenu((Object[])structure[i], factory));
      else if (structure[i] instanceof MenuDescription)
      {
        MenuDescription itemdesc = (MenuDescription)structure[i];
        MenuWidget mi = factory.createMenuItem(itemdesc);
        if (mi instanceof BMenu)
          result.add((BMenu)mi);
      }
    }

    return result;
  }

  /** Create a tree of MenuTreeElement's. Internal recursive version. */

  protected static MenuTreeElement createTreeElements(Object[] itemdescs, TreeList tree)
  {
    MenuDescription desc = (MenuDescription)itemdescs[0];
    
    String childtext;
    MenuDescription childdesc;
    
    MenuTreeElement[] childs = new MenuTreeElement[itemdescs.length-1];

    for(int i = 1; i < itemdescs.length; ++i)
    {
      if (itemdescs[i] instanceof MenuDescription)
      {
        childdesc = (MenuDescription)itemdescs[i];
        childs[i-1] = new MenuTreeElement(childdesc, tree, null);
      }
      else if (itemdescs[i] instanceof Object[])
        childs[i-1] = createTreeElements((Object[])itemdescs[i], tree);
    }
    return new MenuTreeElement(desc, tree, childs);
  }

  /** Create a tree of MenuTreeElement's. */

  public MenuTreeElement createTreeElements(TreeList tree)
  {
    return createTreeElements(structure, tree);
  }
  
  /** Build the menu structure from a tree of MenuTreeElement's. Internal
   recursive version. */
  
  protected static Object[] internBuildFromMenuTree(TreeElement treeelement)
  {
    MenuTreeElement mte = (MenuTreeElement)treeelement;
    MenuTreeElement mtei;
    Object[] result = new Object[mte.getNumChildren()+1];
    
    result[0] = mte.getItemDescription();
    
    for(int i = 0; i < mte.getNumChildren(); ++i)
    {
      mtei = (MenuTreeElement)mte.getChild(i);
      if (mtei.representsMenu())
        result[i+1] = internBuildFromMenuTree(mtei);
      else
        result[i+1] = mtei.getItemDescription();
    }
    
    return result;
  }
  
  /** Build the menu structure from a tree of MenuTreeElement's. This is the
    inverse method of {@link createTreeElements}.*/
  
  public void buildFromMenuTree(TreeElement treeelement)
  {
    structure = internBuildFromMenuTree(treeelement);
  }

  /** Write out menu structure to a stream. Recursive version.*/

  protected static void writeToStream(Object[] itemdescs, Writer w)
      throws IOException
  {
    for(int i = 0; i < itemdescs.length; ++i)
    {
      if (itemdescs[i] instanceof Object[])
      {
        w.write("!SUBMENU\n");
	writeToStream((Object[])itemdescs[i], w);
        // w.write("!END\n");
      }
      else if (itemdescs[i] instanceof MenuDescription)
      {
        w.write(((MenuDescription)itemdescs[i]).getDescText()+"\n");
      }
    }
    w.write("!END\n");
  }

  /** Write out menu structure to a stream.*/

  public void writeToStream(OutputStream output) throws IOException
  {
    Writer w = new OutputStreamWriter(output);
    writeToStream(structure, w);
    w.flush();
    w.close();  // ???
  }

  /** Read menu structure from a stream. Recursive version. */

  protected static Object[] readFromStreamIntern(BufferedReader r)
      throws IOException
  {
    Vector result = new Vector();
    String line;
    MenuDescription desc;

    while (true)
    {
      line = r.readLine();
      if (line == null)
        break;

      line = line.trim();

      if ("!END".equals(line))  //End of (sub)menu
        break;
      else if ("".equals(line))
        continue;
      else if (line.startsWith("#"))  // Comment
        continue;
      else if ("!SUBMENU".equals(line))
      {
        result.add(readFromStreamIntern(r));
      }
      else
      {
        result.add(new MenuDescription(line, result.size() == 0));
      }
    }

    return result.toArray();
  }

  /** Read menu structure from a stream. See {@link #loadStructure} for
    file format details. */

  public void readFromStream(InputStream input) throws IOException
  {
    structure =
        readFromStreamIntern(new BufferedReader(new InputStreamReader(input)));

  }

  /** Load the appropriate description file for the menu structure.
    A description file has the suffix ".menu". It contains lines of the
    structure "<internal name> <menu shortcut> <menu label>" where shortcut
    and label are optional. If the shortcut/label is not given,
    it is looked up in the *.properties files. The interpretation of these
    lines is done by the MenuDescription constructor.
    <P> There are also a few special lines: <UL>
    <LI>"separator" Shows a menu separator
    <LI>"!SUBMENU" Begins a submenu. The next line contains the description of
                   internal name and label of the submenu
    <LI>"!END" End of submenu or end of file (so there is one more "!END" than
                   corresponding "!SUBMENU"s).
    
    @param menuname Internal name of the menu (and its description file)
    @param internal false: Try to load the user defined file first.
                    true: Load only the internal file from JAR-Archive
  */

  public void loadStructure(String menuname, boolean internal)
  {
    InputStream input = null;

    if (!internal)
    {
      File f = new File(System.getProperty("user.home"), ".aoimenus/"+menuname+".menu");
      if (f.exists())
      {
        try
        {
          input = new FileInputStream(f);
        }
        catch (FileNotFoundException ex)
        {
          ex.printStackTrace();
        }
      }
    }

    if (input == null)
      input = getClass().getResourceAsStream("/"+menuname+".menu");

    if (input == null)
    {
      System.out.println("buildStructure input == null");
      return;   // TODO(MB) Better error handling
    }

    try
    {
      readFromStream(input);
    }
    catch(IOException e)
    {
      e.printStackTrace();   // TODO(MB) Better error handling
    }
    finally
    {
      try
      {
        input.close();
      }
      catch(IOException e)
      {
        e.printStackTrace();   // TODO(MB) Better error handling
      }
    }
  }

  public void saveStructure(String menuname)
  {
    File dir = new File(System.getProperty("user.home"), ".aoimenus");
    
    if (!dir.exists())
      dir.mkdirs();
    
    File f = new File(System.getProperty("user.home"), ".aoimenus/"+menuname+".menu");
    OutputStream output = null;
   

    try
    {
      output = new FileOutputStream(f);
    }
    catch (FileNotFoundException ex)
    {
      ex.printStackTrace();
      return;
    }

    try
    {
      writeToStream(output);
    }
    catch(IOException e)
    {
      e.printStackTrace();   // TODO(MB) Better error handling
    }
    finally
    {
      try
      {
        output.close();
      }
      catch(IOException e)
      {
        e.printStackTrace();   // TODO(MB) Better error handling
      }
    }
  }
}