/*
 * JGrass - Free Open Source Java GIS http://www.jgrass.org 
 * (C) HydroloGIS - www.hydrologis.com 
 * 
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Library General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option) any
 * later version.
 * 
 * This library 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 Library General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Library General Public License
 * along with this library; if not, write to the Free Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package org.jgrasstools.hortonmachine.modules.demmanipulation.splitsubbasin;

import static org.jgrasstools.gears.libs.modules.JGTConstants.isNovalue;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_AUTHORCONTACTS;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_AUTHORNAMES;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_DESCRIPTION;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_KEYWORDS;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_LABEL;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_LICENSE;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_NAME;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_STATUS;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_inFlow_DESCRIPTION;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_inHack_DESCRIPTION;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_outNetnum_DESCRIPTION;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_outSubbasins_DESCRIPTION;
import static org.jgrasstools.hortonmachine.i18n.HortonMessages.OMSSPLITSUBBASINS_pHackorder_DESCRIPTION;

import java.awt.image.RenderedImage;
import java.awt.image.WritableRaster;

import javax.media.jai.iterator.RandomIter;
import javax.media.jai.iterator.RandomIterFactory;
import javax.media.jai.iterator.WritableRandomIter;

import oms3.annotations.Author;
import oms3.annotations.Description;
import oms3.annotations.Execute;
import oms3.annotations.In;
import oms3.annotations.Keywords;
import oms3.annotations.Label;
import oms3.annotations.License;
import oms3.annotations.Name;
import oms3.annotations.Out;
import oms3.annotations.Status;

import org.geotools.coverage.grid.GridCoverage2D;
import org.jgrasstools.gears.libs.exceptions.ModelsIllegalargumentException;
import org.jgrasstools.gears.libs.modules.JGTConstants;
import org.jgrasstools.gears.libs.modules.JGTModel;
import org.jgrasstools.gears.libs.modules.ModelsEngine;
import org.jgrasstools.gears.libs.modules.ModelsSupporter;
import org.jgrasstools.gears.utils.RegionMap;
import org.jgrasstools.gears.utils.coverage.CoverageUtilities;
import org.jgrasstools.gears.utils.math.NumericsUtilities;

@Description(OMSSPLITSUBBASINS_DESCRIPTION)
@Author(name = OMSSPLITSUBBASINS_AUTHORNAMES, contact = OMSSPLITSUBBASINS_AUTHORCONTACTS)
@Keywords(OMSSPLITSUBBASINS_KEYWORDS)
@Label(OMSSPLITSUBBASINS_LABEL)
@Name(OMSSPLITSUBBASINS_NAME)
@Status(OMSSPLITSUBBASINS_STATUS)
@License(OMSSPLITSUBBASINS_LICENSE)
public class OmsSplitSubbasins extends JGTModel {
    @Description(OMSSPLITSUBBASINS_inFlow_DESCRIPTION)
    @In
    public GridCoverage2D inFlow = null;

    @Description(OMSSPLITSUBBASINS_inHack_DESCRIPTION)
    @In
    public GridCoverage2D inHack = null;

    @Description(OMSSPLITSUBBASINS_pHackorder_DESCRIPTION)
    @In
    public Double pHackorder = null;

    @Description(OMSSPLITSUBBASINS_outNetnum_DESCRIPTION)
    @Out
    public GridCoverage2D outNetnum = null;

    @Description(OMSSPLITSUBBASINS_outSubbasins_DESCRIPTION)
    @Out
    public GridCoverage2D outSubbasins = null;

    private int[][] dir = ModelsSupporter.DIR_WITHFLOW_ENTERING;

    private int nCols;
    private int nRows;
    private double hackOrder;

    @Execute
    public void process() {
        if (!concatOr(outSubbasins == null, doReset)) {
            return;
        }
        checkNull(inFlow, inHack, pHackorder);

        RegionMap regionMap = CoverageUtilities.getRegionParamsFromGridCoverage(inFlow);
        nCols = regionMap.getCols();
        nRows = regionMap.getRows();

        hackOrder = pHackorder;

        RenderedImage flowRI = inFlow.getRenderedImage();
        WritableRaster flowWR = CoverageUtilities.renderedImage2WritableRaster(flowRI, true);
        RenderedImage hacksRI = inHack.getRenderedImage();
        WritableRaster hackWR = CoverageUtilities.renderedImage2WritableRaster(hacksRI, true);

        WritableRandomIter flowIter = RandomIterFactory.createWritable(flowWR, null);
        WritableRandomIter hacksIter = RandomIterFactory.createWritable(hackWR, null);

        WritableRaster netImage = CoverageUtilities.createDoubleWritableRaster(nCols, nRows, null, null,
                JGTConstants.doubleNovalue);
        WritableRandomIter netIter = RandomIterFactory.createWritable(netImage, null);
        net(hacksIter, netIter);

        WritableRaster netNumberWR = netNumber(flowIter, hacksIter, netIter);
        WritableRandomIter netNumberIter = RandomIterFactory.createWritable(netNumberWR, null);
        WritableRaster subbasinWR = ModelsEngine.extractSubbasins(flowIter, netIter, netNumberIter, nRows, nCols, pm);

        outNetnum = CoverageUtilities.buildCoverage("netnum", netNumberWR, regionMap, inFlow.getCoordinateReferenceSystem()); //$NON-NLS-1$
        outSubbasins = CoverageUtilities.buildCoverage("subbasins", subbasinWR, regionMap, inFlow.getCoordinateReferenceSystem()); //$NON-NLS-1$
    }
    /**
     * Return the map of the network with only the river of the choosen order.
     * 
     * @param hacksIter the hack stream map.
     * @param netIter the network map to build on the required hack orders.
     * @return the map of the network with the choosen order.
     */
    private void net( WritableRandomIter hacksIter, WritableRandomIter netIter ) {
        // calculates the max order of basin (max hackstream value)
        pm.beginTask("Extraction of rivers of chosen order...", nRows);
        for( int r = 0; r < nRows; r++ ) {
            for( int c = 0; c < nCols; c++ ) {
                double value = hacksIter.getSampleDouble(c, r, 0);
                if (!isNovalue(value)) {
                    /*
                     * if the hack value is in the asked range 
                     * => keep it as net
                     */
                    if (value <= hackOrder) {
                        netIter.setSample(c, r, 0, 2);
                    } else {
                        hacksIter.setSample(c, r, 0, JGTConstants.doubleNovalue);
                    }
                }
            }
            pm.worked(1);
        }
        pm.done();
    }

    private WritableRaster netNumber( RandomIter flowIter, RandomIter hacksIter, RandomIter netIter ) {
        int drainingPixelNum = 0;
        int[] flowColRow = new int[2];

        WritableRaster netNumberingImage = CoverageUtilities.createDoubleWritableRaster(nCols, nRows, null, null, 0.0);
        WritableRandomIter netNumberRandomIter = RandomIterFactory.createWritable(netNumberingImage, null);

        int n = 0;
        pm.beginTask("Numbering network...", nRows);
        for( int r = 0; r < nRows; r++ ) {
            for( int c = 0; c < nCols; c++ ) {
                flowColRow[0] = c;
                flowColRow[1] = r;
                if (!isNovalue(netIter.getSampleDouble(c, r, 0)) && flowIter.getSampleDouble(c, r, 0) != 10.0
                        && NumericsUtilities.dEq(netNumberRandomIter.getSampleDouble(c, r, 0), 0.0)) {

                    boolean isSource = true;
                    for( int k = 1; k <= 8; k++ ) {
                        boolean isDraining = flowIter.getSampleDouble(flowColRow[0] + dir[k][1], flowColRow[1] + dir[k][0], 0) == dir[k][2];
                        boolean isOnNet = !isNovalue(netIter.getSampleDouble(flowColRow[0] + dir[k][1],
                                flowColRow[1] + dir[k][0], 0));
                        if (isDraining && isOnNet) {
                            isSource = false;
                            break;
                        }
                    }

                    /*
                     * if it is source pixel, go down
                     */
                    if (isSource) {
                        n++;
                        netNumberRandomIter.setSample(c, r, 0, n);
                        if (!ModelsEngine.go_downstream(flowColRow, flowIter.getSampleDouble(flowColRow[0], flowColRow[1], 0)))
                            throw new ModelsIllegalargumentException("go_downstream failure...", this);
                        /*
                         * while it is on the network, go downstream
                         */
                        while( !isNovalue(flowIter.getSampleDouble(flowColRow[0], flowColRow[1], 0))
                                && netNumberRandomIter.getSampleDouble(flowColRow[0], flowColRow[1], 0) == 0 ) {
                            /*
                             * calculate how many pixels drain into the current pixel.
                             */
                            drainingPixelNum = 0;
                            for( int k = 1; k <= 8; k++ ) {
                                if (!isNovalue(netIter.getSampleDouble(flowColRow[0] + dir[k][1], flowColRow[1] + dir[k][0], 0))
                                        && flowIter.getSampleDouble(flowColRow[0] + dir[k][1], flowColRow[1] + dir[k][0], 0) == dir[k][2]) {
                                    drainingPixelNum++;
                                }
                            }

                            if (drainingPixelNum > 1) {
                                n++;
                            }
                            netNumberRandomIter.setSample(flowColRow[0], flowColRow[1], 0, n);

                            if (!ModelsEngine
                                    .go_downstream(flowColRow, flowIter.getSampleDouble(flowColRow[0], flowColRow[1], 0)))
                                throw new ModelsIllegalargumentException("go_downstream failure...", this);
                        }
                    }
                }
            }
            pm.worked(1);
        }
        pm.done();

        return netNumberingImage;
    }

}
