/**
 * Copyright (c) 2002-2007 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 * 
 * Contributors: 
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.edit.ui.provider;


import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import org.eclipse.jface.resource.CompositeImageDescriptor;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.graphics.Device;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;

import org.eclipse.emf.common.EMFPlugin;
import org.eclipse.emf.common.util.URI;

import org.eclipse.emf.edit.EMFEditPlugin;
import org.eclipse.emf.edit.provider.ComposedImage;


/**
 *
 */
public class ExtendedImageRegistry 
{
  public static final ExtendedImageRegistry INSTANCE = new ExtendedImageRegistry();

  public static ExtendedImageRegistry getInstance()
  {
    return INSTANCE;
  }

  protected final Display display;

  protected HashMap<Object, Image> table = new HashMap<Object, Image>(10);

  protected HashMap<Object, ImageDescriptor> imageDescriptorTable = new HashMap<Object, ImageDescriptor>(10);

  public ExtendedImageRegistry() 
  {
    Display display = Display.getCurrent();
    if (display == null)
    {
      final Display defaultDisplay= Display.getDefault();
      defaultDisplay.asyncExec
        (new Runnable()
         {
           public void run()
           {
             hookDisplayDispose(defaultDisplay);
           }
         });
      this.display = defaultDisplay;
    }
    else
    {
      this.display = display;
    }
  }

  public ExtendedImageRegistry(Display display) 
  {
    this.display = display;
    hookDisplayDispose(display);
  }

  protected static Object resourceURL = 
    EMFEditPlugin.INSTANCE.getImage("full/obj16/Resource");
  
  protected static String resourceURLPrefix = resourceURL.toString() + "#";

  protected static String itemURLPrefix = 
    EMFEditPlugin.INSTANCE.getImage("full/obj16/Item").toString() + "#";

  protected static String createChildURLPrefix = 
    EMFEditPlugin.INSTANCE.getImage("full/ctool16/CreateChild").toString() + "#";

  public Image getImage(Object object) 
  {
    if (object instanceof Image)
    {
      return (Image)object;
    }
    else
    {
      Image result = table.get(object);
      if (result == null)
      {
        if (object instanceof ImageDescriptor)
        {
          ImageDescriptor imageDescriptor = (ImageDescriptor)object;
          result = imageDescriptor.createImage();
        }
        else if (object instanceof URL || object instanceof URI)
        {
          String urlString = object.toString(); 
          ImageDescriptor imageDescriptor = null;
          if (urlString.startsWith(resourceURLPrefix))
          {
            if (EMFPlugin.IS_RESOURCES_BUNDLE_AVAILABLE)
            {
              imageDescriptor = 
                PlatformUI.getWorkbench().getEditorRegistry().getImageDescriptor 
                  ("dummy." + urlString.substring(resourceURLPrefix.length()));
            }
            else
            {
              result = getImage(resourceURL);
            }
          }
          else if (urlString.startsWith(itemURLPrefix))
          {
            try
            {
              URL url = new URL(urlString.substring(0, itemURLPrefix.length()));
              String key1 = urlString.substring(itemURLPrefix.length());
              imageDescriptor = new URLImageDescriptor(url, key1, null);
            }
            catch (IOException exception)
            {
              // Ignore
            }
          }
          else if (urlString.startsWith(createChildURLPrefix))
          {
            try
            {
              URL url = new URL(urlString.substring(0, createChildURLPrefix.length()));
              String key1 = urlString.substring(createChildURLPrefix.length() + 1);
              String key2 = null;
              int index = key1.indexOf("/");
              if (index != -1)
              {
                key2 =  key1.substring(index + 1);
                key1 =  key1.substring(0, index);
              }
              imageDescriptor = new URLImageDescriptor(url, key1, key2);
            }
            catch (IOException exception)
            {
              // Ignore
            }
          }
          else
          {
            try
            {
              imageDescriptor = ImageDescriptor.createFromURL(new URL(urlString));
            }
            catch (IOException exception)
            {
              // Ignore
            }
          }
          if (imageDescriptor != null)
          {
            result = imageDescriptor.createImage();
          }
        }
        else if (object instanceof ComposedImage)
        {
          ImageDescriptor composedImageDescriptor = new ComposedImageDescriptor((ComposedImage)object);
          result = composedImageDescriptor.createImage();
        }

        if (result != null)
        {
          table.put(object, result);
        }
      }
      return result;
    }
  }

  public ImageDescriptor getImageDescriptor(Object object) 
  {
    if (object instanceof ImageDescriptor)
    {
      return (ImageDescriptor)object;
    }
    else if (display == Display.getCurrent())
    {
      final Image image = getImage(object);
      if (image != null)
      {
        return new ImageWrapperImageDescriptor(image);
      }
      else
      {
        return null;
      }
    }
    else
    {
      return getImageDescriptorOnBackgroundThread(object);
    }
  }

  protected synchronized ImageDescriptor getImageDescriptorOnBackgroundThread(Object object) 
  {
    if (object instanceof ImageDescriptor)
    {
      return (ImageDescriptor)object;
    }
    else
    {
      ImageDescriptor result = imageDescriptorTable.get(object);
      if (result == null)
      {
        if (object instanceof Image)
        {
          result = new ImageWrapperImageDescriptor((Image)object);
        }
        else
        {
          Image image = table.get(object);
          if (image != null)
          {
            result = new ImageWrapperImageDescriptor(image);
          }
          else if (object instanceof URL || object instanceof URI)
          {
            String urlString = object.toString(); 
            if (urlString.startsWith(resourceURLPrefix))
            {
              if (EMFPlugin.IS_RESOURCES_BUNDLE_AVAILABLE)
              {
                result = 
                  PlatformUI.getWorkbench().getEditorRegistry().getImageDescriptor 
                    ("dummy." + urlString.substring(resourceURLPrefix.length()));
              }
              else
              {
                result = getImageDescriptor(resourceURL);
              }
            }
            else if (urlString.startsWith(itemURLPrefix))
            {
              try
              {
                URL url = new URL(urlString.substring(0, itemURLPrefix.length()));
                String key1 = urlString.substring(itemURLPrefix.length());
                result = new URLImageDescriptor(url, key1, null);
              }
              catch (IOException exception)
              {
                // Ignore
              }
            }
            else if (urlString.startsWith(createChildURLPrefix))
            {
              try
              {
                URL url = new URL(urlString.substring(0, createChildURLPrefix.length()));
                String key1 = urlString.substring(createChildURLPrefix.length() + 1);
                String key2 = null;
                int index = key1.indexOf("/");
                if (index != -1)
                {
                  key2 =  key1.substring(index + 1);
                  key1 =  key1.substring(0, index);
                }
                result = new URLImageDescriptor(url, key1, key2);
              }
              catch (IOException exception)
              {
                // Ignore
              }
            }
            else
            {
              try
              {
                result = ImageDescriptor.createFromURL(new URL(urlString));
              }
              catch (IOException exception)
              {
                // Ignore
              }
            }
          }
          else if (object instanceof ComposedImage)
          {
            result = new ComposedImageDescriptor((ComposedImage)object);
          }
        }

        if (result != null)
        {
          imageDescriptorTable.put(object,  result);
        }
      }
      return result;
    }
  }

  protected void handleDisplayDispose() 
  {
    for (Image image : table.values())
    {
      image.dispose();
    }
    table = null;
  }

  protected void hookDisplayDispose(Display display) 
  {
    display.disposeExec
      (new Runnable() 
       {
         public void run() 
         {
           handleDisplayDispose();
         }       
       });
  }
}


class ImageWrapperImageDescriptor extends ImageDescriptor
{
  protected Image image;

  public ImageWrapperImageDescriptor(Image image)
  {
    this.image = image;
  }

  @Override
  public boolean equals(Object that)
  {
    return 
      that instanceof ImageWrapperImageDescriptor && 
        ((ImageWrapperImageDescriptor)that).image.equals(image);
  }

  @Override
  public int hashCode()
  {
    return image.hashCode();
  }

  @Override
  public ImageData getImageData()
  {
    return image.getImageData();
  }
}


class ComposedImageDescriptor extends CompositeImageDescriptor
{
  protected ComposedImage composedImage;
  protected List<ImageData> imageDatas;

  public ComposedImageDescriptor(ComposedImage composedImage)
  {
    this.composedImage = composedImage;
  }

  @Override
  @SuppressWarnings("deprecation")
  public void drawCompositeImage(int width, int height)
  {
    ComposedImage.Size size = new ComposedImage.Size();
    size.width = width;
    size.height = height;
    Iterator<ImageData> images = imageDatas.iterator();
    for (Iterator<ComposedImage.Point> points = composedImage.getDrawPoints(size).iterator(); points.hasNext(); )
    {
      ComposedImage.Point point = points.next();
      drawImage(images.next(), point.x, point.y);
    }
  }

  @Override
  public Point getSize()
  {
    List<Object> images = composedImage.getImages();
    imageDatas = new ArrayList<ImageData>(images.size());
    List<ComposedImage.Size> sizes = new ArrayList<ComposedImage.Size>(images.size());
    for (Object object : images)
    {
      Image image = ExtendedImageRegistry.getInstance().getImage(object);
      ImageData imageData = image.getImageData();
      imageDatas.add(imageData);

      ComposedImage.Size size = new ComposedImage.Size();
      size.width = imageData.width;
      size.height = imageData.height;
      sizes.add(size);
    }

    ComposedImage.Size result = composedImage.getSize(sizes);
    return new Point(result.width, result.height);
  }
}

class URLImageDescriptor extends ImageDescriptor 
{
  protected URL url;
  protected String key1;
  protected String key2;

  URLImageDescriptor(URL url, String key1, String key2) 
  {
    this.url = url;
    this.key1 = key1;
    this.key2 = key2;
  }

  @Override
  public ImageData getImageData() 
  {
    InputStream in = null;
    try 
    {
      if (key1 != null)
      {
        ImageDataSynthesizer imageDataSynthesizer = new ImageDataSynthesizer(url);
        in = imageDataSynthesizer.generateGIF(key1, key2);
        return new ImageData(in);
      }
      else
      {
        in = url.openStream();
      }

      return new ImageData(in);
    }
    catch (IOException e) 
    {
      return null;
    }
    finally 
    {
      if (in != null)
      {
        try 
        {
          in.close();
        }
        catch (IOException e) 
        {
          return null;
        }
      }
    }
  }

  protected InputStream getStream()  throws IOException
  {
      return url.openStream();
  }

  @Override
  public int hashCode() 
  {
    return url.hashCode() | (key1 == null ? 0 : key1.hashCode()) | (key2 == null ? 0 : key2.hashCode());
  }

  @Override
  public boolean equals(Object that) 
  {
    if (that instanceof URLImageDescriptor)
    {
      URLImageDescriptor otherURLImageDescriptor = (URLImageDescriptor)that;
      return 
        url.equals(otherURLImageDescriptor.url) &&
          (key1 == null ? otherURLImageDescriptor.key1 == null : key1.equals(otherURLImageDescriptor.key1)) &&
          (key2 == null ? otherURLImageDescriptor.key2 == null : key2.equals(otherURLImageDescriptor.key2));
    }
    else
    {
      return false;
    }
  }
  
  @Override
  public Image createImage(boolean returnMissingImageOnError, Device device)
  {
    // Don't call super because it loses the transparency information if we write this image to disk.
    //
    return new Image(device, getImageData());
  }

  @Override
  public String toString() 
  {
    return getClass().getName() + "(" + url + "#" + key1 + "/" + key2 + ")"; 
  }
}

class ImageDataSynthesizer
{
  protected URL url;

  protected static final int tableOffset1 = 49;
  protected static final int tableOffset2 = 25;

  public ImageDataSynthesizer(URL url)
  {
    this.url = url;
  }

  protected int code(String code)
  {
    int result = 0;
    for (int i = 0; i < code.length(); ++ i)
    {
      result += code.charAt(i) - 32;
    }
    return result;
  }

  public InputStream generateGIF(String key1, String key2)
  {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    try
    {
      byte [] content = new byte [5000];
      int result = getContents(content, url);

      // generateColor();
      ColorInformation info1 = ColorInformation.getColor(code(key1));
      ColorInformation info2 = key2 == null ? null : ColorInformation.getColor(code(key2));


      for (int j = 0; j < result; ++j)
      {
        if (j == tableOffset1 || j == tableOffset1 + 3 || j == tableOffset1 + 6 || j == tableOffset1 + 9)
        {
          int index = (j - tableOffset1) / 3;
          if (!info1.rainbow || info1.which == index - 1)
          {
            content[j] = info1.scale(info1.red, info1.factor[index]);
          }
        }
        else if (j == tableOffset1 + 1 || j == tableOffset1 + 4 || j == tableOffset1 + 7 || j == tableOffset1 + 10)
        {
          int index = (j - tableOffset1 - 1) / 3;
          if (!info1.rainbow || info1.which == index - 1)
          {
            content[j] = info1.scale(info1.green, info1.factor[index]);
          }
        }
        else if (j == tableOffset1 + 2 || j == tableOffset1 + 5 || j == tableOffset1 + 8 || j == tableOffset1 + 11)
        {
          int index = (j - tableOffset1 - 2) / 3;
          if (!info1.rainbow || info1.which == index - 1)
          {
            content[j] = info1.scale(info1.blue, info1.factor[index]);
          }
        }

        if (info2 != null)
        {
          if (j == tableOffset2 || j == tableOffset2 + 3 || j == tableOffset2 + 6 || j == tableOffset2 + 9)
          {
            int index = (j - tableOffset2) / 3;
            if (!info2.rainbow || info2.which == index - 1)
            {
              content[j] = info2.scale(info2.red, info2.factor[index]);
            }
          }
          else if (j == tableOffset2 + 1 || j == tableOffset2 + 4 || j == tableOffset2 + 7 || j == tableOffset2 + 10)
          {
            int index = (j - tableOffset2 - 1) / 3;
            if (!info2.rainbow || info2.which == index - 1)
            {
              content[j] = info2.scale(info2.green, info2.factor[index]);
            }
          }
          else if (j == tableOffset2 + 2 || j == tableOffset2 + 5 || j == tableOffset2 + 8 || j == tableOffset2 + 11)
          {
            int index = (j - tableOffset2 - 2) / 3;
            if (!info2.rainbow || info2.which == index - 1)
            {
              content[j] = info2.scale(info2.blue, info2.factor[index]);
            }
          }
        }
      }

      DataOutputStream writer = new DataOutputStream(outputStream);
      writer.write(content, 0, result);
      writer.close();
    }
    catch (Exception exception)
    {
      exception.printStackTrace();
    }

    return new ByteArrayInputStream(outputStream.toByteArray());
  }

  protected int getContents(byte [] content, URL gifURL) throws IOException
  {
    BufferedInputStream bufferedInputStream = new BufferedInputStream(gifURL.openStream());
    DataInputStream reader = new DataInputStream(bufferedInputStream);
    int result = reader.read(content, 0, content.length);
    reader.close();
    return result;
  }

  protected static class ColorInformation
  {
    public static ColorInformation getColor(int index)
    {
      index = Math.abs(index) % 61;
      while (entries.size() <= index)
      {
        instance.generateColor();

        ColorInformation entry = new ColorInformation();
        entry.red = instance.red;
        entry.green = instance.green;
        entry.blue = instance.blue;
        entry.which = instance.which;
        entry.factor = new double [] { instance.factor[0], instance.factor[1], instance.factor[2], instance.factor[3] };
        entry.rainbow = instance.rainbow;
        entries.add(entry);
        instance.fixFactor();
      }
      return entries.get(index);
    }

    protected static ColorInformation instance = new ColorInformation();

    protected static List<ColorInformation> entries = new ArrayList<ColorInformation>(1000);

    public int red = 192;
    public int green = 64;
    public int blue = 64;

    public int which  = 2;
    public int change  = 64;

    public double [] factor = { 0.35, 0.1, -0.1, -0.3 };
    public boolean rainbow;

    public byte scale(int value, double factor)
    {
      if (factor > 0.0)
      {
        return (byte)(value + (255 - value) * factor);
      }
      else
      {
        return (byte)(value + value * factor);
      }
    }

    protected void generateColor()
    {
      switch (which)
      {
        case 0:
        {
          red += change;
          if (red <= 64)
          {
            which = 1;
            change = -change;
          }
          else if (red >= 192)
          {
            which = 1;
            change = -change;
          }
          break;
        }
        case 1:
        {
          green += change;
          if (green >= 192)
          {
            which = 2;
            change = -change;
          }
          else if (green <= 64)
          {
            which = 2;
            change = -change;
          }
          break;
        }
        case 2:
        {
          blue += change;
          if (blue >= 192)
          {
            which = 0;
            change = -change;
          }
          else if (blue <=64)
          {
            which = 0;
            change = -change;
          }
          break;
        }
      }
    }

    protected void fixFactor()
    {
      if (red == 192 && green == 64 && blue == 64)
      {
        for (int j = 0; j < factor.length; ++j)
        {
          factor[j] += 0.3;
        }
        if (factor[0] >= 1.0)
        {
          rainbow = true;
          for (int j = 0; j < factor.length; ++j)
          {
            factor[j] -= 0.8;
          }
        }
      }
    }
  }
}
