/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.hadoop.metadata.tools.dsl

import org.apache.hadoop.metadata.dsl._
import org.apache.hadoop.metadata.tools.hive.HiveMockMetadataService
import org.apache.hadoop.metadata.typesystem.types.utils.TypesUtil
import org.apache.hadoop.metadata.typesystem.types.{DataTypes, StructType, TypeSystem}
import org.json4s.native.JsonMethods._
import org.junit.{Assert, Before, Test}

/**
 * DSL Test.
 */
class DSLTest {
    val STRUCT_TYPE_1: String = "t1"
    val STRUCT_TYPE_2: String = "t2"


    @Before
    def setup {
        val ts: TypeSystem = TypeSystem.getInstance
        ts.reset()

        val structType: StructType = ts.defineStructType(
            STRUCT_TYPE_1, true,
            TypesUtil.createRequiredAttrDef("a", DataTypes.INT_TYPE),
            TypesUtil.createOptionalAttrDef("b", DataTypes.BOOLEAN_TYPE),
            TypesUtil.createOptionalAttrDef("c", DataTypes.BYTE_TYPE),
            TypesUtil.createOptionalAttrDef("d", DataTypes.SHORT_TYPE),
            TypesUtil.createOptionalAttrDef("e", DataTypes.INT_TYPE),
            TypesUtil.createOptionalAttrDef("f", DataTypes.INT_TYPE),
            TypesUtil.createOptionalAttrDef("g", DataTypes.LONG_TYPE),
            TypesUtil.createOptionalAttrDef("h", DataTypes.FLOAT_TYPE),
            TypesUtil.createOptionalAttrDef("i", DataTypes.DOUBLE_TYPE),
            TypesUtil.createOptionalAttrDef("j", DataTypes.BIGINTEGER_TYPE),
            TypesUtil.createOptionalAttrDef("k", DataTypes.BIGDECIMAL_TYPE),
            TypesUtil.createOptionalAttrDef("l", DataTypes.DATE_TYPE),
            TypesUtil.createOptionalAttrDef("m", ts.defineArrayType(DataTypes.INT_TYPE)),
            TypesUtil.createOptionalAttrDef("n", ts.defineArrayType(DataTypes.BIGDECIMAL_TYPE)),
            TypesUtil.createOptionalAttrDef("o",
                ts.defineMapType(DataTypes.STRING_TYPE, DataTypes.DOUBLE_TYPE)))

        val recursiveStructType: StructType = ts.defineStructType(
            STRUCT_TYPE_2, true,
            TypesUtil.createRequiredAttrDef("a", DataTypes.INT_TYPE),
            TypesUtil.createOptionalAttrDef("s", STRUCT_TYPE_2))
    }

    @Test def test1 {

        // 1. Existing Types in System
        //Assert.assertEquals(s"${listTypes}", "[t2, t1, int, array<bigdecimal>, long, double, date, float, short, biginteger, byte, string, boolean, bigdecimal, map<string,double>, array<int>]")

        defineStructType("mytype",
            attrDef("a", INT_TYPE, ATTR_REQUIRED),
            attrDef("b", BOOLEAN_TYPE),
            attrDef("c", BYTE_TYPE),
            attrDef("d", SHORT_TYPE),
            attrDef("e", INT_TYPE),
            attrDef("f", INT_TYPE),
            attrDef("g", LONG_TYPE),
            attrDef("h", FLOAT_TYPE),
            attrDef("i", DOUBLE_TYPE),
            attrDef("j", BIGINT_TYPE),
            attrDef("k", BIGDECIMAL_TYPE),
            attrDef("l", DATE_TYPE),
            attrDef("m", arrayType(INT_TYPE)),
            attrDef("n", arrayType(BIGDECIMAL_TYPE)),
            attrDef("o", mapType(STRING_TYPE, DOUBLE_TYPE)))

        // 2. 'mytype' available as a a Type
        Assert.assertEquals(s"${listTypes}", "[array<bigdecimal>,array<int>,map<string,double>,mytype,t1,t2]")

        // 3. Create a 'mytype' instance from Json
        val i = createInstance("mytype", """
        {
                               "$typeName$":"mytype",
                               "e":1,
                               "n":[1,1.1],
                               "h":1.0,
                               "b":true,
                               "k":1,
                               "j":1,
                               "d":2,
                               "m":[1,1],
                               "g":1,
                               "a":1,
                               "i":1.0,
                               "c":1,
                               "l":"2014-12-03T08:00:00.000Z",
                               "f":1,
                               "o":{
                                 "b":2.0,
                                 "a":1.0
                               }
                             }
                                         """)

        // 4. Navigate mytype instance in code
        // Examples of Navigate mytype instance in code
        Assert.assertEquals(s"${i.a}", "1")
        Assert.assertEquals(s"${i.o}", "{b=2.0, a=1.0}")
        Assert.assertEquals(s"${i.o.asInstanceOf[java.util.Map[_, _]].keySet}", "[b, a]")

        // 5. Serialize mytype instance to Json
        Assert.assertEquals(s"${pretty(render(i))}", "{\n  \"$typeName$\":\"mytype\",\n  \"e\":1," + "\n  \"n\":[1,1.100000000000000088817841970012523233890533447265625],\n  \"h\":1.0,\n  \"b\":true,\n  \"k\":1,\n  \"j\":1,\n  \"d\":2,\n  \"m\":[1,1],\n  \"g\":1,\n  \"a\":1,\n  \"i\":1.0,\n  \"c\":1,\n  \"l\":\"2014-12-03\",\n  \"f\":1,\n  \"o\":{\n    \"b\":2.0,\n    \"a\":1.0\n  }\n}")
    }

    @Test def test2 {

        // 1. Existing Types in System
        Assert.assertEquals(s"${listTypes}", "[array<bigdecimal>,array<int>,map<string,double>,t1,t2]")

        val addrType = defineStructType("addressType",
            attrDef("houseNum", INT_TYPE, ATTR_REQUIRED),
            attrDef("street", STRING_TYPE, ATTR_REQUIRED),
            attrDef("city", STRING_TYPE, ATTR_REQUIRED),
            attrDef("state", STRING_TYPE, ATTR_REQUIRED),
            attrDef("zip", INT_TYPE, ATTR_REQUIRED),
            attrDef("country", STRING_TYPE, ATTR_REQUIRED)
        )

        val personType = defineStructType("personType",
            attrDef("first_name", STRING_TYPE, ATTR_REQUIRED),
            attrDef("last_name", STRING_TYPE, ATTR_REQUIRED),
            attrDef("address", addrType)
        )

        // 2. updated Types in System
        Assert.assertEquals(s"${listTypes}", "[addressType,array<bigdecimal>,array<int>,map<string,double>,personType,t1,t2]")


        // 3. Construct a Person in Code
        val person = createInstance("personType")
        val address = createInstance("addressType")

        person.first_name = "Meta"
        person.last_name = "Hadoop"

        address.houseNum = 3460
        address.street = "W Bayshore Rd"
        address.city = "Palo Alto"
        address.state = "CA"
        address.zip = 94303
        address.country = "USA"

        person.address = address

        // 4. Convert to Json
        Assert.assertEquals(s"${pretty(render(person))}", "{\n  \"$typeName$\":\"personType\",\n  \"first_name\":\"Meta\",\n  \"address\":{\n    \"$typeName$\":\"addressType\",\n    \"houseNum\":3460,\n    \"city\":\"Palo Alto\",\n    \"country\":\"USA\",\n    \"state\":\"CA\",\n    \"zip\":94303,\n    \"street\":\"W Bayshore Rd\"\n  },\n  \"last_name\":\"Hadoop\"\n}");

        val p2 = createInstance("personType", """{
                                              "first_name":"Meta",
                                              "address":{
                                                "houseNum":3460,
                                                "city":"Palo Alto",
                                                "country":"USA",
                                                "state":"CA",
                                                "zip":94303,
                                                "street":"W Bayshore Rd"
                                              },
                                              "last_name":"Hadoop"
                                            }""")

    }

    @Test def testHive(): Unit = {
        val hiveTable = HiveMockMetadataService.getTable("tpcds", "date_dim")
        //println(hiveTable)

        //name : String, typeName : String, comment : String
        val fieldType = defineStructType("FieldSchema",
            attrDef("name", STRING_TYPE, ATTR_REQUIRED),
            attrDef("typeName", STRING_TYPE, ATTR_REQUIRED),
            attrDef("comment", STRING_TYPE)
        )
        /*
        SerDe(name : String, serializationLib : String, parameters : Map[String, String])
         */
        defineStructType("SerDe",
            attrDef("name", STRING_TYPE, ATTR_REQUIRED),
            attrDef("serializationLib", STRING_TYPE, ATTR_REQUIRED),
            attrDef("parameters", mapType(STRING_TYPE, STRING_TYPE))
        )

        /*
        StorageDescriptor(fields : List[FieldSchema],
                                   location : String, inputFormat : String,
                                    outputFormat : String, compressed : Boolean,
                                    numBuckets : Int, bucketColumns : List[String],
                                    sortColumns : List[String],
                                    parameters : Map[String, String],
                                    storedAsSubDirs : Boolean
                                    )
         */
        val sdType = defineStructType("StorageDescriptor",
            attrDef("location", STRING_TYPE, ATTR_REQUIRED),
            attrDef("inputFormat", STRING_TYPE, ATTR_REQUIRED),
            attrDef("outputFormat", STRING_TYPE, ATTR_REQUIRED),
            attrDef("compressed", BOOLEAN_TYPE),
            attrDef("numBuckets", INT_TYPE),
            attrDef("bucketColumns", arrayType(STRING_TYPE)),
            attrDef("sortColumns", arrayType(STRING_TYPE)),
            attrDef("parameters", mapType(STRING_TYPE, STRING_TYPE)),
            attrDef("storedAsSubDirs", BOOLEAN_TYPE)
        )

        /*
        case class Table(dbName : String, tableName : String, storageDesc : StorageDescriptor,
                       parameters : Map[String, String],
                        tableType : String)
         */
        defineStructType("Table",
            attrDef("dbName", STRING_TYPE, ATTR_REQUIRED),
            attrDef("tableName", STRING_TYPE, ATTR_REQUIRED),
            attrDef("storageDesc", sdType, ATTR_REQUIRED),
            attrDef("compressed", BOOLEAN_TYPE),
            attrDef("numBuckets", INT_TYPE),
            attrDef("bucketColumns", arrayType(STRING_TYPE)),
            attrDef("sortColumns", arrayType(STRING_TYPE)),
            attrDef("parameters", mapType(STRING_TYPE, STRING_TYPE)),
            attrDef("storedAsSubDirs", BOOLEAN_TYPE)
        )
    }
}
