
/*
 * Copyright 2015 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkLights_DEFINED
#define SkLights_DEFINED

#include "../private/SkTArray.h"
#include "SkImage.h"
#include "SkPoint3.h"
#include "SkRefCnt.h"

class SkReadBuffer;
class SkWriteBuffer;

class SK_API SkLights  : public SkRefCnt {
public:
    class Light {
    public:
        enum LightType {
            kDirectional_LightType,
            kPoint_LightType
        };

        Light(const Light& other)
            : fType(other.fType)
            , fColor(other.fColor)
            , fDirOrPos(other.fDirOrPos)
            , fIntensity(other.fIntensity)
            , fShadowMap(other.fShadowMap)
            , fIsRadial(other.fIsRadial) {
        }

        Light(Light&& other)
            : fType(other.fType)
            , fColor(other.fColor)
            , fDirOrPos(other.fDirOrPos)
            , fIntensity(other.fIntensity)
            , fShadowMap(std::move(other.fShadowMap))
            , fIsRadial(other.fIsRadial)  {
        }

        static Light MakeDirectional(const SkColor3f& color, const SkVector3& dir,
                                     bool isRadial = false) {
            Light light(kDirectional_LightType, color, dir, isRadial);
            if (!light.fDirOrPos.normalize()) {
                light.fDirOrPos.set(0.0f, 0.0f, 1.0f);
            }
            return light;
        }

        static Light MakePoint(const SkColor3f& color, const SkPoint3& pos, SkScalar intensity,
                               bool isRadial = false) {
            return Light(kPoint_LightType, color, pos, intensity, isRadial);
        }

        LightType type() const { return fType; }
        const SkColor3f& color() const { return fColor; }
        const SkVector3& dir() const {
            SkASSERT(kDirectional_LightType == fType);
            return fDirOrPos;
        }
        const SkPoint3& pos() const {
            SkASSERT(kPoint_LightType == fType);
            return fDirOrPos;
        }
        SkScalar intensity() const {
            SkASSERT(kPoint_LightType == fType);
            return fIntensity;
        }

        void setShadowMap(sk_sp<SkImage> shadowMap) {
            fShadowMap = std::move(shadowMap);
        }

        SkImage* getShadowMap() const {
            return fShadowMap.get();
        }

        bool isRadial() const { return fIsRadial; }

        Light& operator= (const Light& b) {
            if (this == &b) {
                return *this;
            }

            fColor = b.fColor;
            fType = b.fType;
            fDirOrPos = b.fDirOrPos;
            fIntensity = b.fIntensity;
            fShadowMap = b.fShadowMap;
            fIsRadial = b.fIsRadial;
            return *this;
        }

        bool operator== (const Light& b) {
            if (this == &b) {
                return true;
            }

            return (fColor     == b.fColor) &&
                   (fType      == b.fType) &&
                   (fDirOrPos  == b.fDirOrPos) &&
                   (fShadowMap == b.fShadowMap) &&
                   (fIntensity == b.fIntensity) &&
                   (fIsRadial  == b.fIsRadial);
        }

        bool operator!= (const Light& b) { return !(this->operator==(b)); }

    private:
        LightType   fType;
        SkColor3f   fColor;           // linear (unpremul) color. Range is 0..1 in each channel.

        SkVector3   fDirOrPos;        // For directional lights, holds the direction towards the
                                      // light (+Z is out of the screen).
                                      // If degenerate, it will be replaced with (0, 0, 1).
                                      // For point lights, holds location of point light

        SkScalar    fIntensity;       // For point lights, dictates the light intensity.
                                      // Simply a multiplier to the final light output value.
        sk_sp<SkImage> fShadowMap;
        bool        fIsRadial;        // Whether the light is radial or not. Radial lights will
                                      // cast shadows and lights radially outwards.

        Light(LightType type, const SkColor3f& color, const SkVector3& dirOrPos,
              SkScalar intensity = 0.0f, bool isRadial = false) {
            fType = type;
            fColor = color;
            fDirOrPos = dirOrPos;
            fIntensity = intensity;
            fIsRadial = isRadial;
        }
    };

    class Builder {
    public:
        Builder() : fLights(new SkLights) {}

        void add(const Light& light) {
            if (fLights) {
                fLights->fLights.push_back(light);
            }
        }

        void add(Light&& light) {
            if (fLights) {
                fLights->fLights.push_back(std::move(light));
            }
        }

        void setAmbientLightColor(const SkColor3f& color) {
            if (fLights) {
                fLights->fAmbientLightColor = color;
            }
        }

        sk_sp<SkLights> finish() {
            return std::move(fLights);
        }

    private:
        sk_sp<SkLights> fLights;
    };

    int numLights() const {
        return fLights.count();
    }

    const Light& light(int index) const {
        return fLights[index];
    }

    Light& light(int index) {
        return fLights[index];
    }

    const SkColor3f& ambientLightColor() const {
        return fAmbientLightColor;
    }

    static sk_sp<SkLights> MakeFromBuffer(SkReadBuffer& buf);

    void flatten(SkWriteBuffer& buf) const;

private:
    SkLights() {
        fAmbientLightColor.set(0.0f, 0.0f, 0.0f);
    }
    SkTArray<Light> fLights;
    SkColor3f fAmbientLightColor;
    typedef SkRefCnt INHERITED;
};

#endif
