package tile

import "fmt"
import "strconv"
import "io/ioutil"
import "compress/gzip"
import "compress/zlib"
import "encoding/csv"
import "encoding/binary"


import "gitlab.com/beoran/al5go/al"
// import "gitlab.com/beoran/ebsgo/engine/geometry/point"
import "gitlab.com/beoran/ebsgo/engine/physics/camera"
import "gitlab.com/beoran/ebsgo/engine/fifi"

import "github.com/beevik/etree"


// Tile flipping constants
const (
    TMX_FLIPPED_HORIZONTALLY = 0x80000000
    TMX_FLIPPED_VERTICALLY   = 0x40000000
    TMX_FLIPPED_DIAGONALLY   = 0x20000000
    TMX_FLIPPED_FILTER       = (~0xE0000000)
)


func AttrInt(e * etree.Element, string key) (int, error) {
    return strconv.Atoi(e.SelectAttrValue(key, ""))
}

func AttrFloat64(e * etree.Element, string key) (float64, error) {
    return strconv.ParseFloat(e.SelectAttrValue(key, ""), 64)
}


func Property(e * etree.Element, propname string) string, bool {
  properties := e.FindElements('//properties/property')
  
  for property := range properties {
        name := e.SelectAttrValue("name", "")
        if name == propname {
            return e.SelectAttrValue("value", ""), true
        }
  }
  
  return "", false
}

func PropertyInt(e * etree.Element, propname string) int, error {
    value , ok := Property(e, propname)
    if (!ok) {
        return 0, fmt.Errorf("no such property")
    }
    
    return strconv.Atoi(e.SelectAttrValue(key, ""))
} 


func LoadFrame(xml_frame * etree.Element, tile * Tile, set * Set) * Tile {
    tile_id, err := AttrInt(xml_frame, "tileid")
    if err != nil {
        return tile;
    }
    
    // the tile id obtained here is correct and doesn't need to be 
    // lessened by firstgid
    
    duration, err := AttrFloat64(xml_frame, "duration")
    
    if err != nil {
        return tile;
    }
    
    /* Convert ms to s. */ 
    duration /= 1000.0
    
    tile.AddAnimationFrame(tile_id, duration)
    
    return tile
}


func LoadAnimation(xml_tile * etree.Element, tile * Tile, set * Set) * Tile {
    xml_anim := xml_tile.FindElement("//animation")
    if xml_anim == nil {
        return tile;
    }
    
    xml_frames := xml_anim.FindElements("//frame")
    for xml_frame := range xml_frames {
        LoadFrame(xml_frame, tile, set)
    }
    return tile
}

func XmlToTileType(xml_tile * etree.Element) int, error {
    
    value , ok := Property(e, "type")
    if (!ok) {
        return 0, fmt.Errorf("no type property")
    }
    
    result, ok  := FlagNames[value]
    
    if ! ok {
        return 0, fmt.Errorf("unknown type property")
    }
    
    return result, nil
} 


func LoadTile(xml_tile * etree.Element, index int, set * Set, tm * Map) (*Tile, error) {
     
    if id, err := AttrInt(xml_tile, "id") ; err != nil { 
        return nil, fmr.Errorf("Tile id not found: %s", err)
    }
  
    tile := set.Tile(id)
    if tile == nil { 
        return nil, fmr.Errorf("Tile id not found in tile set: %d", id)
    }
    
    if tiletype, err := XmlToTileType(xml_tile) ; err == nil {
        tile.Type = tiletype
    }
    
    // ianim       , err := PropertyInt(xml_tile, "anim"       )
    // iwait       , err := PropertyInt(xml_tile, "wait"       )
    ilight      , err := PropertyInt(xml_tile, "light"      )
    iblend      , err := PropertyInt(xml_tile, "blend"      )
    ishadow     , err := PropertyInt(xml_tile, "shadow"     )
    ilightmask  , err := PropertyInt(xml_tile, "lightmask"  )
    iblendmask  , err := PropertyInt(xml_tile, "blendmask"  )
    ishadowmask , err := PropertyInt(xml_tile, "shadowmask" )
    
    // No support anymore for "classic" animations since TMX now 
    // supports animations.
    tile.Light      = ilight
    tile.Blend      = iblend
    tile.Shadow     = ishadow
    tile.LightMask  = ilightmask
    tile.BlendMask  = iblendmask
    tile.ShadowMask = ishadowmask

    /* Support tiled-style animations. */
    return LoadAnimation(xml_tile, tile, set)
}


/** Calculates index and the Allegro draw flags and rotation for a tile. */

func TmxTileIndexFlagsAndRotation(tmx_index int32) int, int, float32 { 
    index := int(tmx_index & TMX_FLIPPED_FILTER) 
    flags := 0
    rotate := 0.0

    if (tmx_index & TMX_FLIPPED_HORIZONTALLY) != 0 {
        flags |= al.FLIP_HORIZONTAL
    }
   
    if (tmx_index & TMX_FLIPPED_VERTICALLY) != 0 {
        flags |= al.FLIP_VERTICAL
    } 
  
    if (tmx_index & TMX_FLIPPED_DIAGONALLY) {
        rotate := al.PI / 2.0
    }

  return index, flags, rotate
}


func LoadSet(xml_set * etree.Element, index int, tm * Map) * Set, error { 
    firstgid, err := AttrInt(xml_set, "firstgid")
    if err != nil {
        return nil, fmt.Errorf("Could not find firstgid for tile set %d: %s", index, err)
    }
    
    xml_image := xml_set.FindElement("//image")
    
    if xml_image == nil {
        return nil, fmt.Errorf("Could not find image data for tile set %d", index)
    }
    
    tile_w, err := AttrInt("tilewidth")
    tile_h, err := AttrInt("tilewidth")
    
    if (tile_w < 1) || (tile_h < 1) {
        return nil, fmt.Errorf("Tile size too small for tile set %d: %d x %d", index, tile_w, tile_h)
    }
    
    iname   := xml_image.SelectAttrValue("source", "tile_set_default.png")    
    bmp     := fifi.LoadBitmap("map/" + iname)
    set     := NewSet(bmp, tile_w, tile_h, firstgid)
    
    xml_tiles := xml_set.FindElements("//tile")
    
    for xml_tile, tile_index := range xml_tiles {
        LoadTile(xml_tile, tile_index, set, tm)
    }
    
    return set, nil
}

type Expander(in []byte) []byte, error 

type Decoder(data string, w, h int, expand Expander) [][]uint32, error 


func CsvDecoder(data string, w, h int, expand Expander) [][]uint32, error {
    
    r := csv.NewReader(strings.NewReader(data))
    records, err := r.ReadAll()
    if err != nil {
        return err
    }
    
    result := make([][]uint32, h) 
    for i := 0 ; i < h; i++ { 
        result[i] := make([]uint32, w) 
        for j := 0 ; i < w ; j++ { 
            res, err := strconv.ParseUint(records[i][j], 10, 32) 
            if err != nil {
            result[i][j] = 0
            } else {
                result[i][j] = uint32(res)
            }
        }
    }
    return result
}

func GzipExpander(in []byte) []byte, error {
    zr, err := gzip.NewReader(&in)
    if err != nil {
        return nil, err
    }
    return ioutil.ReadAll(zr)
}

func ZlibExpander(in []byte) []byte, error {
    zr, err := lib.NewReader(&in)
    if err != nil {
        return nil, err
    }
    return ioutil.ReadAll(zr)
}


func Base64Decoder(data string, w, h int, expand Expander) [][]uint32, error {
    bytes, err := base64.StdEncoding.DecodeString()
    if err != nil {
        return nil, err
    }
    
    if expand != nil {
        bytes, err = expand(bytes)
        if err != nil {
            return nil, err
        }
    }
    
    result := make([][]uint32, h) 
    for i := 0 ; i < h; i++ { 
        result[i] := make([]uint32, w) 
        for j := 0 ; i < w ; j++ { 
            index := (i * w + j) * 4
            value := binary.LittleEndian.Uint32(bytes[index:4])
            result[i][j] = value
        }
    }

    return result, nil
}

var Decoders map[string]Decoder = map[string]Decoder {
    "csv"   : CsvDecoder,
    "base64": Base64Decoder,
}

var Expanders map[string]Expander = map[string]Expander {
    "gzip"   : GzipExpander,
    "zlib": ZlibExpander,
}



// Loads a single tile pane of the tile map from xml (tmx). 
func LoadPane(xml_pane * etree.Element, int index, tm * Map) (* Pane, error) {  
    err, width := AttrInt(xml_pane, "width")
    err, height := AttrInt(xml_pane, "height")
  
    if (width < 1) || (height < 1) {
        return nil, 
        fmr.Errorf("Layer %i size too small: %d x %d, ", index, width, height)
    }
    
    name := xml_pane.SelectAttrValue("name", "")
    
    iset, err := PropertyInt(xml_pane, "tileset")
    
    if err != nil {
        iset = 0
    }
    
    set := tm.Set(iset)
    
    if set == nil {
      return nil, 
      fmt.Errorf("No tile set found for layer %d: %d", index, iset)
    }
    
    pane := NewPane(set, width, height, name)
    
    xml_data := xml_pane.FindElement('//data')
    
    if xml_data == nil {
        return nil, fmr.Errorf("Cannot find layer data for layer %d", index)
    }
    
    encoding    := xml_data.SelectAttrValue("encoding"   , "csv")
    compression := xml_data.SelectAttrValue("compression", "")
    
    decoder     := Decoders[encoding]
    
    if decoder == nil {
        return nil, fmt.Errorf("Unknown encoding %s", encoding)
    }
    
    expander    := Expanders[compression]
    
    grid, err   := decoder(xml_data.Text(), width, height, expander) 

    if err != nil {
        return nil, fmt.Errorf("Could not decode data for layer %d: %s %s: %s",
         index, decoder, expander, err )
    }
    
    for y := 0 : y < height, y ++ {
        for x := 0 ; x < width ; x++ {
            tmx_index = grid[y][x]
            if tmx_index == 0 { 
                pane.SetTile(x, y, nil)
            } else {
                index, flags, rotation := TmxTileIndexFlagsAndRotation(tmx_index)
                if pane.Set == nil { 
                    pane.Set = tm.LookupTmxTileset(index)
                }
                ebs_index   := index - pane.FirstGID + 1 
                cell        := pane.Cell(x, y)
                cell.Tile       = pane.Set.Tile(ebs_index)
                cell.Tile.Flags = flags
                cell.Tile.Rotate= rotation 
            }
        }
    }
    return pane, nil
}


// Load a TMX map file from the given XML document.
func LoadMapXml(doc * etree.Document) (* Map, error) {
    root ::= doc.Root()
    if root.Tag != "map" {
        return nil, fmt.Errorf("File is not a TMX map file: %s", root.Tag)
    }
    
    if wide, err := AttrInt(root, "width"); err != nil {
        return nil, fmt.Errorf("Width not a number: %s", err)
    }
    
    if high, err := AttrInt(root, "height"); err != nil {
        return nil, fmt.Errorf("Height not a number: %s", err)
    }
    
    if (wide < 1)  || (high < 1) {
        return nil, fmt.Errorf("Map size too smalll: %d x %d",wide, high)
    }
    
    tm := NewMap(wide, high)
    
    xml_sets := root.FindElements('//tileset')
    
    for xml_set, i := range xml_sets {
        set, err := LoadSet(xml_set, i)
        if err != nil {
            return nil, fmt.Errorf("Could not load tile set: %s", err)
        } else {
            set := tm.AddSet(set)
        }
    }
    
    xml_panes := root.FindElements('//layer')
    
    for xml_pane, i := range xml_panes {
        pane, err := LoadLayer(xml_pane, i, tm.Sets)
        if err != nil {
            return nil, fmt.Errorf("Could not load tile layer: %s", err)
        } else {
            tm.AddPane(pane)
        }
    }
      
  return tm, nil;          
}

// Loads a TMX tile map from the named file.
// Return nil on error.
func LoadMap(filename string) (* Map, error) {
    doc := etree.NewDocument()
    err = doc.ReadFromFile(filename)
    if err != nil {
        return nil, err
    }
    return LoadMapXml(doc)
}