/*
 * Project:  NextGIS Mobile
 * Purpose:  Mobile GIS for Android.
 * Author:   Dmitry Baryshnikov (aka Bishop), [email protected]
 * Author:   NikitaFeodonit, [email protected]
 * Author:   Stanislav Petriakov, [email protected]
 * *****************************************************************************
 * Copyright (c) 2012-2017 NextGIS, [email protected]
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation, either version 3 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 Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.nextgis.maplib.display;

import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.text.TextUtils;

import com.nextgis.maplib.api.ITextStyle;
import com.nextgis.maplib.datasource.GeoGeometry;
import com.nextgis.maplib.datasource.GeoLineString;
import com.nextgis.maplib.datasource.GeoMultiLineString;
import com.nextgis.maplib.datasource.GeoPoint;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.List;

import static com.nextgis.maplib.util.Constants.JSON_DISPLAY_NAME;
import static com.nextgis.maplib.util.Constants.JSON_NAME_KEY;
import static com.nextgis.maplib.util.Constants.JSON_TYPE_KEY;
import static com.nextgis.maplib.util.Constants.JSON_VALUE_KEY;
import static com.nextgis.maplib.util.GeoConstants.GTLineString;
import static com.nextgis.maplib.util.GeoConstants.GTMultiLineString;

public class SimpleLineStyle extends Style implements ITextStyle {
    public final static int LineStyleSolid = 1;
    public final static int LineStyleDash = 2;
    public final static int LineStyleEdgingSolid = 3;

    protected int mType;
    protected Paint.Cap mStrokeCap;
    protected String mField;
    protected String mText;

    public SimpleLineStyle() {
        super();
        mStrokeCap = Paint.Cap.BUTT;
    }

    public SimpleLineStyle(int fillColor, int outColor, int type) {
        super(fillColor, outColor);
        mType = type;
        mStrokeCap = Paint.Cap.BUTT;
    }

    @Override
    public SimpleLineStyle clone() throws CloneNotSupportedException {
        SimpleLineStyle obj = (SimpleLineStyle) super.clone();
        obj.mType = mType;
        obj.mStrokeCap = mStrokeCap;
        obj.mText = mText;
        obj.mField = mField;
        return obj;
    }

    public void onDraw(GeoLineString lineString, GISDisplay display) {
        if (null == lineString) {
            return;
        }

        float scaledWidth = (float) (mWidth / display.getScale());
        Path mainPath = null;
        switch (mType) {
            case LineStyleSolid:
                mainPath = drawSolidLine(scaledWidth, lineString, display);
                break;

            case LineStyleDash:
                mainPath = drawDashLine(scaledWidth, lineString, display);
                break;

            case LineStyleEdgingSolid:
                mainPath = drawSolidEdgingLine(scaledWidth, lineString, display);
                break;
        }

        drawText(scaledWidth, mainPath, display);
    }

    protected void drawText(float scaledWidth, Path mainPath, GISDisplay display) {
        if (TextUtils.isEmpty(mText) || mainPath == null)
            return;

        Paint textPaint = new Paint();
        textPaint.setColor(mOutColor);
        textPaint.setAntiAlias(true);
        textPaint.setStyle(Paint.Style.FILL);
        textPaint.setStrokeCap(Paint.Cap.ROUND);
        textPaint.setStrokeWidth(scaledWidth);

        float textSize = 12 * scaledWidth;
        textPaint.setTextSize(textSize);
        float textWidth = textPaint.measureText(mText);
        float vOffset = (float) (textSize / 2.7);

        // draw text along the main path
        PathMeasure pm = new PathMeasure(mainPath, false);
        float length = pm.getLength();
        float gap = textPaint.measureText("_");
        float period = textWidth + gap;
        float startD = gap;
        float stopD = startD + period;

        Path textPath = new Path();

        while (stopD < length) {
            textPath.reset();
            pm.getSegment(startD, stopD, textPath, true);
            textPath.rLineTo(0, 0); // workaround for API <= 19

            display.drawTextOnPath(mText, textPath, 0, vOffset, textPaint);

            startD += period;
            stopD += period;
        }

        stopD = startD;
        float rest = length - stopD;

        if (rest > gap * 2) {
            stopD = length - gap;

            textPath.reset();
            pm.getSegment(startD, stopD, textPath, true);
            textPath.rLineTo(0, 0); // workaround for API <= 19

            display.drawTextOnPath(mText, textPath, 0, vOffset, textPaint);
        }
    }

    @Override
    public void onDraw(GeoGeometry geoGeometry, GISDisplay display) {
        mColor = Color.argb(mInnerAlpha, Color.red(mColor), Color.green(mColor), Color.blue(mColor));
        mOutColor = Color.argb(mOuterAlpha, Color.red(mOutColor), Color.green(mOutColor), Color.blue(mOutColor));

        switch (geoGeometry.getType()) {
            case GTLineString:
                onDraw((GeoLineString) geoGeometry, display);
                break;

            case GTMultiLineString:
                GeoMultiLineString multiLineString = (GeoMultiLineString) geoGeometry;
                for (int i = 0; i < multiLineString.size(); i++) {
                    onDraw(multiLineString.get(i), display);
                }
                break;

            //throw new IllegalArgumentException(
            //        "The input geometry type is not support by this style");
        }

    }

    protected Path drawSolidLine(float scaledWidth, GeoLineString lineString, GISDisplay display) {
        Paint paint = new Paint();
        paint.setColor(mColor);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeCap(mStrokeCap);
        paint.setStrokeWidth(scaledWidth);

        List<GeoPoint> points = lineString.getPoints();

        Path path = new Path();
        path.incReserve(points.size());

        path.moveTo((float) points.get(0).getX(), (float) points.get(0).getY());

        for (int i = 1; i < points.size(); ++i) {
            path.lineTo((float) points.get(i).getX(), (float) points.get(i).getY());
        }

        display.drawPath(path, paint);

        return path;
    }

    protected Path drawDashLine(float scaledWidth, GeoLineString lineString, GISDisplay display) {
        Paint paint = new Paint();
        paint.setColor(mColor);
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeCap(Paint.Cap.BUTT);
        paint.setStrokeWidth(scaledWidth);

        List<GeoPoint> points = lineString.getPoints();

        // workaround for "DashPathEffect/drawLine not working properly when hardwareAccelerated="true""
        // https://code.google.com/p/android/issues/detail?id=29944

        // get all points to the main path
        Path mainPath = new Path();
        mainPath.incReserve(points.size());

        mainPath.moveTo((float) points.get(0).getX(), (float) points.get(0).getY());

        for (int i = 1; i < points.size(); ++i) {
            mainPath.lineTo((float) points.get(i).getX(), (float) points.get(i).getY());
        }

        // draw along the main path
        PathMeasure pm = new PathMeasure(mainPath, false);
        float[] coordinates = new float[2];
        float length = pm.getLength();
        float dash = (float) (10 / display.getScale());
        float gap = (float) (5 / display.getScale());
        float distance = dash;
        boolean isDash = true;

        Path dashPath = new Path();
        dashPath.incReserve((int) (2 * length / (dash + gap)));

        dashPath.moveTo((float) points.get(0).getX(), (float) points.get(0).getY());

        while (distance < length) {
            // get a point from the main path
            pm.getPosTan(distance, coordinates, null);

            if (isDash) {
                dashPath.lineTo(coordinates[0], coordinates[1]);
                distance += gap;
            } else {
                dashPath.moveTo(coordinates[0], coordinates[1]);
                distance += dash;
            }

            isDash = !isDash;
        }

        // add a rest from the main path
        if (isDash) {
            distance = distance - dash;
            float rest = length - distance;

            if (rest > (float) (1 / display.getScale())) {
                distance = length - 1;
                pm.getPosTan(distance, coordinates, null);
                dashPath.lineTo(coordinates[0], coordinates[1]);
            }
        }

        display.drawPath(dashPath, paint);

        return mainPath;
    }

    protected Path drawSolidEdgingLine(float scaledWidth, GeoLineString lineString, GISDisplay display) {
        Paint mainPaint = new Paint();
        mainPaint.setColor(mColor);
        mainPaint.setAntiAlias(true);
        mainPaint.setStyle(Paint.Style.STROKE);
        mainPaint.setStrokeCap(Paint.Cap.BUTT);
        mainPaint.setStrokeWidth(scaledWidth);

        Paint edgingPaint = new Paint(mainPaint);
        edgingPaint.setColor(mOutColor);
        edgingPaint.setStrokeCap(Paint.Cap.BUTT);
        edgingPaint.setStrokeWidth(scaledWidth * 3);

        List<GeoPoint> points = lineString.getPoints();

        Path path = new Path();
        path.incReserve(points.size());

        path.moveTo((float) points.get(0).getX(), (float) points.get(0).getY());

        for (int i = 1; i < points.size(); ++i) {
            path.lineTo((float) points.get(i).getX(), (float) points.get(i).getY());
        }

        display.drawPath(path, edgingPaint);
        display.drawPath(path, mainPaint);

        return path;
    }

    public int getType() {
        return mType;
    }

    public void setType(int type) {
        mType = type;
    }

    public String getField() {
        return mField;
    }

    public void setField(String field) {
        mField = field;
    }

    public String getText() {
        return mText;
    }

    public void setText(String text) {
        if (!TextUtils.isEmpty(text))
            mText = text;
        else
            mText = null;
    }

    @Override
    public JSONObject toJSON() throws JSONException {
        JSONObject rootConfig = super.toJSON();
        rootConfig.put(JSON_NAME_KEY, "SimpleLineStyle");
        rootConfig.put(JSON_TYPE_KEY, mType);

        if (null != mText) {
            rootConfig.put(JSON_DISPLAY_NAME, mText);
        }
        if (null != mField) {
            rootConfig.put(JSON_VALUE_KEY, mField);
        }

        return rootConfig;
    }

    @Override
    public void fromJSON(JSONObject jsonObject) throws JSONException {
        super.fromJSON(jsonObject);
        mType = jsonObject.getInt(JSON_TYPE_KEY);

        if (jsonObject.has(JSON_DISPLAY_NAME)) {
            mText = jsonObject.getString(JSON_DISPLAY_NAME);
        }
        if (jsonObject.has(JSON_VALUE_KEY)) {
            mField = jsonObject.getString(JSON_VALUE_KEY);
        }
    }
}