/*
 * Created on 2004-09-03
 *
 * Part of the Symbian development plug-in for Eclipse
 * Copyright (C) Mikołaj Zalewski, 2004
 */
package pl.zalewski.mikolaj.symbdev.skins;



import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;

import javax.imageio.ImageIO;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import pl.zalewski.mikolaj.symbdev.SymbdevPlugin;
import pl.zalewski.mikolaj.symbdev.SymbianSDKEntry;
import pl.zalewski.mikolaj.symbdev.utils.BMPFile;

/**
 * @author mikolaj
 */
public class SkinXmlApplier extends DefaultHandler {

    public static final String BMP_FILE="epoc.eclipse.bmp";
    
    SkinXmlApplier(EpocIniRewriter rewriter, IPath skinRoot, IPath sdkRoot, int symbianVersion)
    {
        super();
        fRewriter=rewriter;
        fSkinRoot=skinRoot;
        fSdkRoot=sdkRoot;
        fSymbianVersion=symbianVersion;
        rewriter.addRewriter("VirtualKey", new DeleteCommand());
        rewriter.addRewriter("MouseTarget", new DeleteCommand());
        
        if (symbianVersion<=SymbianSDKEntry.SYMBIAN_6_1)
        {
            // SDKs for Series 60 1.x requires defining keys with Default
            fKeycodesMap=new HashMap();
            fillKeyTable();
        }
    }
    
    Locator fLocator = null;

	public void setDocumentLocator(Locator locator) {
		fLocator = locator;  
	}
	
	public boolean checkMainTag(String str, String name, boolean throwException)
		throws SAXException
	{
	    if ((str==null && fMainTag!=null) ||
	        (str!=null && fMainTag==null) ||
	        (str!=null && fMainTag!=null && !str.equals(fMainTag)))
	    {
	        if (throwException)
	            throw new SAXParseException("Tag "+name+" in wrong place", fLocator);
	        return false;
	    }

	    return true;
	}
		
	protected void copyBitmap(String source)
		throws IOException
	{
	    if (source==null) throw new IOException();
	    
	    IPath sourcePath=fSkinRoot.append(source);
	    String destPath=fSdkRoot.append("epoc32/data/"+BMP_FILE).toString();
	    
	    String ext=sourcePath.getFileExtension();
	    if (ext==null || ext.equalsIgnoreCase("bmp"))
	    {
	        SymbdevPlugin.traceStr(SymbdevPlugin.TRACE_SKIN, "Source is *.bmp - copying file...");
		    FileInputStream in=new FileInputStream(sourcePath.toString());
		    FileOutputStream out=new FileOutputStream(destPath);
		    
		    byte[] buf=new byte[65536];
		    while (true)
		    {
		        int count=in.read(buf);
		        if (count<=0)
		            break;
		        out.write(buf, 0, count);
		    }
		    
		    in.close();
		    out.close();
	    } else
	    {
	        SymbdevPlugin.traceStr(SymbdevPlugin.TRACE_SKIN, "Source is not *.bmp - converting...");
	        BufferedImage img = ImageIO.read(new File(sourcePath.toString()));
	        int width=img.getWidth(), height=img.getHeight();
	        BMPFile.saveBitmap(destPath, img.getRGB(0, 0, width, height, null, 0, width), width, height);
/*	        if (!ImageIO.write(img, "bmp", new File(destPath)))
	            throw new IOException("Error writing bitmap: format unknown");*/
	    }
	}
	
	protected int getNumber(String x, String attr, String elem)
		throws SAXParseException
	{
	    int ret;
        try {
            if (x==null)
                throw new NumberFormatException();
            ret=Integer.parseInt(x);
        }
        catch (NumberFormatException e)
        {
            throw new SAXParseException("Expected a number attribute "+attr+" of element "+elem,
                    fLocator);            
        }
        return ret;
	}
	
	protected void fillKeyTable()
	{
	    // generated from e32keys.h
	    fKeycodesMap.put("EStdKeyNull", new Integer(0x00));
	    fKeycodesMap.put("EStdKeyBackspace", new Integer(0x01));
	    fKeycodesMap.put("EStdKeyTab", new Integer(0x02));
	    fKeycodesMap.put("EStdKeyEnter", new Integer(0x03));
	    fKeycodesMap.put("EStdKeyEscape", new Integer(0x04));
	    fKeycodesMap.put("EStdKeySpace", new Integer(0x05));
	    fKeycodesMap.put("EStdKeyPrintScreen", new Integer(0x06));
	    fKeycodesMap.put("EStdKeyPause", new Integer(0x07));
	    fKeycodesMap.put("EStdKeyHome", new Integer(0x08));
	    fKeycodesMap.put("EStdKeyEnd", new Integer(0x09));
	    fKeycodesMap.put("EStdKeyPageUp", new Integer(0x0a));
	    fKeycodesMap.put("EStdKeyPageDown", new Integer(0x0b));
	    fKeycodesMap.put("EStdKeyInsert", new Integer(0x0c));
	    fKeycodesMap.put("EStdKeyDelete", new Integer(0x0d));
	    fKeycodesMap.put("EStdKeyLeftArrow", new Integer(0x0e));
	    fKeycodesMap.put("EStdKeyRightArrow", new Integer(0x0f));
	    fKeycodesMap.put("EStdKeyUpArrow", new Integer(0x10));
	    fKeycodesMap.put("EStdKeyDownArrow", new Integer(0x11));
	    fKeycodesMap.put("EStdKeyLeftShift", new Integer(0x12));
	    fKeycodesMap.put("EStdKeyRightShift", new Integer(0x13));
	    fKeycodesMap.put("EStdKeyLeftAlt", new Integer(0x14));
	    fKeycodesMap.put("EStdKeyRightAlt", new Integer(0x15));
	    fKeycodesMap.put("EStdKeyLeftCtrl", new Integer(0x16));
	    fKeycodesMap.put("EStdKeyRightCtrl", new Integer(0x17));
	    fKeycodesMap.put("EStdKeyLeftFunc", new Integer(0x18));
	    fKeycodesMap.put("EStdKeyRightFunc", new Integer(0x19));
	    fKeycodesMap.put("EStdKeyCapsLock", new Integer(0x1a));
	    fKeycodesMap.put("EStdKeyNumLock", new Integer(0x1b));
	    fKeycodesMap.put("EStdKeyScrollLock", new Integer(0x1c));
	    fKeycodesMap.put("EStdKeyF1", new Integer(0x60));
	    fKeycodesMap.put("EStdKeyF2", new Integer(0x61));
	    fKeycodesMap.put("EStdKeyF3", new Integer(0x62));
	    fKeycodesMap.put("EStdKeyF4", new Integer(0x63));
	    fKeycodesMap.put("EStdKeyF5", new Integer(0x64));
	    fKeycodesMap.put("EStdKeyF6", new Integer(0x65));
	    fKeycodesMap.put("EStdKeyF7", new Integer(0x66));
	    fKeycodesMap.put("EStdKeyF8", new Integer(0x67));
	    fKeycodesMap.put("EStdKeyF9", new Integer(0x68));
	    fKeycodesMap.put("EStdKeyF10", new Integer(0x69));
	    fKeycodesMap.put("EStdKeyF11", new Integer(0x6a));
	    fKeycodesMap.put("EStdKeyF12", new Integer(0x6b));
	    fKeycodesMap.put("EStdKeyF13", new Integer(0x6c));
	    fKeycodesMap.put("EStdKeyF14", new Integer(0x6d));
	    fKeycodesMap.put("EStdKeyF15", new Integer(0x6e));
	    fKeycodesMap.put("EStdKeyF16", new Integer(0x6f));
	    fKeycodesMap.put("EStdKeyF17", new Integer(0x70));
	    fKeycodesMap.put("EStdKeyF18", new Integer(0x71));
	    fKeycodesMap.put("EStdKeyF19", new Integer(0x72));
	    fKeycodesMap.put("EStdKeyF20", new Integer(0x73));
	    fKeycodesMap.put("EStdKeyF21", new Integer(0x74));
	    fKeycodesMap.put("EStdKeyF22", new Integer(0x75));
	    fKeycodesMap.put("EStdKeyF23", new Integer(0x76));
	    fKeycodesMap.put("EStdKeyF24", new Integer(0x77));
	    fKeycodesMap.put("EStdKeyXXX", new Integer(0x78));
	    fKeycodesMap.put("EStdKeyComma", new Integer(0x79));
	    fKeycodesMap.put("EStdKeyFullStop", new Integer(0x7a));
	    fKeycodesMap.put("EStdKeyForwardSlash", new Integer(0x7b));
	    fKeycodesMap.put("EStdKeyBackSlash", new Integer(0x7c));
	    fKeycodesMap.put("EStdKeySemiColon", new Integer(0x7d));
	    fKeycodesMap.put("EStdKeySingleQuote", new Integer(0x7e));
	    fKeycodesMap.put("EStdKeyHash", new Integer(0x7f));
	    fKeycodesMap.put("EStdKeySquareBracketLeft", new Integer(0x80));
	    fKeycodesMap.put("EStdKeySquareBracketRight", new Integer(0x81));
	    fKeycodesMap.put("EStdKeyMinus", new Integer(0x82));
	    fKeycodesMap.put("EStdKeyEquals", new Integer(0x83));
	    fKeycodesMap.put("EStdKeyNkpForwardSlash", new Integer(0x84));
	    fKeycodesMap.put("EStdKeyNkpAsterisk", new Integer(0x85));
	    fKeycodesMap.put("EStdKeyNkpMinus", new Integer(0x86));
	    fKeycodesMap.put("EStdKeyNkpPlus", new Integer(0x87));
	    fKeycodesMap.put("EStdKeyNkpEnter", new Integer(0x88));
	    fKeycodesMap.put("EStdKeyNkp1", new Integer(0x89));
	    fKeycodesMap.put("EStdKeyNkp2", new Integer(0x8a));
	    fKeycodesMap.put("EStdKeyNkp3", new Integer(0x8b));
	    fKeycodesMap.put("EStdKeyNkp4", new Integer(0x8c));
	    fKeycodesMap.put("EStdKeyNkp5", new Integer(0x8d));
	    fKeycodesMap.put("EStdKeyNkp6", new Integer(0x8e));
	    fKeycodesMap.put("EStdKeyNkp7", new Integer(0x8f));
	    fKeycodesMap.put("EStdKeyNkp8", new Integer(0x90));
	    fKeycodesMap.put("EStdKeyNkp9", new Integer(0x91));
	    fKeycodesMap.put("EStdKeyNkp0", new Integer(0x92));
	    fKeycodesMap.put("EStdKeyNkpFullStop", new Integer(0x93));
	    fKeycodesMap.put("EStdKeyMenu", new Integer(0x94));
	    fKeycodesMap.put("EStdKeyBacklightOn", new Integer(0x95));
	    fKeycodesMap.put("EStdKeyBacklightOff", new Integer(0x96));
	    fKeycodesMap.put("EStdKeyBacklightToggle", new Integer(0x97));
	    fKeycodesMap.put("EStdKeyIncContrast", new Integer(0x98));
	    fKeycodesMap.put("EStdKeyDecContrast", new Integer(0x99));
	    fKeycodesMap.put("EStdKeySliderDown", new Integer(0x9a));
	    fKeycodesMap.put("EStdKeySliderUp", new Integer(0x9b));
	    fKeycodesMap.put("EStdKeyDictaphonePlay", new Integer(0x9c));
	    fKeycodesMap.put("EStdKeyDictaphoneStop", new Integer(0x9d));
	    fKeycodesMap.put("EStdKeyDictaphoneRecord", new Integer(0x9e));
	    fKeycodesMap.put("EStdKeyHelp", new Integer(0x9f));
	    fKeycodesMap.put("EStdKeyOff", new Integer(0xa0));
	    fKeycodesMap.put("EStdKeyDial", new Integer(0xa1));
	    fKeycodesMap.put("EStdKeyIncVolume", new Integer(0xa2));
	    fKeycodesMap.put("EStdKeyDecVolume", new Integer(0xa3));
	    fKeycodesMap.put("EStdKeyDevice0", new Integer(0xa4));
	    fKeycodesMap.put("EStdKeyDevice1", new Integer(0xa5));
	    fKeycodesMap.put("EStdKeyDevice2", new Integer(0xa6));
	    fKeycodesMap.put("EStdKeyDevice3", new Integer(0xa7));
	    fKeycodesMap.put("EStdKeyDevice4", new Integer(0xa8));
	    fKeycodesMap.put("EStdKeyDevice5", new Integer(0xa9));
	    fKeycodesMap.put("EStdKeyDevice6", new Integer(0xaa));
	    fKeycodesMap.put("EStdKeyDevice7", new Integer(0xab));
	    fKeycodesMap.put("EStdKeyDevice8", new Integer(0xac));
	    fKeycodesMap.put("EStdKeyDevice9", new Integer(0xad));
	    fKeycodesMap.put("EStdKeyDeviceA", new Integer(0xae));
	    fKeycodesMap.put("EStdKeyDeviceB", new Integer(0xaf));
	    fKeycodesMap.put("EStdKeyDeviceC", new Integer(0xb0));
	    fKeycodesMap.put("EStdKeyDeviceD", new Integer(0xb1));
	    fKeycodesMap.put("EStdKeyDeviceE", new Integer(0xb2));
	    fKeycodesMap.put("EStdKeyDeviceF", new Integer(0xb3));
	    fKeycodesMap.put("EStdKeyApplication0", new Integer(0xb4));
	    fKeycodesMap.put("EStdKeyApplication1", new Integer(0xb5));
	    fKeycodesMap.put("EStdKeyApplication2", new Integer(0xb6));
	    fKeycodesMap.put("EStdKeyApplication3", new Integer(0xb7));
	    fKeycodesMap.put("EStdKeyApplication4", new Integer(0xb8));
	    fKeycodesMap.put("EStdKeyApplication5", new Integer(0xb9));
	    fKeycodesMap.put("EStdKeyApplication6", new Integer(0xba));
	    fKeycodesMap.put("EStdKeyApplication7", new Integer(0xbb));
	    fKeycodesMap.put("EStdKeyApplication8", new Integer(0xbc));
	    fKeycodesMap.put("EStdKeyApplication9", new Integer(0xbd));
	    fKeycodesMap.put("EStdKeyApplicationA", new Integer(0xbe));
	    fKeycodesMap.put("EStdKeyApplicationB", new Integer(0xbf));
	    fKeycodesMap.put("EStdKeyApplicationC", new Integer(0xc0));
	    fKeycodesMap.put("EStdKeyApplicationD", new Integer(0xc1));
	    fKeycodesMap.put("EStdKeyApplicationE", new Integer(0xc2));
	    fKeycodesMap.put("EStdKeyApplicationF", new Integer(0xc3));
	    fKeycodesMap.put("EStdKeyYes", new Integer(0xc4));
	    fKeycodesMap.put("EStdKeyNo", new Integer(0xc5));
	    fKeycodesMap.put("EStdKeyIncBrightness", new Integer(0xc6));
	    fKeycodesMap.put("EStdKeyDecBrightness", new Integer(0xc7));
	}

	protected String prepareKey(String code)
	{
	    if (fSymbianVersion>=SymbianSDKEntry.SYMBIAN_7_0)
	        return code;
	    
        int codeNum=-1;
        
        Integer i=(Integer)fKeycodesMap.get(code);
        if (i!=null)
            codeNum=i.intValue();
        
        if (code.length()==1)
        {
            codeNum=(int)code.charAt(0);
            code="E"+code+"Key_";
        }

        if (codeNum!=-1)
	        fRewriter.addAppendWriter(new LineWriter("Default "+code+" "+codeNum));
        	else SymbdevPlugin.getDefault().getLog().log(new Status(IStatus.WARNING,
                    SymbdevPlugin.getUniqueIdentifier(), SymbdevPlugin.WARNING_SKIN,
                    "Unknown key: "+code, null));
        
        return code;
	}
	
	protected void addRect(String code, int x, int y, int w, int h)
	{
	    if (fSymbianVersion>=SymbianSDKEntry.SYMBIAN_7_0)
	    {
	        fRewriter.addAppendWriter(new LineWriter("VirtualKey "+code+"\trect "+x+","+y+"\t"+w+","+h));
	    } else {
	        fRewriter.addAppendWriter(new LineWriter("MouseTarget Rect\t"+x+" "+y+"\t"+(x+w)+" "+(y+h)+"\t"+code));
	    }
	}
	
	public void startElement(String uri, String localName,
            String qName, Attributes attributes)
     		throws SAXException
	{
	    fLevel++;
	    if (qName.equals("bitmap"))
	    {
	        checkMainTag(null, "bitmap", true);
			String bitmapSrc=attributes.getValue("src");
			try {
			    copyBitmap(bitmapSrc);
			}
			catch (IOException e)
			{
			    throw new SAXParseException("Background image file copy error: "+e.getMessage(), fLocator, e);
			}
			fRewriter.addRewriter("FasciaBitmap", new SetParameterRewriter("FasciaBitmap", BMP_FILE, true));
	    }
	    
	    if (qName.equals("screen"))
	    {
	        if (checkMainTag("bitmap", "screen", false))
	        {
	            String x=attributes.getValue("x");
	            String y=attributes.getValue("y");
	            
	            int ix, iy;
	            try {
	                if (x==null || y==null)
	                    throw new NumberFormatException();
	                ix=Integer.parseInt(x);
	                iy=Integer.parseInt(y);
	            }
	            catch (NumberFormatException e)
	            {
	                throw new SAXParseException("Error in attributes x or y", fLocator, e);
	            }
	            
				fRewriter.addRewriter("ScreenOffsetX", new SetParameterRewriter("ScreenOffsetX", x, true));
				fRewriter.addRewriter("ScreenOffsetY", new SetParameterRewriter("ScreenOffsetY", y, true));	            
	        }
	    }

	    if (qName.equals("key"))
	    {
	        checkMainTag(null, "key", true);
	        String code=attributes.getValue("code");
	        if (code==null)
	            throw new SAXParseException("No code attribute for key", fLocator);
	        code=prepareKey(code);
	        fMainTagData=code;
	    }
	    
	    if (qName.equals("rect"))
	    {
	        checkMainTag("key", "rect", true);
	        int x, y, w, h;
	        x=getNumber(attributes.getValue("x"), "x", "rect");
	        y=getNumber(attributes.getValue("y"), "y", "rect");
	        w=getNumber(attributes.getValue("width"), "w", "rect");
	        h=getNumber(attributes.getValue("height"), "h", "rect");

	        addRect((String)fMainTagData, x, y, w, h);
	    }

	    if (qName.equals("ellipse"))
	    {
	        checkMainTag("key", "ellipse", true);
	        
	        int x, y, xrad, yrad;
	        x=getNumber(attributes.getValue("x"), "x", "ellpise");
	        y=getNumber(attributes.getValue("y"), "y", "ellipse");
	        xrad=getNumber(attributes.getValue("xdiam"), "xdiam", "ellipse")/2;
	        yrad=getNumber(attributes.getValue("ydiam"), "ydiam", "ellipse")/2;
	        
	        // simulate an ellipse with three rects
	        addRect((String)fMainTagData, (int)(x-0.500*xrad), (int)(y-0.866*yrad), (int)(2*0.500*xrad), (int)(2*0.866*yrad));
	        addRect((String)fMainTagData, (int)(x-0.707*xrad), (int)(y-0.707*yrad), (int)(2*0.707*xrad), (int)(2*0.707*yrad));
	        addRect((String)fMainTagData, (int)(x-0.866*xrad), (int)(y-0.500*yrad), (int)(2*0.866*xrad), (int)(2*0.500*yrad));
	    }	    
	    
	    if (fLevel==2)
	        fMainTag=qName;
	}	
	
	
    
    /* (non-Javadoc)
     * @see org.xml.sax.ContentHandler#endElement(java.lang.String, java.lang.String, java.lang.String)
     */
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        fLevel--;
        if (fLevel<=1)
        {
            fMainTag=null;
            fMainTagData=null;
        }
    }
    
	int fLevel=0;
	int fSymbianVersion;
    String fMainTag;
    Object fMainTagData;
    EpocIniRewriter fRewriter;
    String fBitmapSrc;
    IPath fSkinRoot;
    IPath fSdkRoot;
    HashMap fKeycodesMap;
}
