//  Author:
//       Christohper James Halse Rogers <raof@ubuntu.com>
//
//  Copyright © 2014 Christopher James Halse Rogers <raof@ubuntu.com>
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General 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 General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>.

// This sample will guide you through elements of the F# language. 
//
// *******************************************************************************************************
//   To execute the code in F# Interactive, highlight a section of code and press Alt-Enter or right-click
//   and select "Execute in Interactive".  You can open the F# Interactive Window from the "View" menu.
// *******************************************************************************************************
//
// For more about F#, see:
//     http://fsharp.net
//
// For additional templates to use with F#, see the 'Online Templates' in Visual Studio,
//     'New Project' --> 'Online Templates'
//
// For specific F# topics, see:
//     http://fsharp.org (F# Open Organization)
//     http://tryfsharp.org (F# Learning Portal)
//     http://go.microsoft.com/fwlink/?LinkID=234174 (Visual F# Development Portal)
//     http://go.microsoft.com/fwlink/?LinkID=124614 (Visual F# Code Gallery)
//     http://go.microsoft.com/fwlink/?LinkId=235173 (Visual F# Math/Stats Programming)
//     http://go.microsoft.com/fwlink/?LinkId=235176 (Visual F# Charting)


// Contents:

//    - Integers and basic functions
//    - Booleans
//    - Strings
//    - Tuples
//    - Lists and list processing
//    - Classes
//    - Generic classes
//    - Implementing interfaces
//    - Arrays
//    - Sequences
//    - Recursive functions
//    - Record types
//    - Union types
//    - Option types           
//    - Pattern matching       
//    - Units of measure       
//    - Parallel array programming
//    - Using events
//    - Database access using type providers
//    - OData access using type providers
 

// ---------------------------------------------------------------
//         Integers and basic functions
// ---------------------------------------------------------------

module Integers =
    let sampleInteger = 176
 
    /// Do some arithmetic starting with the first integer
    let sampleInteger2 = (sampleInteger/4 + 5 - 7) * 4

    /// A list of the numbers from 0 to 99
    let sampleNumbers = [ 0 .. 99 ]
 
    /// A list of all tuples containing all the numbers from 0 to 99 and their squares
    let sampleTableOfSquares = [ for i in 0 .. 99 -> (i, i*i) ]
 
    // The next line prints a list that includes tuples, using %A for generic printing
    printfn "The table of squares from 0 to 99 is:\n%A" sampleTableOfSquares
 
 
module BasicFunctions =

    // Use 'let' to define a function that accepts an integer argument and returns an integer.
    let func1 x = x*x + 3            

    // Parenthesis are optional for function arguments
    let func1a (x) = x*x + 3            


    /// Apply the function, naming the function return result using 'let'.
    /// The variable type is inferred from the function return type.
    let result1 = func1 4573

    printfn "The result of squaring the integer 4573 and adding 3 is %d" result1

    // When needed, annotate the type of a parameter name using '(argument:type)'
    let func2 (x:int) = 2*x*x - x/5 + 3

    let result2 = func2 (7 + 4)

    printfn "The result of applying the 1st sample function to (7 + 4) is %d" result2


    let func3 x =
        if x < 100.0 then
            2.0*x*x - x/5.0 + 3.0
        else
            2.0*x*x + x/5.0 - 37.0

    let result3 = func3 (6.5 + 4.5)

    printfn "The result of applying the 2nd sample function to (6.5 + 4.5) is %f" result3


// ---------------------------------------------------------------
//         Booleans
// ---------------------------------------------------------------


module SomeBooleanValues =

    let boolean1 = true

    let boolean2 = false

    let boolean3 = not boolean1 && (boolean2 || false)

    printfn "The expression 'not boolean1 && (boolean2 || false)' is %A" boolean3

 

// ---------------------------------------------------------------
//         Strings
// ---------------------------------------------------------------


module StringManipulation =

    let string1 = "Hello"

    let string2  = "world"

    /// Use @ to create a verbatim string literal

    let string3 = @"c:\Program Files\"

    /// Using a triple-quote string literal
    let string4 = """He said "hello world" after you did"""

    let helloWorld = string1 + " " + string2 // concatenate the two strings with a space in between

    printfn "%s" helloWorld

    /// A string formed by taking the first 7 characters of one of the result strings
    let substring = helloWorld.[0..6]

    printfn "%s" substring



// ---------------------------------------------------------------
//         Tuples (ordered sets of values)
// ---------------------------------------------------------------

module Tuples =

    /// A simple tuple of integers
    let tuple1 = (1, 2, 3)

    /// A function that swaps the order of two values in a tuple.
    /// QuickInfo shows that the function is inferred to have a generic type.

    let swapElems (a, b) = (b, a)

    printfn "The result of swapping (1, 2) is %A" (swapElems (1,2))

    /// A tuple consisting of an integer, a string, and a double-precision floating point number
    let tuple2 = (1, "fred", 3.1415)

    printfn "tuple1: %A    tuple2: %A" tuple1 tuple2 

 

// ---------------------------------------------------------------
//         Lists and list processing
// ---------------------------------------------------------------

module Lists =

    let list1 = [ ]            /// an empty list
    
    let list2 = [ 1; 2; 3 ]    /// list of 3 elements

    let list3 = 42 :: list2    /// a new list with '42' added to the beginning

    let numberList = [ 1 .. 1000 ]  /// list of integers from 1 to 1000

    /// A list containing all the days of the year
    let daysList =
        [ for month in 1 .. 12 do
              for day in 1 .. System.DateTime.DaysInMonth(2012, month) do
                  yield System.DateTime(2012, month, day) ]

    /// A list containing the tuples which are the coordinates of the black squares on a chess board.
    let blackSquares =

        [ for i in 0 .. 7 do
              for j in 0 .. 7 do
                  if (i+j) % 2 = 1 then
                      yield (i, j) ]

    /// Square the numbers in numberList, using the pipeline operator to pass an argument to List.map   
    let squares =
        numberList
        |> List.map (fun x -> x*x)

    /// Computes the sum of the squares of the numbers divisible by 3.
    let sumOfSquaresUpTo n =
        numberList
        |> List.filter (fun x -> x % 3 = 0)
        |> List.sumBy (fun x -> x * x)




// ---------------------------------------------------------------
//         Classes
// ---------------------------------------------------------------


module DefiningClasses =
    /// The class's constructor takes two arguments: dx and dy, both of type 'float'.
    type Vector2D(dx : float, dy : float) =

        /// The length of the vector, computed when the object is constructed
        let length = sqrt (dx*dx + dy*dy)


        // 'this' specifies a name for the object's self identifier
        // In instance methods, it must appear before the member name.

        member this.DX = dx 

        member this.DY = dy

        member this.Length = length

        member this.Scale(k) = Vector2D(k * this.DX, k * this.DY)

   
    /// An instance of the Vector2D class
    let vector1 = Vector2D(3.0, 4.0)

    /// Get a new scaled vector object, without modifying the original object
    let vector2 = vector1.Scale(10.0)

    printfn "Length of vector1: %f      Length of vector2: %f" vector1.Length vector2.Length

 

// ---------------------------------------------------------------
//         Generic classes
// ---------------------------------------------------------------

module DefiningGenericClasses =

    type StateTracker<'T>(initialElement: 'T) = // 'T is the type parameter for the class

        /// Store the states in an array
        let mutable states = [ initialElement ]

        /// Add a new element to the list of states
        member this.UpdateState newState =
            states <- newState :: states  // use the '<-' operator to mutate the value


        /// Get the entire list of historical states
        member this.History = states

 
        /// Get the latest state
        member this.Current = states.Head


    /// An 'int' instance of the state tracker class. Note that the type parameter is inferred.
    let tracker = StateTracker 10


    // Add a state
    tracker.UpdateState 17



// ---------------------------------------------------------------
//         Implementing interfaces
// ---------------------------------------------------------------

/// Type that implements IDisposable

type ReadFile() =

    let file = new System.IO.StreamReader("readme.txt")

    member this.ReadLine() = file.ReadLine()

    // this class's implementation of IDisposable members
    interface System.IDisposable with   

        member this.Dispose() = file.Close()


// ---------------------------------------------------------------
//         Arrays
// ---------------------------------------------------------------


module Arrays =


    /// The empty array
    let array1 = [| |]

    let array2 = [| "hello"; "world"; "and"; "hello"; "world"; "again" |]

    let array3 = [| 1 .. 1000 |]

    /// An array containing only the words "hello" and "world"

    let array4 = [| for word in array2 do
                        if word.Contains("l") then
                            yield word |]


    /// An array initialized by index and containing the even numbers from 0 to 2000
    let evenNumbers = Array.init 1001 (fun n -> n * 2)

    /// sub-array extracted using slicing notation
    let evenNumbersSlice = evenNumbers.[0..500]

    for word in array4 do
        printfn "word: %s" word

    // modify an array element using the left arrow assignment operator
    array2.[1] <- "WORLD!"

    /// Calculates the sum of the lengths of the words that start with 'h'
    let sumOfLengthsOfWords =
        array2
        |> Array.filter (fun x -> x.StartsWith "h")
        |> Array.sumBy (fun x -> x.Length)
 

// ---------------------------------------------------------------
//         Sequences
// ---------------------------------------------------------------

 
module Sequences =

    // Sequences are evaluated on-demand and are re-evaluated each time they are iterated.
    // An F# sequence is an instance of a System.Collections.Generic.IEnumerable<'T>,
    // so Seq functions can be applied to Lists and Arrays as well.

    /// The empty sequence
    let seq1 = Seq.empty

    let seq2 = seq { yield "hello"; yield "world"; yield "and"; yield "hello"; yield "world"; yield "again" }

    let numbersSeq = seq { 1 .. 1000 }

    /// another array containing only the words "hello" and "world"
    let seq3 =
        seq { for word in seq2 do
                  if word.Contains("l") then
                      yield word }


    let evenNumbers = Seq.init 1001 (fun n -> n * 2)

    let rnd = System.Random()

    /// An infinite sequence which is a random walk
    //  Use yield! to return each element of a subsequence, similar to IEnumerable.SelectMany.

    let rec randomWalk x =
        seq { yield x
              yield! randomWalk (x + rnd.NextDouble() - 0.5) }
 

    let first100ValuesOfRandomWalk =
        randomWalk 5.0
        |> Seq.truncate 100
        |> Seq.toList


// ---------------------------------------------------------------
//         Recursive functions
// ---------------------------------------------------------------


module RecursiveFunctions  =

    /// Compute the factorial of an integer. Use 'let rec' to define a recursive function
    let rec factorial n =
        if n = 0 then 1 else n * factorial (n-1)
 
    /// Computes the greatest common factor of two integers.
    //  Since all of the recursive calls are tail calls, the compiler will turn the function into a loop,
    //  which improves performance and reduces memory consumption.

    let rec greatestCommonFactor a b =                      
        if a = 0 then b
        elif a < b then greatestCommonFactor a (b - a)          
        else greatestCommonFactor (a - b) b


    /// Computes the sum of a list of integers using recursion.
    let rec sumList xs =
        match xs with
        | []    -> 0
        | y::ys -> y + sumList ys


    /// Make the function tail recursive, using a helper function with a result accumulator
    let rec private sumListTailRecHelper accumulator xs =
        match xs with
        | []    -> accumulator
        | y::ys -> sumListTailRecHelper (accumulator+y) ys


    let sumListTailRecursive xs = sumListTailRecHelper 0 xs

 

// ---------------------------------------------------------------
//         Record types
// ---------------------------------------------------------------


module RecordTypes =
    // define a record type
    type ContactCard =
        { Name     : string
          Phone    : string
          Verified : bool }

    let contact1 = { Name = "Alf" ; Phone = "(206) 555-0157" ; Verified = false }

    // Create a new record that is a copy of contact1,
    // but has different values for the 'Phone' and 'Verified' fields

    let contact2 = { contact1 with Phone = "(206) 555-0112"; Verified = true }


    /// Converts a 'ContactCard' object to a string
    let showCard c =
        c.Name + " Phone: " + c.Phone + (if not c.Verified then " (unverified)" else "")

       
// ---------------------------------------------------------------
//         Union types
// ---------------------------------------------------------------

module UnionTypes =

    /// Represents the suit of a playing card
    type Suit =
        | Hearts
        | Clubs
        | Diamonds
        | Spades

    /// Represents the rank of a playing card
    type Rank =
        /// Represents the rank of cards 2 .. 10
        | Value of int
        | Ace
        | King
        | Queen
        | Jack

        static member GetAllRanks() =
            [ yield Ace
              for i in 2 .. 10 do yield Value i
              yield Jack
              yield Queen
              yield King ]

    type Card =  { Suit: Suit; Rank: Rank }

    /// Returns a list representing all the cards in the deck

    let fullDeck =
        [ for suit in [ Hearts; Diamonds; Clubs; Spades] do
              for rank in Rank.GetAllRanks() do
                  yield { Suit=suit; Rank=rank } ]


    /// Converts a 'Card' object to a string
    let showCard c =
        let rankString =
            match c.Rank with
            | Ace -> "Ace"
            | King -> "King"
            | Queen -> "Queen"
            | Jack -> "Jack"
            | Value n -> string n

        let suitString =
            match c.Suit with
            | Clubs -> "clubs"
            | Diamonds -> "diamonds"
            | Spades -> "spades"
            | Hearts -> "hearts"

        rankString  + " of " + suitString

    let printAllCards() =
        for card in fullDeck do
            printfn "%s" (showCard card)

 

// ---------------------------------------------------------------
//         Option types
// ---------------------------------------------------------------

module OptionTypes =
    /// Option values are any kind of value tagged with either 'Some' or 'None'.
    /// They are used extensively in F# code to represent the cases where many other
    /// languages would use null references.

    type Customer = { zipCode : decimal option }

    /// Abstract class that computes the shipping zone for the customer's zip code,
    /// given implementations for the 'GetState' and 'GetShippingZone' abstract methods.

    [<AbstractClass>]

    type ShippingCalculator =

        abstract GetState : decimal -> string option

        abstract GetShippingZone : string -> int

        /// Return the shipping zone corresponding to the customer's ZIP code
        /// Customer may not yet have a ZIP code or the ZIP code may be invalid
        member this.CustomerShippingZone(customer : Customer) =

            customer.zipCode |> Option.bind this.GetState |> Option.map this.GetShippingZone

 
// ---------------------------------------------------------------
//         Pattern matching
// ---------------------------------------------------------------

module PatternMatching =

    /// A record for a person's first and last name

    type Person =   
        { First : string
          Last  : string }


    /// Define a discriminated union of 3 different kinds of employees

    type Employee =
        /// Engineer is just herself
        | Engineer  of Person
        /// Manager has list of reports
        | Manager   of Person * list<Employee>            
        /// Executive also has an assistant
        | Executive of Person * list<Employee> * Employee 

    /// Count everyone underneath the employee in the management hierarchy, including the employee
    let rec countReports(emp : Employee) =
        1 + match emp with
            | Engineer(id) ->
                0
            | Manager(id, reports) ->
                reports |> List.sumBy countReports
            | Executive(id, reports, assistant) ->
                (reports |> List.sumBy countReports) + countReports assistant

    /// Find all managers/executives named "Dave" who do not have any reports
    let rec findDaveWithOpenPosition(emps : Employee list) =
        emps
        |> List.filter(function
                       | Manager({First = "Dave"}, []) -> true       // [] matches the empty list
                       | Executive({First = "Dave"}, [], _) -> true
                       | _ -> false)                                 // '_' is a wildcard pattern that matches anything
                                                                     // this handles the "or else" case


// ---------------------------------------------------------------
//         Units of measure
// ---------------------------------------------------------------

module UnitsOfMeasure =
    // Code can be annotated with units of measure when using F# arithmetic over numeric types

    open Microsoft.FSharp.Data.UnitSystems.SI.UnitNames

    [<Measure>]
    type mile =

        /// Conversion factor mile to meter: meter is defined in SI.UnitNames

        static member asMeter = 1600.<meter/mile>

 
    let d  = 50.<mile>          // Distance expressed using imperial units
    let d' = d * mile.asMeter   // Same distance expressed using metric system

    printfn "%A = %A" d d'
    // let error = d + d'       // Compile error: units of measure do not match


 

// ---------------------------------------------------------------
//         Parallel array programming
// ---------------------------------------------------------------
 

module ParallelArrayProgramming =

    let oneBigArray = [| 0 .. 100000 |]

    // do some CPU intensive computation
    let rec computeSomeFunction x =
        if x <= 2 then 1
        else computeSomeFunction (x - 1) + computeSomeFunction (x - 2)

    // Do a parallel map over a large input array
    let computeResults() = oneBigArray |> Array.Parallel.map (fun x -> computeSomeFunction (x % 20))

    printfn "Parallel computation results: %A" (computeResults())

 

// ---------------------------------------------------------------
//         Using events
// ---------------------------------------------------------------

module Events =

    open System

    // Create instance of Event object that consists of subscription point (event.Publish) and event trigger (event.Trigger)
    let simpleEvent = new Event<int>()

    // Add handler
    simpleEvent.Publish.Add(fun x -> printfn "this is handler was added with Publish.Add: %d" x)

    // Trigger event
    simpleEvent.Trigger(5)

    // Create instance of Event that follows standard .NET convention: (sender, EventArgs)
    let eventForDelegateType = new Event<EventHandler, EventArgs>()   

 
    // Add handler
    eventForDelegateType.Publish.AddHandler(
        EventHandler(fun _ _ -> printfn "this is handler was added with Publish.AddHandler"))

 
    // Trigger event (note that sender argument should be set)
    eventForDelegateType.Trigger(null, EventArgs.Empty)



// ---------------------------------------------------------------
//         Database access using type providers
// ---------------------------------------------------------------

module DatabaseAccess =

    // The easiest way to access a SQL database from F# is to use F# type providers.
    // Add references to System.Data, System.Data.Linq, and FSharp.Data.TypeProviders.dll.
    // You can use Server Explorer to build your ConnectionString.

    (*

    #r "System.Data"
    #r "System.Data.Linq"
    #r "FSharp.Data.TypeProviders"

    open Microsoft.FSharp.Data.TypeProviders

    type SqlConnection = SqlDataConnection<ConnectionString = @"Data Source=.\sqlexpress;Initial Catalog=tempdb;Integrated Security=True">
    let db = SqlConnection.GetDataContext()

    let table =
        query { for r in db.Table do
                select r }

    *)


    // You can also use SqlEntityConnection instead of SqlDataConnection, which accesses the database using Entity Framework.

    ()

 

// ---------------------------------------------------------------
//         OData access using type providers
// ---------------------------------------------------------------

module OData =

    (*

    open System.Data.Services.Client
    open Microsoft.FSharp.Data.TypeProviders

    // Consume demographics population and income OData service from Azure Marketplace.
    // For more information, see http://go.microsoft.com/fwlink/?LinkId=239712

    type Demographics = Microsoft.FSharp.Data.TypeProviders.ODataService<ServiceUri = "https://api.datamarket.azure.com/Esri/KeyUSDemographicsTrial/">
    let ctx = Demographics.GetDataContext()

    // Sign up for a Azure Marketplace account at https://datamarket.azure.com/account/info
    ctx.Credentials <- System.Net.NetworkCredential ("<your liveID>", "<your Azure Marketplace Key>")

    let cities = 
        query { for c in ctx.demog1 do
                where (c.StateName = "Washington") }

    for c in cities do
        printfn "%A - %A" c.GeographyId c.PerCapitaIncome2010.Value

    *)

    ()

 
#if COMPILED

module BoilerPlateForForm =
    [<System.STAThread>]
    do ()
//    do System.Windows.Forms.Application.Run()

#endif

