/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2008 jOpenDocument, by ILM Informatique. All rights reserved.
 * 
 * The contents of this file are subject to the terms of the GNU
 * General Public License Version 3 only ("GPL").  
 * You may not use this file except in compliance with the License. 
 * You can obtain a copy of the License at http://www.gnu.org/licenses/gpl-3.0.html
 * See the License for the specific language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each file.
 * 
 */

package org.jopendocument.dom.spreadsheet;

import org.jopendocument.dom.NS;
import org.jopendocument.dom.ODPackage;
import org.jopendocument.dom.ODSingleXMLDocument;
import org.jopendocument.dom.OOUtils;
import org.jopendocument.dom.OOXML;
import org.jopendocument.dom.spreadsheet.SheetTableModel.MutableTableModel;

import java.awt.Point;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;

import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

import junit.framework.TestCase;

import org.xml.sax.SAXException;

public class SheetTest extends TestCase {

    static final DefaultTableModel tm;
    static {
        tm = new DefaultTableModel();
        tm.addColumn("col1");
        tm.addColumn("col2");
        tm.addColumn("col3");

        tm.addRow(new Object[] { "un1", new Float(-5.32), new Integer(123) });
    }

    private Sheet sheet;
    private Sheet realSheet;

    protected void setUp() throws Exception {
        this.sheet = SpreadSheet.createEmpty(tm, NS.getOOo()).getSheet(0);
        this.realSheet = SpreadSheet.create(new ODPackage(this.getClass().getResourceAsStream("test.ods"))).getSheet(0);
    }

    protected void tearDown() throws Exception {
        this.sheet = null;
        this.realSheet = null;
    }

    public void testCreate() throws IOException {
        // save
        final TableModel model = new DefaultTableModel(new Object[][] { { "Data", "DataCol2" } }, new Object[] { "Col1", "Col2" });
        final SpreadSheet created = SpreadSheet.createEmpty(model);
        final File saved = created.saveAs(File.createTempFile("testCreate", null));
        saved.deleteOnExit();
        // then read
        final Sheet sheetRead = SpreadSheet.createFromFile(saved).getSheet(0);
        // and check that the data is correct
        assertEquals(2, sheetRead.getRowCount());
        assertEquals("Col1", sheetRead.getValueAt("A1"));
        // data w/o column names
        final SheetTableModel<SpreadSheet> tmRead = sheetRead.getTableModel(0, 1);
        assertEquals(2, tmRead.getColumnCount());
        assertEquals(1, tmRead.getRowCount());
        assertEquals("DataCol2", tmRead.getValueAt(0, 1));
    }

    public void testName() throws Exception {
        final String n = "users";
        final SpreadSheet realSpread = this.realSheet.getSpreadSheet();
        assertEquals(n, realSpread.getSheet(n).getName());

        // the sheet can be accessed with a changed name
        final String newName = "newName";
        assertFalse(newName.equals(this.realSheet.getName()));
        this.realSheet.setName(newName);
        assertTrue(newName.equals(this.realSheet.getName()));
        assertSame(this.realSheet, realSpread.getSheet(newName));

        // test add
        final Sheet addSheet = realSpread.addSheet(1, "newSheet");
        assertSame(addSheet, realSpread.getSheet(1));

        // test rm
        final int count = realSpread.getSheetCount();
        this.realSheet.detach();
        assertNull(realSpread.getSheet(this.realSheet.getName()));
        assertEquals(count - 1, realSpread.getSheetCount());
    }

    /*
     * Test method for 'org.jopendocument.dom.spreadsheet.Sheet.setValueAt(Object, int, int)'
     */
    public void testSetValueAt() {
        assertEquals("col1", this.sheet.getValueAt(0, 0));
        this.sheet.setValueAt("test", 0, 0);
        assertEquals("test", this.sheet.getValueAt(0, 0));

        final Calendar now = Calendar.getInstance();
        this.sheet.setValueAt(now.getTime(), 0, 0);
        final Object valueAt = this.sheet.getValueAt(0, 0);
        // OpenDocument can encode time up to the ms
        assertEquals(now.getTime(), valueAt);

        try {
            this.realSheet.getCellAt("C3");
            fail("should have failed since C3 is covered");
        } catch (IllegalArgumentException e) {
            // ok
        }
        this.realSheet.getCellAt("B3").unmerge();
        // should now succeed
        this.realSheet.getCellAt("C3").setValue("foo");
    }

    /*
     * Test method for 'org.jopendocument.dom.spreadsheet.Sheet.getValueAt(int, int)'
     */
    public void testGetValueAt() {
        assertEquals(new Float(123), this.sheet.getValueAt(2, 1));

        // testGetValueAtString()
        // oo ne connait les int seulement les float
        assertEquals(new Float(123), this.sheet.getValueAt("C2"));
        assertFalse(new Integer(123).equals(this.sheet.getValueAt("C2")));

        final Calendar c = Calendar.getInstance();
        c.clear();
        c.set(2008, 11, 25);
        assertEquals(c.getTime(), this.realSheet.getValueAt("F3"));

        assertSame(this.realSheet.getCellAt("F3"), this.realSheet.getSpreadSheet().getCellAt(this.realSheet.getName() + ".$F$3"));
    }

    /*
     * Test method for 'org.jopendocument.dom.spreadsheet.Sheet.getRowCount()'
     */
    public void testGetCount() {
        // col labels + 1 row
        assertEquals(2, this.sheet.getRowCount());
        assertEquals(3, this.sheet.getColumnCount());
    }

    /*
     * Test method for 'org.jopendocument.dom.spreadsheet.Sheet.ensureColumnCount(int)'
     */
    public void testEnsureColumnCount() {
        final int before = this.sheet.getColumnCount();
        final int newSize = before - 2;
        this.sheet.ensureColumnCount(newSize);
        assertEquals(before, this.sheet.getColumnCount());
    }

    public void testSetColumnCount() throws Exception {
        // test in-memory
        final int newSize = this.sheet.getColumnCount() + 5;
        try {
            this.sheet.setValueAt("test", newSize - 1, 0);
            fail("should have thrown IndexOutOfBoundsException");
        } catch (IndexOutOfBoundsException exn) {
            // normal
        }
        this.sheet.setColumnCount(newSize);
        this.sheet.setValueAt("test", newSize - 1, 0);

        // test with real file
        final int origCount = this.realSheet.getColumnCount();
        try {
            this.realSheet.setValueAt("over", origCount + 10 - 1, 1);
            fail("should throw exn since we try to write past the limit");
        } catch (RuntimeException e) {
            // ok
        }
        this.realSheet.setColumnCount(origCount + 10);
        this.realSheet.setValueAt("over", origCount + 10 - 1, 1);
        final ByteArrayOutputStream out = new ByteArrayOutputStream(1024 * 15);
        this.realSheet.getSpreadSheet().getPackage().save(out);
        final SpreadSheet reRead = SpreadSheet.create(new ODPackage(new ByteArrayInputStream(out.toByteArray())));
        assertEquals("over", reRead.getSheet(0).getValueAt(origCount + 10 - 1, 1));

        assertNull(this.isValidXML(this.realSheet.getSpreadSheet()));
    }

    public void testRemoveCol() throws Exception {
        this.realSheet.setColumnCount(9, -1, true);
        assertEquals(9, this.realSheet.getColumnCount());
        assertFalse(this.realSheet.getImmutableCellAt("C3").isValid());
        this.realSheet.removeColumn(1, true);
        assertEquals(8, this.realSheet.getColumnCount());
        // B3 was covering C3, but since the B column is gone C3 is incovered
        assertTrue(this.realSheet.getImmutableCellAt("C3").isValid());

        final ODSingleXMLDocument doc = new ODPackage(this.getClass().getResourceAsStream("../test.odt")).toSingle();
        final Table<ODSingleXMLDocument> t = new Table<ODSingleXMLDocument>(doc, doc.getDescendantByName("table:table", "JODTestTable"));
        final float colW = t.getColumn(1).getWidth();
        float tableW = t.getWidth();
        t.removeColumn(1, false);
        // normal float error
        assertTrue(Math.abs(tableW - colW - t.getWidth()) <= Math.ulp(t.getWidth()));

        tableW = t.getWidth();
        t.removeColumn(1, true);
        assertEquals(tableW, t.getWidth());
    }

    public void testSetRowCount() throws IOException {
        this.realSheet.setRowCount(3);
        final SpreadSheet reParsed = new SpreadSheet(this.realSheet.getODDocument().getContent(), null);
        assertEquals(3, reParsed.getSheet(0).getRowCount());
    }

    /*
     * Test method for 'org.jopendocument.dom.spreadsheet.Sheet.getTableModel(int, int)'
     */
    public void testGetTableModel() {
        SheetTableModel<SpreadSheet> t = this.sheet.getTableModel(1, 0);
        assertEquals(tm.getColumnCount() - 1, t.getColumnCount());
        // +1: col labels
        assertEquals(tm.getRowCount() + 1, t.getRowCount());
        assertEquals(new Float(-5.32), t.getValueAt(1, 0));

        // empty hence no style
        assertNull(t.getImmutableCellAt(0, 0).getStyle());
        try {
            t.getValueAt(1, t.getColumnCount());
            fail("should have thrown exn");
        } catch (IndexOutOfBoundsException e) {
            // ok
        }
    }

    /*
     * Test method for 'org.jopendocument.dom.spreadsheet.Sheet.getMutableTableModel(int, int)'
     */
    public void testGetMutableTableModel() {
        this.sheet.getMutableTableModel(1, 0).setValueAt("test", 0, 1);
        assertEquals("test", this.sheet.getCellAt("C1").getValue());

        final MutableTableModel<SpreadSheet> tm = this.realSheet.getSpreadSheet().getTableModel("rights");
        assertEquals("K1", tm.getValueAt(13, 6));

        assertEquals("#00ffff", tm.getCellAt(0, 0).getStyle().getBackgroundColor());
        assertEquals("#ffcc99", tm.getCellAt(1, 2).getStyle().getBackgroundColor());

        try {
            tm.setValueAt("foo", 0, tm.getColumnCount());
            fail("should have thrown exn");
        } catch (IndexOutOfBoundsException e) {
            // ok
        }
    }

    /*
     * Test method for 'org.jopendocument.dom.spreadsheet.Sheet.merge(TableModel, int, int)'
     */
    public void testMerge() {

    }

    public void testResolve() {
        assertEquals(new Point(0, 22), Sheet.resolve("A23"));
        assertEquals(new Point(26, 33), Sheet.resolve("AA34"));
        assertNull(Sheet.resolve("A23A"));
        assertNull(Sheet.resolve("test"));
        assertNull(Sheet.resolve("23"));

        assertEquals(77, Sheet.toInt("BZ"));
    }

    public void testDuplicateRows() throws Exception {
        this.realSheet.duplicateRows(8, 3, 2);
        assertEquals(this.realSheet.getValueAt("B11"), this.realSheet.getValueAt("B14"));
        assertEquals("Comptabilité", this.realSheet.getValueAt("H9"));
        // two copies
        assertEquals(this.realSheet.getValueAt("H9"), this.realSheet.getValueAt("H12"));
        assertEquals(this.realSheet.getValueAt("H9"), this.realSheet.getValueAt("H15"));
    }

    public void testXY() throws Exception {
        final int y = 35;
        {
            final MutableCell c = this.realSheet.getCellAt(2, y);
            assertEquals("LECTURE SEULE", c.getValue());
            assertEquals(2, c.getX());
            assertEquals(y, c.getY());
        }
        // offset by 6
        this.realSheet.duplicateRows(8, 3, 2);

        {
            final MutableCell c = this.realSheet.getCellAt(2, y + 6);
            assertEquals("LECTURE SEULE", c.getValue());
            assertEquals(2, c.getX());
            assertEquals(y + 6, c.getY());
        }
    }

    public void testStyle() throws Exception {
        // even though the style-name is null
        assertNull(this.realSheet.getImmutableCellAt(2, 10).getStyleAttr());
        final String style = this.realSheet.getStyleNameAt(2, 10);
        assertNotNull(style);
        // the correct style can still be found
        assertEquals("#00ffff", this.realSheet.getCellAt(2, 10).getStyle().getBackgroundColor());
        // also from MutableCell
        assertEquals(style, this.realSheet.getCellAt(2, 10).getStyleName());

        final CellStyle centeredInBold = this.realSheet.getCellAt("B9").getStyle();
        assertEquals("center", centeredInBold.getParagraphProperties().getAlignment());
        assertEquals("bold", centeredInBold.getTextProperties().getWeight());

        // column styles
        assertEquals(54.38f, this.realSheet.getColumn(1).getWidth());
        final SpreadSheet sxc = SpreadSheet.create(new ODPackage(this.getClass().getResourceAsStream("test.sxc")));
        assertEquals(33.85f, sxc.getSheet(0).getColumn(1).getWidth());
    }

    private String isValidXML(SpreadSheet ssheet) throws UnsupportedEncodingException, SAXException {
        return OOXML.get(OOUtils.OD).isValid(ssheet.getContent());
    }
}
