{-# LANGUAGE ExistentialQuantification #-}
{-| This module contains the Haskell data model for the Google Chart API.

Details about the parameters can be found on the Google Chart API website :
<http://code.google.com/apis/chart/>

Some chart types are not supported yet :

- Maps <http://code.google.com/apis/chart/types.html#maps>

- QRCodes <http://code.google.com/apis/chart/types.html#qrcodes>

Some parameters are not supported yet :

- Data Scaling <http://code.google.com/apis/chart/formats.html#data_scaling>

- Bar chart zero line  <http://code.google.com/apis/chart/styles.html#zero_line>

- Data point labels <http://code.google.com/apis/chart/labels.html#data_point_labels>

- Fill area <http://code.google.com/apis/chart/colors.html#fill_area_marker>

- Line Styles <http://code.google.com/apis/chart/styles.html#line_styles>

- Pie chart orientation <http://code.google.com/apis/chart/types.html#pie_charts>

-}

module Graphics.GChart.Types (

  -- * Typeclasses
  -- | Typeclasses for abstraction
  ChartM, ChartItem(set,encode), ChartDataEncodable(addEncodedChartData),

  -- * Chart data
  -- | This type represents the Chart
  Chart(..),

  -- * Required Parameters
  -- | These parameters are required for all charts

  -- ** Chart Size
  ChartSize(..),
  -- ** Chart Data
  ChartData(..),
  -- ** Chart Type
  ChartType(..),

  -- * Optional Parameters
  -- | All other parameters are optional

  -- ** Colors

  -- *** Chart Colors
  ChartColors(..), Color,

  -- ** Chart Fills : Solid Fill, Linear Gradient, Linear Stripes
  ChartFills, Fill(..), FillKind(..), FillType(..),
  Offset, Width, Angle,

  -- ** Labels

  -- *** Chart Title
  ChartTitle(..),

  -- *** Chart Legend
  ChartLegend(..), LegendPosition(..),

  -- *** Pie chart and Google-o-meter labels
  ChartLabels(..),

  -- *** Axis styles and labels
  ChartAxes, Axis(..), AxisType(..), AxisLabel, AxisPosition, FontSize,
  AxisRange(..), AxisStyle(..), DrawingControl(..), AxisStyleAlignment(..),

  -- ** Styles

  -- *** Bar width and spacing
  BarChartWidthSpacing(..), BarWidth(..), BarGroupSpacing(..),

  -- *** Chart Margins
  ChartMargins(..),

  -- *** Grid Lines
  ChartGrid(..),

  -- *** Shape, Range and Financial Markers
  AnyChartMarker(..), ChartMarker(..), ChartMarkers,
  ShapeType(..), MarkerDataPoint(..), ShapeMarker(..),
  RangeMarkerType(..), RangeMarker(..), FinancialMarker(..),

  -- * Default Values
  {-| These functions return default values for complex parameters, which can be
       used as starting points to construct parameters when creating charts. -}

  defaultChart, defaultAxis, defaultGrid, defaultSpacing, defaultShapeMarker,
  defaultRangeMarker, defaultFinancialMarker
) where

import Control.Monad.State

-- | Size of the chart. width and height specified in pixels
data ChartSize = Size Int Int deriving Show


-- | Chart type <http://code.google.com/apis/chart/types.html>
data ChartType
  = Line                  -- ^ Line Chart
  | Sparklines            -- ^ Sparklines
  | LineXY                -- ^ Line Chart w/ XY co-ordinates
  | BarHorizontalStacked  -- ^ Horizontal bar chart w/ stacked bars
  | BarVerticalStacked    -- ^ Vertical bar chart w/ stacked bars
  | BarHorizontalGrouped  -- ^ Horizontal bar chart w/ grouped bars
  | BarVerticalGrouped    -- ^ Vertical bar chart w/ grouped bars
  | Pie                   -- ^ Two dimensional pie chart
  | Pie3D                 -- ^ Three dimensional pie chart
  | PieConcentric         -- ^ Concentric pie chart
  | Venn                  -- ^ Venn Diagram
  | ScatterPlot           -- ^ Scatter Plot
  | Radar                 -- ^ Radar Chart
  | GoogleOMeter          -- ^ Google-o-meter
    deriving Show

-- | Title of the chart
-- | <http://code.google.com/apis/chart/labels.html#chart_title>
data ChartTitle =
    ChartTitle {
      titleStr ::String               -- ^ Title
    , titleColor :: Maybe Color       -- ^ Title Color
    , titleFontSize :: Maybe FontSize -- ^ Title Font Size
    } deriving Show

-- | Chart data along with encoding. XY data for is encoded a pair of
-- | consecutive data sets
-- | <http://code.google.com/apis/chart/formats.html>
data ChartData
  = Simple [[Int]]   -- ^ lets you specify integer values from 0-61, inclusive
  | Text [[Float]]   -- ^ supports floating point numbers from 0-100, inclusive
  | Extended [[Int]] -- ^ lets you specify integer values from 0-4095, inclusive
    deriving Show

-- | Color data specified as a hex string
type Color = String

-- | Chart colors specified as a list of 'Color' values for each data point.
-- <http://code.google.com/apis/chart/colors.html#chart_colors>
data ChartColors = ChartColors [Color] deriving Show


-- | Specifies the angle of the gradient between 0 (horizontal) and 90
-- (vertical). Applicable to 'LinearGradient' and 'LinearStripes'
type Angle = Float

-- | Specifies at what point the color is pure. In this parameter, 0 specifies
-- the right-most chart position and 1 specifies the left-most chart
-- position. Applicable to 'LinearGradient'
type Offset = Float

-- | Width of the stripe. must be between 0 and 1, where 1 is the full width of
-- the chart
type Width = Float

-- | Specifies the kind of fill
data FillKind
    = Solid Color -- ^ Solid Fill <http://code.google.com/apis/chart/colors.html#solid_fill>
    | LinearGradient Angle [(Color,Offset)] -- ^ Linear Gradient <http://code.google.com/apis/chart/colors.html#linear_gradient>
    | LinearStripes Angle [(Color,Width)]  -- ^ Linear Stripes <http://code.google.com/apis/chart/colors.html#linear_stripes>
      deriving Show

-- | Specifies the type of fill
data FillType
    = Background   -- ^ Background fill
    | Area         -- ^ Chart area fill
    | Transparent  -- ^ Apply transparency to whole chart (applicable to 'Solid' fill only)
      deriving Show

-- | Constructor for a chart fill
data Fill = Fill FillKind FillType deriving Show

-- | Chart fills, as a list of `Fill's
type ChartFills = [Fill]

-- | Position of legend on chart. Applies to 'ChartLegend'
data LegendPosition
    = LegendBottom   -- ^ Bottom of chart, horizontally
    | LegendTop      -- ^ Top of chart, horizontally
    | LegendVBottom  -- ^ Bottom of chart, vertically
    | LegendVTop     -- ^ Bottom of chart, vertically
    | LegendRight    -- ^ Left of chart
    | LegendLeft     -- ^ Right of chart
      deriving Show

-- | Specifies a chart legend
-- <http://code.google.com/apis/chart/labels.html#chart_legend>
data ChartLegend = Legend [String] (Maybe LegendPosition) deriving Show

-- | Type of 'Axis'
-- <http://code.google.com/apis/chart/labels.html#axis_type>
data AxisType
    = AxisBottom -- ^ Bottom x-axis
    | AxisTop    -- ^ Top x-axis
    | AxisLeft   -- ^ Left y-axis
    | AxisRight  -- ^ Right y-axis
      deriving Show

-- | 'Axis' Labels.
-- <http://code.google.com/apis/chart/labels.html#axis_labels>
type AxisLabel = String

{-| 'Axis' Label Positions. <http://code.google.com/apis/chart/labels.html#axis_label_positions>

Labels with a specified position of 0 are placed at the bottom of the y- or
r-axis, or at the left of the x- or t-axis.

Labels with a specified position of 100 are placed at the top of the y- or
r-axis, or at the right of the x- or t-axis.

-}
type AxisPosition = Float

{-| 'Axis' Range <http://code.google.com/apis/chart/labels.html#axis_range>

The range is specifies with a tuple containing the start and end values. An
optional interval value can be specified.

-}
data AxisRange = Range (Float,Float) (Maybe Float) deriving (Show,Eq)

-- | Font size in pixels. Applicable to 'AxisStyle' and 'ChartTitle'
type FontSize = Int

-- | Alignment of 'Axis' labels. Applies to 'AxisStyle'
data AxisStyleAlignment
    = AxisStyleLeft    -- ^ Left aligned labels
    | AxisStyleCenter  -- ^ Centered labels
    | AxisStyleRight   -- ^ Right aligned labels
      deriving (Show,Eq)

-- | Control drawing of 'Axis'. Applicable to 'AxisStyle'
data DrawingControl
    = DrawLines      -- ^ Draw axis lines only
    | DrawTicks      -- ^ Draw tick marks only
    | DrawLinesTicks -- ^ Draw axis lines and tick marks
      deriving (Show,Eq)

-- | Specify 'Axis' style
-- <http://code.google.com/apis/chart/labels.html#axis_styles>
data AxisStyle = Style { axisColor :: Color,
                         axisFontSize :: Maybe FontSize,
                         axisStyleAlign :: Maybe AxisStyleAlignment,
                         axisDrawingControl :: Maybe DrawingControl,
                         tickMarkColor      :: Maybe Color } deriving (Show,Eq)

-- | Specify an axis for chart.
-- <http://code.google.com/apis/chart/labels.html#axis_styles>
data Axis = Axis { axisType :: AxisType,
                   axisLabels :: Maybe [AxisLabel],
                   axisPositions :: Maybe [AxisPosition],
                   axisRange :: Maybe AxisRange,
                   axisStyle :: Maybe AxisStyle } deriving Show

-- | List of 'Axis' for chart
type ChartAxes = [Axis]

-- | Grid Lines for Chart
-- <http://code.google.com/apis/chart/styles.html#grid>
data ChartGrid =
    ChartGrid {
      xAxisStep :: Float -- ^ x-axis step size (0-100)
    , yAxisStep :: Float -- ^ y-axis step size (0-100)
    , lineSegmentLength :: Maybe Float  -- ^ length of line segment
    , blankSegmentLength :: Maybe Float -- ^ length of blank segment
    , xOffset :: Maybe Float -- ^ x axis offset
    , yOffset :: Maybe Float -- ^ y axis offset
    } deriving Show

-- | Shape type of 'ShapeMarker'
data ShapeType = ShapeArrow       -- ^ Arrow
               | ShapeCross       -- ^ Cross
               | ShapeDiamond     -- ^ Diamond
               | ShapeCircle      -- ^ Circle
               | ShapeSquare      -- ^ Square
               | VerticalLine     -- ^ Vertical line from x-axis to data point
               | VerticalLineFull -- ^ Vertical line across the chart
               | HorizontalLine   -- ^ Horizontal line across the chart
               | ShapeX           -- ^ X shape
                 deriving Show


-- | Data point value of `ShapeMarker`
data MarkerDataPoint =
    DataPoint Float       -- ^ A specific data point in the dataset. Use a
                          -- decimal value to interpolate between two points

  | DataPointEvery        -- ^ Draw a marker on each data point

  | DataPointEveryN Int   -- ^ Draw a marker on every n-th data point

  | DataPointEveryNRange (Int,Int) Int -- ^ @(x,y), n@ draw a marker on every n-th
                                       -- data point in a range, where x is the
                                       -- first data point in the range, and y is
                                       -- the last data point in the range

  | DataPointXY (Float,Float) -- ^ draw a marker at a specific point
                              -- (x,y). Specify the coordinates as floating
                              -- point values, where 0:0 is the bottom left
                              -- corner of the chart, 0.5:0.5 is the center of
                              -- the chart, and 1:1 is the top right corner of
                              -- the chart
    deriving Show

-- | Shape Marker
data ShapeMarker =
    SM { shapeType  :: ShapeType           -- ^ Shape type
       , shapeColor :: Color               -- ^ Shape Marker color
       , shapeDataSetIdx :: Int            -- ^ Data Set Index
       , shapeDataPoint  :: MarkerDataPoint -- ^ Data point value
       , shapeSize :: Int                  -- ^ Size in pixels
       , shapePriority :: Int              -- ^ Priority of drawing. Can be one of -1,0,1
       } deriving Show

-- | 'RangeMarker' type
data RangeMarkerType = RangeMarkerHorizontal -- ^ horizontal range
                     | RangeMarkerVertical   -- ^ vertical range
                       deriving Show

-- | Range Marker
data RangeMarker =
  RM { rangeMarkerType  :: RangeMarkerType -- ^ Range marker type
     , rangeMarkerColor :: Color           -- ^ Range marker color
     , rangeMarkerRange :: (Float, Float) -- ^ @(start,end) range. @For
                                          -- horizontal range markers, the
                                          -- (start,end) value is a position on
                                          -- the y-axis, where 0.00 is the
                                          -- bottom of the chart, and 1.00 is
                                          -- the top of the chart. For vertical
                                          -- range markers, the (start,end)
                                          -- value is a position on the x-axis,
                                          -- where 0.00 is the left of the
                                          -- chart, and 1.00 is the right of the
                                          -- chart.
    } deriving Show


-- | Financial Marker, for line charts and vertical bar charts
data FinancialMarker =
    FM { financeColor :: Color                -- ^ Finance Marker color
       , financeDataSetIdx :: Int             -- ^ Data Set Index
       , financeDataPoint  :: MarkerDataPoint -- ^ Data point value
       , financeSize :: Int                   -- ^ Size in pixels
       , financePriority :: Int               -- ^ Priority of drawing. Can be one of -1,0,1
       } deriving Show

-- | Typeclass to abstract over different chart markers
class Show a => ChartMarker a where
    encodeChartMarker :: a -> String
    encodeChartMarker a = ""

-- | Data type to abstract over all kinds of ChartMarker
data AnyChartMarker = forall w. ChartMarker w => AnyChartMarker w

instance ChartMarker AnyChartMarker where
    encodeChartMarker (AnyChartMarker m) = encodeChartMarker m

instance Show AnyChartMarker where
    show (AnyChartMarker m) = show m

type ChartMarkers = [AnyChartMarker]

-- | Labels for Pie Chart and Google-o-meter.
-- Specify a list with a single label for Google-o-meter
data ChartLabels = ChartLabels [String] deriving Show

-- | Chart Margins. All margin values specified are the minimum margins around
-- the plot area, in pixels.
-- <http://code.google.com/apis/chart/styles.html#chart_margins>
data ChartMargins =
    ChartMargins {
      leftMargin   :: Int                -- ^ Left margin around plot area
    , rightMargin  :: Int                -- ^ Right margin around plot area
    , topMargin    :: Int                -- ^ Top margin around plot area
    , bottomMargin :: Int                -- ^ Bottom margin around plot area
    , legendMargins  :: Maybe (Int,Int)  -- ^ Minimum width and height  of legend
    } deriving Show


-- | Bar Width
data BarWidth = Automatic    -- ^ Automatic resizing
              | BarWidth Int -- ^ Bar width in pixels
              deriving Show

-- | Bar and Group Spacing
data BarGroupSpacing = Fixed (Int, Int)          -- ^ Fixed spacing values in pixels
                     | Relative (Float,Float)    -- ^ Relative values as percentages
                     deriving Show


-- | Bar Width and Spacing.
type BarChartWidthSpacing =  (Maybe BarWidth, Maybe BarGroupSpacing)

-- | Data type for the chart
data Chart =
    Chart {
      chartSize    :: ChartSize
    , chartType    :: ChartType
    , chartData    :: ChartData
    , chartTitle   :: Maybe ChartTitle
    , chartColors  :: Maybe ChartColors
    , chartFills   :: Maybe ChartFills
    , chartLegend  :: Maybe ChartLegend
    , chartAxes    :: Maybe ChartAxes
    , chartMarkers :: Maybe ChartMarkers
    , chartGrid    :: Maybe ChartGrid
    , chartLabels  :: Maybe ChartLabels
    , chartMargins :: Maybe ChartMargins
    , barChartWidthSpacing :: Maybe BarChartWidthSpacing
    } deriving Show


-- | Chart monad which wraps a 'State' monad in turn
-- to keep track of the chart state and make it convenient
-- to update it
type ChartM a = State Chart a

-- | Typeclass abstracting all the fields in a chart
class ChartItem c where
  -- | sets the item in a chart
  set :: c -> ChartM ()

  -- | encode the field into a list string params that can
  -- then be converted into a query string URL
  encode :: c -> [(String,String)]


-- | Typeclass abstracting the numeric data that can be encoded.
-- This helps in passing Int and Float values as chart data, which
-- are then encoded correctly
class Num a => ChartDataEncodable a where
    -- | Adds the array of numeric data to the existing chart data.
    -- Throws a error if the data passed in doesnt match with the
    -- current data encoding format.
    addEncodedChartData :: [a] -> ChartData -> ChartData


-- | Default value for a chart
defaultChart =
    Chart { chartSize  = Size 320 200,
            chartType  = Line,
            chartData  = Simple [],
            chartTitle = Nothing,
            chartColors = Nothing,
            chartFills = Nothing,
            chartLegend = Nothing,
            chartAxes = Nothing,
            chartGrid = Nothing,
            chartLabels = Nothing,
            chartMargins = Nothing,
            chartMarkers = Nothing,
            barChartWidthSpacing = Nothing
          }

-- | Default value for an axis
defaultAxis = Axis { axisType = AxisBottom,
                     axisLabels = Nothing,
                     axisPositions = Nothing,
                     axisRange = Nothing,
                     axisStyle = Nothing }

-- | Default value for an axis style
defaultAxisStyle = Style { axisColor = "0000DD",
                           axisFontSize = Nothing,
                           axisStyleAlign = Nothing,
                           axisDrawingControl = Nothing,
                           tickMarkColor = Nothing }

-- | Default value for a chart grid
defaultGrid = ChartGrid {  xAxisStep = 20,
                           yAxisStep = 20,
                           lineSegmentLength = Nothing,
                           blankSegmentLength = Nothing,
                           xOffset = Nothing,
                           yOffset = Nothing }

-- | Default value for bar and group spacing in bar chart
defaultSpacing = Fixed (4,8)

-- | Default value of a shape marker. Make sure you change the value of @shapeDataSetIdx@
defaultShapeMarker =  SM { shapeType = ShapeCircle,
                           shapeColor = "0000DD",
                           shapeDataSetIdx = -1,
                           shapeDataPoint = DataPointEvery,
                           shapeSize = 5,
                           shapePriority = 0 }

-- | Default value of range marker
defaultRangeMarker = RM { rangeMarkerType  = RangeMarkerHorizontal,
                          rangeMarkerColor = "0000DD",
                          rangeMarkerRange = (0.0,1.0) }

-- | Default value of a financial marker. Make sure you change the value of @financeDataSetIdx@
defaultFinancialMarker = FM { financeColor = "0000DD",
                              financeDataSetIdx = -1,
                              financeDataPoint = DataPointEvery,
                              financeSize = 5,
                              financePriority = 0 }